带有继承的Python单元测试测试用例



目前我有许多类似的单元测试用例。每个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'

两个问题:

  • 我的"设计"好吗
  • 还需要什么才能让它发挥作用

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

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

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

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()这样的方法时,它不会在基测试类上被调用,所以你必须在你编写的每个扩展基类的类中调用它。

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

BTW:这是在蟒蛇3中完成的-我不知道它在蟒蛇2 中会有什么反应

更新:

这可能更好,更像蟒蛇

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",测试运行器就不会解析这些测试,它们也不会在套件中运行。它们只会在基类扩展它们的地方运行。

要使其按预期工作,您至少需要:

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

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

设计(或多或少)很好——一个"小问题"是,当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()

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

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

最新更新