【问题标题】:Python unittest TestCase with inheritance具有继承的Python unittest TestCase
【发布时间】:2015-02-04 23:21:45
【问题描述】:

目前我有许多类似的单元测试测试用例。每个 TestCase 都包含数据(输入值 + 预期输出值)和逻辑(调用 SUT 并将实际输出与预期输出进行比较)。

我想将数据与逻辑分开。因此,我想要一个只包含逻辑的基类和一个只包含数据的派生类。到目前为止,我想出了这个:

import unittest

class MyClass():
    def __init__(self, input):
        self.input = input
    def get_result(self):
        return self.input * 2

class TestBase(unittest.TestCase):
    def check(self, input, expected_output):
        obj = self.class_under_test(input)
        actual_output = obj.get_result()
        self.assertEqual(actual_output, expected_output)

    def test_get_result(self):
        for value in self.values:
            self.check(value[0], value[1])

class TestMyClass(TestBase):
    def __init__(self, methodName='runTest'):
        unittest.TestCase.__init__(self, methodName)        
        self.class_under_test = MyClass
        self.values = [(1, 2), (3, 6)]

unittest.main(exit = False)

但这失败并出现以下错误:

AttributeError: 'TestBase' object has no attribute 'values'

两个问题:

  • 我的“设计”有用吗?
  • 还需要什么才能使其正常工作?

【问题讨论】:

  • 请注意,将TestCase.__init__ 用于任何事情都是不寻常的(根据我的经验)。一般初始化代码在TestCase.setUp

标签: python inheritance unit-testing


【解决方案1】:

这里有点晚,但最近开始需要有单元测试继承

我能找到的最优雅的解决方案是:

首先 - 你需要一个基础测试类

class MyBaseUnitTest(unittest.TestCase):
    __test__ = False
    def test_someting(self):
        ...

    def test_something_else(self):
        ...

然后继承该类并运行测试:

class TestA(MyBaseUnitTest):
    __test__ = True

    def test_feature(self):
        pass
    def test_feature2(self):
        pass

这是拥有单一视图集继承的最佳且最简单的方法。

我发现多重继承的问题是,当您尝试调用 setUp() 之类的方法时,它不会在基测试类上调用,因此您必须在您编写的每个扩展基类的类中调用它。

我希望这将在未来的某个地方帮助某人。

顺便说一句:这是在 python3 中完成的 - 我不知道它在 python2 中会如何反应

更新:

这可能更好,更pythonic

class MyBaseUnitTest(object):
    def test_someting(self):
        ...

    def test_something_else(self):
        ...

class TestA(MyBaseUnitTest, unittest.TestCase):

    def test_feature(self):
        pass
    def test_feature2(self):
        pass

只要基础测试类不扩展“unittest.TestCase”,测试运行器就不会解析这些测试,它们也不会在套件中运行。它们只会在基类扩展它们的地方运行。

【讨论】:

  • 我发现更新中建议的代码存在潜在问题。由于 mixin MyBaseUnitTest 没有从 unittest.TestCase 继承,因此无法在其中调用 self.assertEqual 等。
【解决方案2】:

Python 的unittest.main() 执行来自当前命名空间中所有测试类的所有测试。

不直接在基类中运行测试的一种方法是将其移动到不同的模块(例如,将 TestBase 移动到 testbase.py):

在调用unittest.main() 的文件中,确保不要将该类导入您的命名空间(不要使用from testbase import * 或类似名称):

import testbase

class TestMyClass1(testbase.TestBase):
    values = ((1, 2), (3, 4))
    class_under_test = MyClass1

【讨论】:

    【解决方案3】:

    设计(或多或少)很好——一个“小问题”是当 unittest 查看 所有 TestCase 类并在它们上运行以“test”开头的方法时。此时您有几个选择。

    一种方法是将被测类和值指定为 上的属性。在这里,如果可能的话,你会希望这些值是不可变的......

    class TestBase(unittest.TestCase):
    
        def check(self, input, expected_output):
            obj = self.class_under_test(input)
            actual_output = obj.get_result()
            self.assertEqual(actual_output, expected_output)
    
        def check_all(self):
            for value in self.values:
                self.check(value[0], value[1])
    
    class TestMyClass1(TestBase):
        values = ((1, 2), (3, 4))
        class_under_test = MyClass1
    
        def test_it(self):
            self.check_all()
    
    class TestMyClass2(TestBase):
        values = (('a', 'b'), ('d', 'e'))
        class_under_test = MyClass2
    
        def test_it(self):
            self.check_all()
    

    【讨论】:

      【解决方案4】:

      要使这项工作按预期进行,您至少需要:

      • 确保您的子类测试用例的 init 方法与 TestCase 的方法匹配,即__init__(self, methodName="runTest")
      • 在子类的 init 方法中添加一个超级调用,例如super(TestMyClass, self).__init__(methodName)
      • 为 test_get_result 添加一个 self 参数,即def test_get_result(self):

      至于它是否是好的设计,请记住,您的测试部分地充当了您的代码如何工作的文档。如果您将所有工作都隐藏在 TestCase 实例状态中,那么它的作用就不会那么明显了。例如,您最好编写一个具有自定义断言的 mixin 类,该断言接受输入和预期输出。

      【讨论】:

      • 您能举一个使用 mixin 进行更好设计的例子吗?
      • 这个答案帮助我解决了 TestLoader().loadTestsFromModule() 遇到的类似继承问题。添加修改后的 __init__() 对我有用。
      【解决方案5】:

      你打了两次test_get_result()。我认为任何test*() 方法都不应该在TestBase 中。相反,使用TestBase 提供自定义断言、错误格式化程序、测试数据生成器等,并将实际测试保存在TestMyClass 中。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-09-30
        • 1970-01-01
        • 1970-01-01
        • 2014-07-01
        • 2014-11-15
        • 1970-01-01
        • 2023-03-21
        • 2021-11-28
        相关资源
        最近更新 更多