【问题标题】:How does Python's unittest module detect test cases?Python 的 unittest 模块如何检测测试用例?
【发布时间】:2012-04-11 03:40:36
【问题描述】:

我想知道当我们运行unittest.main() 时,Python 是如何知道unittest.Testcase 有哪些子类?

例如,如果我添加一个类FromRomanBadInput(unittest.TestCase)unittest 怎么知道运行这个?

【问题讨论】:

  • 元类是很酷的东西:stackoverflow.com/questions/100003/…
  • 明白。非常感谢 Amber!
  • @Amber 你是什么意思? unittest 没有单个元类。
  • 只是指出元类是实现检测给定类的子类的一种方法。
  • @Amber 这不是他们使用的,但如果他们使用它可能会更干净。

标签: python unit-testing


【解决方案1】:

所以我在我的Python27/Lib 目录中环顾四周...

unittest.main 实际上是一个类的别名,unittest.TestProgram。所以发生的事情是你构造了一个这个实例,它的__init__ 运行,它进行了一堆健全性检查和配置,包括你调用它的模块的动态导入(它使用__import__ 函数,与__main__ 作为要导入的模块的名称,默认情况下)。所以现在它有一个 self.module 属性,其中包含一个代表您的源的模块对象。

最终,它得到了这段代码:

self.test = self.testLoader.loadTestsFromModule(self.module)

其中self.testLoaderunittest.TestLoader 的一个实例。该方法包含以下内容:

    for name in dir(module):
        obj = getattr(module, name)
        if isinstance(obj, type) and issubclass(obj, case.TestCase):
            tests.append(self.loadTestsFromTestCase(obj))

因此它使用模块对象的dir 来获取您定义的所有全局变量(包括类)的名称,将其过滤到仅派生自unittest.TestCase 的类(在本地,case.TestCase 是别名),然后在这些类中查找测试方法以添加到tests 列表中。该搜索的行为类似:

    def isTestMethod(attrname, testCaseClass=testCaseClass,
                     prefix=self.testMethodPrefix):
        return attrname.startswith(prefix) and \
            hasattr(getattr(testCaseClass, attrname), '__call__')
    testFnNames = filter(isTestMethod, dir(testCaseClass))

所以它使用类的dir 来获取要尝试的名称列表,查找具有这些名称的属性,并选择那些以self.testMethodPrefix 开头(默认为'test')并且可调用的属性(反过来,有一个__call__ 属性)。 (我真的很惊讶他们在这里没有使用内置的callable 函数。我想这是为了避免使用嵌套类。)

【讨论】:

  • 哇。这真的很全面,很有帮助。感谢您的时间和精力!
  • 可能很全面,但我真的无法想象它是如何有帮助的。文档已经告诉你如何使用 unittest,这应该是你真正需要知道的。
  • @KarlKnechtel:好吧,碰巧你确实帮助了我这个答案。 :) 我为单元测试和整个测试用例编写了一个装饰器,并且想知道为什么测试运行器不会执行装饰过的测试用例。运行上面的代码,我意识到我忘记了我的装饰器(没有参数)将我的 unittest.TestCase 子类变成了一个完全不同的对象......
  • @KarlKnechtel 它帮助了我,因为我试图动态添加测试用例,假设鼻子创建了我拥有的 unittest.TestCase 类的实例,即它调用 init在我的考试课上。它没有,它直接检查类。所以它也对我有帮助。
【解决方案2】:

'main' 函数在导入的模块中搜索所有继承了 unittest.TestCase 的类。和当前路径,然后尝试运行以 'test' 开头的每个方法

来自python's document

import random
import unittest

class TestSequenceFunctions(unittest.TestCase):

    def setUp(self):
        self.seq = range(10)

    def test_shuffle(self):
        # make sure the shuffled sequence does not lose any elements
        random.shuffle(self.seq)
        self.seq.sort()
        self.assertEqual(self.seq, range(10))

        # should raise an exception for an immutable sequence
        self.assertRaises(TypeError, random.shuffle, (1,2,3))

    def test_choice(self):
        element = random.choice(self.seq)
        self.assertTrue(element in self.seq)

    def test_sample(self):
        with self.assertRaises(ValueError):
            random.sample(self.seq, 20)
        for element in random.sample(self.seq, 5):
            self.assertTrue(element in self.seq)

if __name__ == '__main__':
    unittest.main()

通过继承unittest.TestCase 来创建一个测试用例。他们三个 单独的测试是用名称以开头的方法定义的 字母测试。此命名约定通知测试运行程序 哪些方法代表测试。

【讨论】:

    【解决方案3】:

    我写了一些代码,试图做类似于下面的 unittest.main() 的行为。总之,我遍历模块,对于不以名称“unittest”开头的模块,我检查它的成员。然后,如果这些成员是一个类并且是 unittest.TestCase 的子类,我会解析该类的成员。然后,如果这些类的成员是以“test”开头的函数或方法,我将其添加到测试列表中。类对象的__dict__ 用于内省方法/函数,因为使用inspect.getmembers 可能会显示太多。最后,该测试列表被转换为一个元组并包装为一个套件。然后使用运行器以详细级别 2 运行该套件。请注意,当然,可以删除在函数/方法名称开头检查“test”的正则表达式,以将 bar_test() 包含到测试列表中如果你不想要那个限制。

    #!/usr/bin/env python
    
    import unittest
    import inspect
    import sys
    import re
    
    class Foo(unittest.TestCase):
       @staticmethod
       def test_baz():
          pass
    
       @classmethod
       def test_mu(cls):
          pass
    
       def test_foo(self):
          self.assertEqual('foo', 'foo')
    
       def bar_test(self):
          self.assertEqual('bar', 'bar')
    
    class Bar:
       pass
    
    if __name__ == '__main__':
       runner = unittest.TextTestRunner(verbosity=2)
       tests = []
       is_member_valid_test_class = lambda member: inspect.isclass(member) and \
          issubclass(member, unittest.TestCase)
    
       for module_name, module_obj in sys.modules.items():
          if not re.match(r'unittest', module_name):
             for cls_name, cls in inspect.getmembers(
                module_obj, is_member_valid_test_class):
                for methname, methobj in cls.__dict__.items():
                   if inspect.isroutine(methobj) and re.match(r'test', methname):
                      tests.append(cls(methname))
    
       suite = unittest.TestSuite(tests=tuple(tests))
       runner.run(suite)
    

    结果输出是:

    test_foo (__main__.Foo) ... ok
    test_baz (__main__.Foo) ... ok
    test_mu (__main__.Foo) ... ok
    
    ----------------------------------------------------------------------
    Ran 3 tests in 0.001s
    
    OK
    

    【讨论】:

      猜你喜欢
      • 2012-09-04
      • 2022-10-18
      • 1970-01-01
      • 2011-06-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-15
      • 2013-04-09
      • 1970-01-01
      相关资源
      最近更新 更多