如何在Python unittest中创建和使用“返回测试套件的可调用对象”?

5 投票
5 回答
3731 浏览
提问于 2025-04-15 12:32

我正在学习Python,想更深入地了解Python的unittest模块的细节。文档中提到:

为了方便运行测试,稍后我们会看到,在每个测试模块中提供一个可调用的对象,这个对象可以返回一个预先构建好的测试套件,是个不错的主意:

def suite():
    suite = unittest.TestSuite()
    suite.addTest(WidgetTestCase('testDefaultSize'))
    suite.addTest(WidgetTestCase('testResize'))
    return suite

从我目前的理解来看,这样做的目的并没有解释清楚。此外,我也搞不明白该怎么使用这种方法。我尝试了几种方法,但都没有成功(除了学到了一些错误信息):

import unittest

def average(values):
    return sum(values) / len(values)

class MyTestCase(unittest.TestCase):
    def testFoo(self):
        self.assertEqual(average([10,100]),55)

    def testBar(self):
        self.assertEqual(average([11]),11)

    def testBaz(self):
        self.assertEqual(average([20,20]),20)

    def suite():
        suite = unittest.TestSuite()
        suite.addTest(MyTestCase('testFoo'))
        suite.addTest(MyTestCase('testBar'))
        suite.addTest(MyTestCase('testBaz'))
        return suite

if __name__ == '__main__':
    # s = MyTestCase.suite()
    # TypeError: unbound method suite() must be called 
    # with MyTestCase instance as first argument

    # s = MyTestCase.suite(MyTestCase())
    # ValueError: no such test method in <class '__main__.MyTestCase'>: runTest

    # s = MyTestCase.suite(MyTestCase('testFoo'))
    # TypeError: suite() takes no arguments (1 given)

下面这个“有效”,但感觉有点别扭,而且我还需要把suite()的方法签名改成'def suite(self):'。

s = MyTestCase('testFoo').suite()
unittest.TextTestRunner().run(s)

5 个回答

1

文档里提到,suite() 应该是模块里的一个函数,而不是你类里的一个方法。这个函数主要是为了方便你以后可以把多个模块的测试套件合并在一起。

alltests = unittest.TestSuite([
  my_module_1.suite(),
  my_module_2.suite(),
])

你在调用这个函数时遇到的问题,都是因为它是类里的一个方法,但又没有按照方法的方式来写。self 参数代表“当前对象”,这是实例方法所必需的。(比如说,a.b(1, 2)b(a, 1, 2) 在概念上是一样的。)如果这个方法是针对类本身而不是实例的,可以看看 classmethod 的相关内容。如果这个方法只是为了方便和类放在一起,但既不操作类也不操作实例,那就看看 staticmethod 的相关内容。这两者对你使用 unittest 可能没有直接帮助,但可以帮助你理解你遇到的问题。

1

有一点很重要,nose 是一个非常简单易用的工具,用来运行测试。它让你可以在命令行中精确指定要运行哪些测试,还能自动遍历每个文件夹,运行里面的每个测试。

6

你收到的第一个错误信息是很重要的,它解释了很多事情。

print MyTestCase.suite # <unbound method MyTestCase.suite>

未绑定。这意味着你不能直接调用它,除非把它绑定到一个实例上。对于 MyTestCase.run 也是一样的:

print MyTestCase.run # <unbound method MyTestCase.run>

也许现在你还不明白为什么不能调用 suite,但请先把这个问题放一边。你会尝试像上面那样在类上调用 run 吗?像这样:

MyTestCase.run() # ?

可能不会,对吧?这样写没有意义,而且也不会成功,因为 run 是一个实例方法,需要一个 self 实例来工作。看起来 Python 对 suite 的理解和对 run 是一样的,都是未绑定的方法。

让我们看看为什么:

如果你试着把 suite 方法放在类的外面,定义成一个全局函数,它就能正常工作:

import unittest

def average(values):
    return sum(values) / len(values)

class MyTestCase(unittest.TestCase):
    def testFoo(self):
        self.assertEqual(average([10,100]),55)

    def testBar(self):
        self.assertEqual(average([11]),11)

    def testBaz(self):
        self.assertEqual(average([20,20]),20)

def suite():
    suite = unittest.TestSuite()
    suite.addTest(MyTestCase('testFoo'))
    suite.addTest(MyTestCase('testBar'))
    suite.addTest(MyTestCase('testBaz'))
    return suite

print suite() # <unittest.TestSuite tests=[<__main__.MyTestCase testMethod=testFoo>, <__main__.MyTestCase testMethod=testBar>, <__main__.MyTestCase testMethod=testBaz>]>

但你不想把它放在类的外面,因为你想调用 MyTestCase.suite()

你可能觉得既然 suite 是某种“静态”的,或者说不依赖于实例的,那就没有必要加 self 参数,对吧?这没错。

但是如果你在 Python 类里面定义一个方法,Python 会期待这个方法的第一个参数是 self。仅仅省略 self 参数并不会让你的方法自动变成 static。当你想定义一个“静态”方法时,你需要使用 staticmethod 装饰器:

@staticmethod
def suite():
    suite = unittest.TestSuite()
    suite.addTest(MyTestCase('testFoo'))
    suite.addTest(MyTestCase('testBar'))
    suite.addTest(MyTestCase('testBaz'))
    return suite

这样一来,Python 就不会把 MyTestCase 视为实例方法,而是当作一个函数:

print MyTestCase.suite # <function suite at 0x...>

当然,现在你可以调用 MyTestCase.suite(),这会按预期工作。

if __name__ == '__main__':
    s = MyTestCase.suite()
    unittest.TextTestRunner().run(s) # Ran 3 tests in 0.000s, OK

撰写回答