• unittest框架


    unittest框架

    About
    回到顶部

    unittest是Python内置的单元测试框架(模块),不仅可以完成单元测试,也适用于web自动化测试中。

    unittest提供了丰富的断言方法,判断测试用例是否通过,然后生成测试结果报告。

    必要的准备与注意事项
    回到顶部

    首先,我们准备这样一个目录:

    M:tests  # 我的是M盘的tests目录,所有操作都在tests目录内完成
        ├─discover   
        │  ├─son
        │  │  ├─test_dict.py 
        │  │  └─__init__.py
        │  ├─test_list.py
        │  ├─test_str.py
        │  └─__init__.py
        ├─loadTestsFromTestCaseDemo
        │  └─loadTestsFromTestCaseDemo.py
        ├─case_set.py
        ├─myMain.py   # 代码演示文件,所有演示脚本文件
        ├─test_tuple.py
        └─__init__.py
    

    如果你跟我的流程走, 请务必建立和理解这样的一个目录,目前这些文件都是空的,后续会一一建立,各目录内的__init__.py也必须建立,虽然它是空的,但是它无比重要,因为它标明它所在目录是Python的包。

    case_set.py有4个函数,分别计算加减乘除,并且代码不变:

    """
    用例集
    """
    

    def add(x, y):
    """ 两数相加 """
    return x + y

    def sub(x, y):
    """ 两数相减 """
    return x - y

    def mul(x, y):
    """ 两数相乘 """
    return x * y

    def div(x, y):
    """ 两数相除 """
    return x / y

    if name == 'main':
    print(div(10, 5))
    print(div(10, 0))

    上述4个函数将成为我们的测试用例。

    另外,示例演示环境是:

    python3.6 + windows10 + pycharm2018.1
    

    注意!注意!!注意!!!

    如果你对pycharm的使用烂熟于心,那么在运行接下来的示例时,请不要右键运行或者点击运行按钮执行脚本,而是通过Terminal或者终端执行脚本,因为pycharm的集成环境会影响测试结果。

    unittest简单上手
    回到顶部

    runTest

    import unittest  # 导入unittest框架
    import case_set  # 导入用例集
    

    class myUnitTest(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">setUp</span><span class="hljs-params">(self)</span>:</span>
        <span class="hljs-string">""" 用例初始化 """</span>
        print(<span class="hljs-string">"用例初始化 setup"</span>)
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">runTest</span><span class="hljs-params">(self)</span>:</span>
        <span class="hljs-string">""" 执行用例 """</span>
        print(case_set.add(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>) == <span class="hljs-number">5</span>)
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">tearDown</span><span class="hljs-params">(self)</span>:</span>
        <span class="hljs-string">""" 用例执行完,收尾 """</span>
        print(<span class="hljs-string">"用例执行完毕,收尾"</span>)
    

    if name == 'main':
    demo = myUnitTest()
    demo.run() # 固定的调用方法run

    执行结果:

    Ran 1 test in 0.002s
    

    OK
    用例初始化 setup
    True
    用例执行完毕,收尾

    由结果可以看到,1个用例在多少时间内执行完毕,并且用例执行通过。

    用例的执行流程是:

    • setUp先开第一枪,处理一些初始化操作。
    • 接着runTest执行用例,用例返回True。
    • 最后,tearDown打扫战场!

    在每个用例执行时,setUp和tearDown都会执行。

    注意:

    • myUnitTest类名可以自定义,但是必须继承unittest.TestCase
    • 示例中的setUp和tearDown方法名是固定的,但如果,我们测试用例时,没有初始化和收尾的工作,setUp和tearDown方法可以省略不写。

    至于runTest方法名叫什么,取决于在实例化myUnitTest类时,是否传参,我们来看unittest.TestCase类的__init__方法和run方法做了什么:

    class TestCase(object):
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span><span class="hljs-params">(self, methodName=<span class="hljs-string">'runTest'</span>)</span>:</span>
        self._testMethodName = methodName
        self._outcome = <span class="hljs-keyword">None</span>
        self._testMethodDoc = <span class="hljs-string">'No test'</span>  <span class="hljs-comment"># 也请留意这个鬼东西 No test</span>
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">run</span><span class="hljs-params">(self, result=None)</span>:</span>
        <span class="hljs-comment"># run方法反射了methodName</span>
        testMethod = getattr(self, self._testMethodName)
    

    可以看到,在实例化的时候,其实有个methodName默认参数,正好也叫runTest。而在实例化后,实例化对象调用run方法的时候,反射了那个methodName值,然后用例正常执行了。

    所以,runTest方法名可以自定义:

    import unittest
    import case_set
    

    class myUnitTest(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_test</span><span class="hljs-params">(self)</span>:</span>
        <span class="hljs-string">""" 执行用例 """</span>
        print(case_set.add(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>) == <span class="hljs-number">5</span>)
    

    if name == 'main':
    demo = myUnitTest(methodName='add_test')
    demo.run()

    执行多个用例

    那么,如果要执行多个用例怎么办?

    import unittest
    import case_set
    

    class myUnitTestAdd(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">runTest</span><span class="hljs-params">(self)</span>:</span>
        <span class="hljs-string">""" 执行用例 """</span>
        print(case_set.add(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>) == <span class="hljs-number">5</span>)
    

    class myUnitTestSub(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">runTest</span><span class="hljs-params">(self)</span>:</span>
        <span class="hljs-string">""" 执行用例 """</span>
        print(case_set.sub(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>) == <span class="hljs-number">5</span>)  <span class="hljs-comment"># 用例结果不符合预期</span>
    

    if name == 'main':
    demo1 = myUnitTestAdd()
    demo2 = myUnitTestSub()
    demo1.run()
    demo2.run()

    上面的示例,也可以写成:

    import unittest
    import case_set
    

    class myUnitTest(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_test</span><span class="hljs-params">(self)</span>:</span>
        <span class="hljs-string">""" 执行用例 """</span>
        print(case_set.add(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>) == <span class="hljs-number">5</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">sub_test</span><span class="hljs-params">(self)</span>:</span>
        <span class="hljs-string">""" 执行用例"""</span>
        print(case_set.sub(<span class="hljs-number">10</span>, <span class="hljs-number">5</span>) == <span class="hljs-number">2</span>)
    

    if name == 'main':
    demo1 = myUnitTest('add_test')
    demo2 = myUnitTest('sub_test')
    demo1.run()
    demo2.run()

    如上方式,每个用例都要实例化一次,虽然可以执行多个用例,但是这么写实在是太low了,反倒没有之前测试除法用例来的简单。

    另外,用print打印也不符合真实的测试环境。

    我们先来解决print的问题。

    使用unittest提供的断言
    回到顶部

    来看看unittest为我们提供了哪些断言方法吧!

    unittet.TestCase提供了一些断言方法用来检查并报告故障。

    下表列出了最常用的方法:

    Method Checks that description New in
    assertEqual(a, b, msg) a == b 如果a不等于b,断言失败
    assertNotEqual(a, b, msg) a != b 如果a等于b,断言失败
    assertTrue(x, msg) bool(x) is True 如果表达式x不为True,断言失败
    assertFalse(x, msg) bool(x) is False 如果表达式x不为False,断言失败
    assertIs(a, b, msg) a is b 如果a is not 2,断言失败 3.1
    assertIsNot(a, b, msg) a is not b 如果a is b,断言失败 3.1
    assertIsNone(x, msg) x is not None 如果x不是None,断言失败 3.1
    assertIn(a, b, msg) a in b 如果a not in b,断言失败 3.1
    assertNotIn(a, b, msg) a not in b 如果a in b,断言失败 3.1
    assertIsInstance(a, b, msg) isinstance(a, b) 如果a不是b类型,断言失败 3.2
    assertNotIsInstance(a, b, msg) not isinstance(a, b) 如果a是b类型,断言失败 3.2

    示例:

    import unittest
    

    class TestStringMethods(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_assertEqual</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, msg=<span class="hljs-string">'1 != 2'</span>)  <span class="hljs-comment"># AssertionError: 1 != 2 : 1 != 2</span>
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_assertTrue</span><span class="hljs-params">(self)</span>:</span>
        self.assertTrue(<span class="hljs-string">''</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_assertFalse</span><span class="hljs-params">(self)</span>:</span>
        self.assertFalse(<span class="hljs-string">''</span>)
    

    if name == 'main':
    unittest.main()

    所有的assert方法都接收一个msg参数,如果指定,该参数将用作失败时的错误提示。

    结果示例:

    F.F
    ======================================================================
    FAIL: test_assertEqual (__main__.TestStringMethods)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "myMain.py", line 251, in test_assertEqual
        self.assertEqual(1, 2, msg='1 != 2')  # AssertionError: 1 != 2 : 1 != 2
    AssertionError: 1 != 2 : 1 != 2
    

    ======================================================================
    FAIL: test_assertTrue (main.TestStringMethods)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
    File "myMain.py", line 254, in test_assertTrue
    self.assertTrue('')
    AssertionError: '' is not true

    ----------------------------------------------------------------------
    Ran 3 tests in 0.001s

    FAILED (failures=2)

    结果中,F.F表示,如果用例通过返回.,失败返回F,所以结果告诉我们执行了3个用例,成功1个,失败两个FAILED (failures=2)AssertionError是错误信息。

    unittest.TestSuite
    回到顶部

    测试套件(test suite)是由许多测试用例组成的复合测试,也可以理解为承载多个用例集合的容器。
    使用时需要创建一个TestSuite实例对象,然后使用该对象添加用例:

    • suite_obj.addTest(self, test),添加一个测试用例。
    • suite_obj.addTests(self, tests),添加多个测试用例。
    • 在实例化方法中添加测试用例。

    当添加完所有用例后,该测试套件将被交给测试执行(运行)器,如TextTestRunner,该执行器会按照用例的添加顺序执行各用例,并聚合结果。

    TestSuite有效的解决了:

    • 因为是顺序执行,当多个用例组成一个链式测试操作时,谁先谁后的问题就不存在了。
    • 有效地将多个用例组织到一起进行集中测试,解决了之前一个一个测试的问题。

    suite_obj.addTest(self, test)

    import unittest
    import case_set
    

    class myUnitTest(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_test</span><span class="hljs-params">(self)</span>:</span>
        <span class="hljs-string">""" 执行用例 """</span>
        self.assertEqual(case_set.add(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>), <span class="hljs-number">5</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">sub_test</span><span class="hljs-params">(self)</span>:</span>
        <span class="hljs-string">""" 执行用例"""</span>
        self.assertEqual(case_set.sub(<span class="hljs-number">10</span>, <span class="hljs-number">5</span>), <span class="hljs-number">5</span>)
    

    def create_suite():
    """ 创建用例集 """
    # 拿到两个用例对象
    add = myUnitTest('add_test')
    sub = myUnitTest('sub_test')
    # 实例化suite对象
    suite_obj = unittest.TestSuite()
    # 添加用例
    suite_obj.addTest(add)
    suite_obj.addTest(sub)
    return suite_obj

    if name == 'main':
    suite = create_suite()
    # 可以查看suite中的用例数量
    # print(suite.countTestCases()) # 2
    # 拿到执行器对象
    runner = unittest.TextTestRunner()
    # 你想用执行器执行谁?就把它传进去
    runner.run(suite)

    代码注释已经说得很明白了,只需要记住可以通过suite.countTestCases()方法获取suite中用例的数量。

    suite_obj.addTests(self, tests)

    一个一个往suite中添加用例比较麻烦,所以,再来个简单的:

    import unittest
    import case_set
    

    class myUnitTest(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_test</span><span class="hljs-params">(self)</span>:</span>
        <span class="hljs-string">""" 执行用例 """</span>
        self.assertEqual(case_set.add(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>), <span class="hljs-number">5</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">sub_test</span><span class="hljs-params">(self)</span>:</span>
        <span class="hljs-string">""" 执行用例"""</span>
        self.assertEqual(case_set.sub(<span class="hljs-number">10</span>, <span class="hljs-number">5</span>), <span class="hljs-number">5</span>)
    

    def create_suite():
    """ 创建用例集 """
    '''
    # 拿到两个用例对象
    add = myUnitTest('add_test')
    sub = myUnitTest('sub_test')
    # 实例化suite对象
    suite_obj = unittest.TestSuite()
    # 添加用例
    suite_obj.addTests([add, sub])
    '''

    # 上面的代码也可以这么写
    map_obj = map(myUnitTest, ['add_test', 'sub_test'])
    suite_obj = unittest.TestSuite()
    suite_obj.addTests(map_obj)
    return suite_obj

    if name == 'main':

    suite = create_suite()
    <span class="hljs-comment"># 可以查看suite中的用例数量</span>
    <span class="hljs-comment"># print(suite.countTestCases())  # 2</span>
    <span class="hljs-comment"># 拿到执行器对象</span>
    runner = unittest.TextTestRunner()
    <span class="hljs-comment"># 你想用执行器执行谁?就把它传进去</span>
    runner.run(suite)
    

    实例化时添加用例

    import unittest
    import case_set
    

    class myUnitTest(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_test</span><span class="hljs-params">(self)</span>:</span>
        <span class="hljs-string">""" 执行用例 """</span>
        self.assertEqual(case_set.add(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>), <span class="hljs-number">5</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">sub_test</span><span class="hljs-params">(self)</span>:</span>
        <span class="hljs-string">""" 执行用例"""</span>
        self.assertEqual(case_set.sub(<span class="hljs-number">10</span>, <span class="hljs-number">5</span>), <span class="hljs-number">5</span>)
    

    def create_suite():
    """ 创建用例集 """
    map_obj = map(myUnitTest, ['add_test', 'sub_test'])
    suite_obj = unittest.TestSuite(tests=map_obj)
    return suite_obj

    if name == 'main':
    suite = create_suite()
    runner = unittest.TextTestRunner()
    runner.run(suite)

    怎么玩的呢?其实我们在实例化时做了添加用例的操作,以下示例演示了实例化的过程:

    import unittest
    import case_set
    

    class myUnitTest(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_test</span><span class="hljs-params">(self)</span>:</span>
        <span class="hljs-string">""" 执行用例 """</span>
        self.assertEqual(case_set.add(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>), <span class="hljs-number">5</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">sub_test</span><span class="hljs-params">(self)</span>:</span>
        <span class="hljs-string">""" 执行用例"""</span>
        self.assertEqual(case_set.sub(<span class="hljs-number">10</span>, <span class="hljs-number">5</span>), <span class="hljs-number">5</span>)
    

    class myUnitTestSuite(unittest.TestSuite):
    def init(self):
    # 当实例化suite对象时,传递用例
    map_obj = map(myUnitTest, ['add_test', 'sub_test'])
    # 调用父类的 init 方法
    super().init(tests=map_obj)

    if name == 'main':
    suite_obj = myUnitTestSuite()
    runner = unittest.TextTestRunner()
    runner.run(suite_obj)

    虽然在一定程度上,我们优化了代码,但是还不够,因为,我们还需要手动的将用例添加到suite的中。接下来,我们来学习,如何自动添加。

    unittest.makeSuite
    回到顶部

    import unittest
    import case_set
    

    class myUnitTest(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_test</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(case_set.add(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>), <span class="hljs-number">5</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">sub_test</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(case_set.sub(<span class="hljs-number">10</span>, <span class="hljs-number">5</span>), <span class="hljs-number">2</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_mul</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(case_set.mul(<span class="hljs-number">10</span>, <span class="hljs-number">5</span>), <span class="hljs-number">50</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_div</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(case_set.div(<span class="hljs-number">10</span>, <span class="hljs-number">5</span>), <span class="hljs-number">2</span>)
    

    def create_suite():
    """ 创建用例集 """
    suite_obj = unittest.makeSuite(testCaseClass=myUnitTest, prefix='test')
    return suite_obj

    if name == 'main':
    suite_obj = create_suite()
    print(suite_obj.countTestCases()) # 2
    runner = unittest.TextTestRunner()
    runner.run(suite_obj)

    想要自动添加,需要使用unittest.makeSuite类来完成,在实例化unittest.makeSuite(testCaseClass, prefix='test')时,需要告诉makeSuite添加用例的类名,上例是myUnitTest,然后makeSuite将myUnitTest类中所有以prefix参数指定开头的用例,自动添加到suite中。

    再次强调,prefix参数默认读取以test开头的用例,也可以自己指定:

    import unittest
    import case_set
    

    class myUnitTest(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">my_add_test</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(case_set.add(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>), <span class="hljs-number">5</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">my_sub_test</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(case_set.sub(<span class="hljs-number">10</span>, <span class="hljs-number">5</span>), <span class="hljs-number">2</span>)  <span class="hljs-comment"># AssertionError: 5 != 2</span>
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_mul</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(case_set.mul(<span class="hljs-number">10</span>, <span class="hljs-number">5</span>), <span class="hljs-number">50</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_div</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(case_set.div(<span class="hljs-number">10</span>, <span class="hljs-number">5</span>), <span class="hljs-number">2</span>)
    

    def create_suite():
    """ 创建用例集 """
    suite_obj = unittest.makeSuite(myUnitTest, prefix='my')
    return suite_obj

    if name == 'main':
    suite_obj = create_suite()
    print(suite_obj.countTestCases()) # 2
    runner = unittest.TextTestRunner()
    runner.run(suite_obj)

    如上例示例,读取myUnitTest类中所有以my开头的用例方法。但建议还是按照人家默认的test就好了。

    除此之外,这都9102年了, 车车都是手自一体的,咱们除了能玩自动添加,也能手动的将指定的用例添加到suite中:

    import unittest
    import case_set
    

    class myUnitTest(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">my_add_test</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(case_set.add(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>), <span class="hljs-number">5</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">my_sub_test</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(case_set.sub(<span class="hljs-number">10</span>, <span class="hljs-number">5</span>), <span class="hljs-number">2</span>)  <span class="hljs-comment"># AssertionError: 5 != 2</span>
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_mul</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(case_set.mul(<span class="hljs-number">10</span>, <span class="hljs-number">5</span>), <span class="hljs-number">50</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_div</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(case_set.div(<span class="hljs-number">10</span>, <span class="hljs-number">5</span>), <span class="hljs-number">2</span>)
    

    def create_suite():
    """ 创建用例集 """
    suite_obj = unittest.makeSuite(myUnitTest, prefix='my')
    suite_obj.addTests(map(myUnitTest, ['test_mul', 'test_div']))
    return suite_obj

    if name == 'main':
    suite_obj = create_suite()
    print(suite_obj.countTestCases()) # 4
    runner = unittest.TextTestRunner()
    runner.run(suite_obj)

    上例,使用makeSuite自动添加所有以my开头的用例,然后又使用addTests添加两个用例。

    unittest.TestLoader
    回到顶部

    到目前为止,我们所有的用例方法都封装在一个用例类中,但是有的时候,我们会根据不同的功能编写不同的测试用例文件,甚至是存放在不同的目录内。

    这个时候在用addTest添加就非常的麻烦了。
    unittest提供了TestLoader类来解决这个问题。先看提供了哪些方法:

    • TestLoader.loadTestsFromTestCase,返回testCaseClass中包含的所有测试用例的suite。
    • TestLoader.loadTestsFromModule,返回包含在给定模块中的所有测试用例的suite。
    • TestLoader.loadTestsFromName,返回指定字符串的所有测试用例的suite。
    • TestLoader.loadTestsFromNames,返回指定序列中的所有测试用例suite。
    • TestLoader.discover,从指定的目录开始递归查找所有测试模块。

    执行脚本文件为myMain.py,目录结构,参见开头的目录结构示例。
    TestLoader.loadTestsFromTestCase

    首先,loadTestsFromTestCaseDemo.py代码如下:

    import unittest
    

    class LoadTestsFromTestCaseDemo(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_is_upper</span><span class="hljs-params">(self)</span>:</span>
        self.assertTrue(<span class="hljs-string">'FOO'</span>.isupper())
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_is_lower</span><span class="hljs-params">(self)</span>:</span>
        self.assertTrue(<span class="hljs-string">'foo'</span>.islower())
    

    LoadTestsFromTestCaseDemo类中有两个测试用例。

    import unittest
    from loadTestsFromTestCaseDemo.loadTestsFromTestCaseDemo import LoadTestsFromTestCaseDemo
    

    class MyTestCase(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_upper</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(<span class="hljs-string">'FOO'</span>, <span class="hljs-string">'foo'</span>.upper())
    

    if name == 'main':
    # 使用loadTestsFromTestCase获取当前脚本和loadTestsFromTestCaseDemo脚本中的用例类
    test_case1 = unittest.TestLoader().loadTestsFromTestCase(MyTestCase)
    test_case2 = unittest.TestLoader().loadTestsFromTestCase(LoadTestsFromTestCaseDemo)
    # 创建suite并添加用例类
    suite = unittest.TestSuite()
    suite.addTests([test_case1, test_case2])
    unittest.TextTestRunner(verbosity=2).run(suite)

    上例中,loadTestsFromTestCase需要传入用例类的类名。无所谓这个用例类所处的目录或者文件。

    TestLoader.loadTestsFromModule

    loadTestsFromTestCaseDemo.py代码稍微有些变动:

    import unittest
    

    class LoadTestsFromTestCaseDemo1(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_is_upper</span><span class="hljs-params">(self)</span>:</span>
        self.assertTrue(<span class="hljs-string">'FOO'</span>.isupper())
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_is_lower</span><span class="hljs-params">(self)</span>:</span>
        self.assertTrue(<span class="hljs-string">'foo'</span>.islower())
    

    class LoadTestsFromTestCaseDemo2(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_startswith</span><span class="hljs-params">(self)</span>:</span>
        self.assertTrue(<span class="hljs-string">'FOO'</span>.startswith(<span class="hljs-string">'F'</span>))
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_endswith</span><span class="hljs-params">(self)</span>:</span>
        self.assertTrue(<span class="hljs-string">'foo'</span>.endswith(<span class="hljs-string">'o'</span>))
    

    再来看maMain.py

    import unittest
    from loadTestsFromTestCaseDemo import loadTestsFromTestCaseDemo
    

    class MyTestCase(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_upper</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(<span class="hljs-string">'FOO'</span>, <span class="hljs-string">'foo'</span>.upper())
    

    if name == 'main':
    # 使用 loadTestsFromTestCase 获取当前脚本的用例类
    test_case1 = unittest.TestLoader().loadTestsFromTestCase(MyTestCase)
    # 使用 loadTestsFromModule 获取 loadTestsFromTestCaseDemo 脚本中的用例类
    test_case2 = unittest.TestLoader().loadTestsFromModule(loadTestsFromTestCaseDemo)
    # 创建suite并添加用例类
    suite = unittest.TestSuite()
    suite.addTests([test_case1, test_case2])
    unittest.TextTestRunner(verbosity=2).run(suite)

    上例中,loadTestsFromModule只要传入用例类所在的脚本名即可。

    TestLoader.loadTestsFromName && TestLoader.loadTestsFromNames
    loadTestsFromTestCaseDemo.py代码不变。
    再来看maMain.py

    import unittest
    from loadTestsFromTestCaseDemo import loadTestsFromTestCaseDemo
    

    class MyTestCase(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_upper</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(<span class="hljs-string">'FOO'</span>, <span class="hljs-string">'foo'</span>.upper())
    

    if name == 'main':
    # 使用 loadTestsFromName 获取当前脚本用例类的用例方法名称
    test_case1 = unittest.TestLoader().loadTestsFromName(name='MyTestCase.test_upper', module=import(name))
    # 使用 loadTestsFromNames 获取 loadTestsFromTestCaseDemo脚本中的LoadTestsFromTestCaseDemo1用例类的用例方法名
    test_case2 = unittest.TestLoader().loadTestsFromNames(
    names=['LoadTestsFromTestCaseDemo1.test_is_upper',
    'LoadTestsFromTestCaseDemo1.test_is_lower'
    ],
    module=loadTestsFromTestCaseDemo
    )
    # 创建suite并添加用例类
    suite = unittest.TestSuite()
    suite.addTests([test_case1, test_case2])
    unittest.TextTestRunner(verbosity=2).run(suite)

    切记,无论是loadTestsFromName还是loadTestsFromNames,name参数都必须传递的是用例类下的方法名字,并且,方法名必须是全名。module参数就是脚本名字。

    unittest.TestLoader().loadTestsFromNames(
    	name="ClassName.MethodName",   # 类名点方法名
    	module=ModuleName			   # 脚本名
    )
    

    TestLoader.discover
    首先,创建一些测试用例,注意,一定要知道各文件所在的位置。
    M: estsdiscover est_list.py代码如下:

    import unittest
    

    class TextCaseList(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_list_append</span><span class="hljs-params">(self)</span>:</span>
        l = []
        l.append(<span class="hljs-string">'a'</span>)
        self.assertEqual(l, [<span class="hljs-string">'a'</span>])   <span class="hljs-comment"># 判断 l 是否等于 ['a']</span>
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_list_remove</span><span class="hljs-params">(self)</span>:</span>
        l = [<span class="hljs-string">'a'</span>]
        l.remove(<span class="hljs-string">'a'</span>)
        self.assertEqual(l, [])
    

    创建了两个关于list的测试用例。
    来看M: estsdiscover est_str.py的代码示例:

    import unittest
    

    class TextCaseStr(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_str_index</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(<span class="hljs-string">'abc'</span>.index(<span class="hljs-string">'a'</span>), <span class="hljs-number">0</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_str_find</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(<span class="hljs-string">'abc'</span>.find(<span class="hljs-string">'a'</span>), <span class="hljs-number">0</span>)
    

    创建了两个关于str的测试用例。
    来看M: estsdiscoverson est_dict.py的代码示例:

    import unittest
    

    class TextCaseDict(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_dict_get</span><span class="hljs-params">(self)</span>:</span>
        d = {<span class="hljs-string">'a'</span>: <span class="hljs-number">1</span>}
        self.assertEqual(d.get(<span class="hljs-string">'a'</span>), <span class="hljs-number">1</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_dict_pop</span><span class="hljs-params">(self)</span>:</span>
        d = {<span class="hljs-string">'a'</span>: <span class="hljs-number">1</span>}
        self.assertEqual(d.pop(<span class="hljs-string">'a'</span>), <span class="hljs-number">1</span>)
    

    创建了两个关于dict的测试用例。
    来看M: ests est_tuple.py的代码示例:

    import unittest
    

    class TextCaseTuple(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_tuple_count</span><span class="hljs-params">(self)</span>:</span>
        t = (<span class="hljs-string">'a'</span>, <span class="hljs-string">'b'</span>)
        self.assertEqual(t.count(<span class="hljs-string">'a'</span>), <span class="hljs-number">1</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_tuple_index</span><span class="hljs-params">(self)</span>:</span>
        t = (<span class="hljs-string">'a'</span>, <span class="hljs-string">'b'</span>)
        self.assertEqual(t.index(<span class="hljs-string">'a'</span>), <span class="hljs-number">0</span>)
    

    这样,在不同的目录中,新建了8个测试用例。
    来研究一下discover怎么玩的。
    discover部分无比重要,需要注意的地方有很多。要打起精神哦!
    首先,来看discover的语法:

    discover = unittest.TestLoader().discover(
    	start_dir=base_dir,   # 该参必传
    	pattern='test*.py',   # 保持默认即可。
    	top_level_dir=None
    	)
    unittest.TextTestRunner(verbosity=2).run(discover)
    

    通过TestLoader()实例化对象,然后通过实例化对象调用discover方法,discover根据给定目录,递归找到子目录下的所有符合规则的测试模块,然后交给TestSuit生成用例集suite。完事交给TextTestRunner执行用例。
    该discover方法接收三个参数:

    • start_dir:要测试的模块名或者测试用例的目录。
    • pattern="test*.py":表示用例文件名的匹配原则,默认匹配以test开头的文件名,星号表示后续的多个字符。
    • top_level_dir=None:测试模块的顶层目录,如果没有顶层目录,默认为None。

    注意!!!意!!

    • discover对给定的目录是有要求的,它只识别Python的包,也就是目录内有__init__.py文件的才算是Python的包,只要是要读取的目录,都必须是包
    • 关于start_dir和top_level_dir的几种情况:
      • start_dir目录可以单独指定,这个时候,让top_level_dir保持默认(None)即可。
      • start_dir == top_level_dir, start_dir目录与top_level_dir目录一致,discover寻找start_dir指定目录内的符合规则的模块。
      • start_dir < top_level_dir,start_dir目录是top_level_dir目录的子目录。discover寻找start_dir指定目录内的符合规则的模块。
      • start_dir > top_level_dir,start_dir目录如果大于top_level_dir目录,等待你的是报错AssertionError: Path must be within the project。说你指定的路径(start_dir)必须位于项目内(top_level_dir)。

    这里再补充一点。
    我们知道,TestLoader类根据各种标准加载测试用例,并将它们返回给测试套件(suite)。但一般的,我们也可以不需要创建这个类实例(想要用某个类的方法,通常都是通过个该类的实例化对象调用)。unittest已经帮我们实例化好了TestLoader对象————defaultTestLoader,我们可以直接使用defaultTestLoader.discover。

    discover = unittest.defaultTestLoader.discover(
    	start_dir=base_dir, 
    	pattern='test*.py', 
    	top_level_dir=base_dir
    	)
    unittest.TextTestRunner(verbosity=2).run(discover)
    

    最后,仔细品味示例吧:

    import os
    import unittest
    

    class MyTestCase(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_upper</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(<span class="hljs-string">'FOO'</span>, <span class="hljs-string">'foo'</span>.upper())
    

    if name == 'main':
    base_dir = os.path.dirname(os.path.abspath(name)) # M: ests
    discover_dir = os.path.join(base_dir, 'discover') # M: estsdiscover
    son_dir = os.path.join(discover_dir, 'son') # M: estsdiscoverson
    print(base_dir, discover_dir, son_dir)
    '''
    # start_dir 和top_level_dir 的目录一致,获取该 start_dir 目录及子目录内的所有以 test 开头的 py 文件中的测试用例类
    discover = unittest.defaultTestLoader.discover(start_dir=base_dir, pattern='test.py', top_level_dir=base_dir)
    unittest.TextTestRunner(verbosity=2).run(discover) # 8个用例被执行
    '''

    # start_dir 是 top_level_dir 的子目录,获取该 start_dir 目录及子目录内的所有以 test 开头的 py 文件中的测试用例类
    discover = unittest.defaultTestLoader.discover(start_dir=discover_dir, pattern='test
    .py', top_level_dir=base_dir)
    unittest.TextTestRunner(verbosity=2).run(discover) # 6个用例被执行

    <span class="hljs-comment"># discover = unittest.TestLoader().discover(start_dir=base_dir)</span>
    <span class="hljs-comment"># unittest.TextTestRunner(verbosity=2).run(discover)</span>
    

    在参考示例时,心里默念注意事项。

    一探unittest.main
    回到顶部

    现在,makeSuite虽然很好用,但是依然不够,我们需要更加便捷和省事,一般情况下,我们更加倾向专注于编写测试用例,而后直接使用unittest执行即可,希望makeSuite这一步都能由unittest来完成,而不是我们自己来。
    是的,懒惰既是美德!Python或者unittest做到了:

    import unittest
    import case_set
    

    class myUnitTest(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_add</span><span class="hljs-params">(self)</span>:</span>
        <span class="hljs-string">""" 测试加法用例 """</span>
        print(self._testMethodName, self._testMethodDoc)   <span class="hljs-comment"># test_add 测试加法用例</span>
        self.assertEqual(case_set.add(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>), <span class="hljs-number">5</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_sub</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(case_set.sub(<span class="hljs-number">10</span>, <span class="hljs-number">5</span>), <span class="hljs-number">2</span>) <span class="hljs-comment"># AssertionError: 5 != 2</span>
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_mul</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(case_set.mul(<span class="hljs-number">10</span>, <span class="hljs-number">5</span>), <span class="hljs-number">50</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_div</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(case_set.div(<span class="hljs-number">10</span>, <span class="hljs-number">5</span>), <span class="hljs-number">2</span>)
    

    if name == 'main':
    unittest.main()

    正如上例,我们只需要在用例类中将用例方法以test开头,然后直接unittest.main()就可以直接测试了。
    我想通过前面的铺垫,这里也能大致的知道unittest.main()在内部做了什么了。我们将在最后来剖析它背后的故事。现在还有一些重要的事情等着我们。
    另外,你也可以通过self._testMethodName来查看用例名称;可以使用self._testMethodDoc来查看用例注释(如果你写了注释的话)。

    setUpClass && tearDownClass
    回到顶部

    在开始,我们学习了在测试某一个用例时,都会对应的执行三个方法:

    • setUp,开头一枪的那家伙,它负责该用例之前可能需要的一些准备,比如连接数据库。
    • runTest,执行用例逻辑,没的说,干活的长工。
    • tearDown,负责打扫战场,比如关闭数据库连接。

    示例:

    import unittest
    import case_set
    

    class myUnitTest(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_add</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(case_set.add(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>), <span class="hljs-number">5</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_sub</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(case_set.sub(<span class="hljs-number">10</span>, <span class="hljs-number">5</span>), <span class="hljs-number">5</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">setUp</span><span class="hljs-params">(self)</span>:</span>
        <span class="hljs-string">""" 如果myUnitTest中有我,我将在用例之前执行,无论我在myUnitTest的什么位置 """</span>
        print(<span class="hljs-string">'敌军还有三十秒到达战场, 碾碎他们....'</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">tearDown</span><span class="hljs-params">(self)</span>:</span>
        <span class="hljs-string">""" 如果myUnitTest中有我,我将在用例之后执行,无论我在myUnitTest的什么位置 """</span>
        print(<span class="hljs-string">'ace .....'</span>)
    

    if name == 'main':
    unittest.main()

    结果:

    敌军还有三十秒到达战场, 碾碎他们....
    True
    打完收工,阿sir出来洗地了.....
    .敌军还有三十秒到达战场, 碾碎他们....
    False
    打完收工,阿sir出来洗地了.....
    .
    ----------------------------------------------------------------------
    Ran 2 tests in 0.001s
    

    OK

    由结果可以看到,有两个用例被执行并通过,并且,每一个用例执行前后都触发了setUp和tearDown方法执行。
    但是,同志们,如果这是由1000甚至更多的用例组成的用例集,并且每一个用例都去操作数据,那么每个用例都会做连接/关闭数据库的操作。这就蛋疼了,就不能一次连接,所有用例都完事后,再关闭?这一下一下的......
    是的,可以解决这个问题:

    import unittest
    import case_set
    

    class myUnitTest(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_add</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(case_set.add(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>), <span class="hljs-number">5</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_sub</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(case_set.sub(<span class="hljs-number">10</span>, <span class="hljs-number">5</span>), <span class="hljs-number">5</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">setUp</span><span class="hljs-params">(self)</span>:</span>
        print(<span class="hljs-string">'敌军还有三十秒到达战场, 碾碎他们....'</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">tearDown</span><span class="hljs-params">(self)</span>:</span>
        print(<span class="hljs-string">'打完收工,阿sir出来洗地了.....'</span>)
    

    @classmethod
    def setUpClass(cls):
    print('在用例集开始执行,我去建立数据库连接......')

    @classmethod
    def tearDownClass(cls):
    print('全军撤退, 我收工.......')

    if name == 'main':
    unittest.main()

    结果:

    在用例集开始执行,我去建立数据库连接......
    敌军还有三十秒到达战场, 碾碎他们....
    True
    打完收工,阿sir出来洗地了.....
    .敌军还有三十秒到达战场, 碾碎他们....
    False
    打完收工,阿sir出来洗地了.....
    .全军撤退, 我收工.......
    

    Ran 2 tests in 0.002s

    OK

    由结果可以看到,setUpClasstearDownClass这两个类方法完美的解决我们的问题,这让我们在某些情况下可以更加灵活的组织逻辑。

    verbosity参数
    回到顶部

    verbosity
    上述的断言结果虽然很清晰,但是还不够!我们可以控制错误输出的详细程度。

    import unittest
    

    class TestStringMethods(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_assertFalse</span><span class="hljs-params">(self)</span>:</span>
        self.assertFalse(<span class="hljs-string">''</span>)
    

    if name == 'main':
    unittest.main(verbosity=1)

    在执行unittest.main(verbosity=1)时,可以通过verbosity参数来控制错误信息的详细程度。
    verbosity=0

    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    

    OK

    verbosity=1

    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    

    OK

    verbosity=2

    test_assertFalse (__main__.TestStringMethods) ... ok
    

    Ran 1 test in 0.000s

    OK

    由结果可以总结,verbosity有3种的错误信息状态提示报告:

    • 0,静默模式,对于测试结果给予简单提示。
    • 1,默认模式,与静默模式类似,只是在每个成功的用例前面有个.每个失败的用例前面有个F,跳过的用例有个S
    • 2,详细模式,测试结果会显示每个用例的所有相关的信息。

    切记,只有0、1、2三种状态。
    默认的是1。
    -v
    除此之外,我们在终端执行时也可以输出详细报告:

    M:tests>python36 myMain.py -v
    test_assertFalse (__main__.TestStringMethods) ... ok
    

    ----------------------------------------------------------------------
    Ran 1 test in 0.000s

    OK

    如上示例,使verbosity参数保持默认,我们通过在终端加-v来输入详细报告信息。

    除了-v,还可以有:

    M:	ests>python36 myMain.py -p    # 等效于verbosity=0
    

    什么都不加,就是verbosity=1

    跳过测试用例:skip
    回到顶部

    从Python3.1版本开始,unittest支持跳过单个测试方法甚至整个测试类。
    也就是说,某些情况下,我们需要跳过指定的用例。
    我们可以使用unittest提供的相关装饰器来完成:

    decorators description
    @unittest.skip(reason) 无条件地跳过装饰测试用例。 理由应该描述为什么跳过测试用例。
    @unittest.skipIf(condition, reason) 如果条件为真,则跳过修饰的测试用例。
    @unittest.skipUnless(condition, reason) 除非条件为真,否则跳过修饰的测试用例。
    @unittest.expectedFailure 将测试标记为预期的失败。如果测试失败,将被视为成功。如果测试用例通过,则认为是失败。
    expection unittest.SkipTest(reason) 引发此异常以跳过测试测试用例。

    示例:

    import unittest
    

    class TestCase01(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_assertTrue</span><span class="hljs-params">(self)</span>:</span>
        self.assertTrue(<span class="hljs-string">''</span>)
    

    @unittest.skip('no test') # 跳过该条用例
    def test_assertFalse(self):
    self.assertFalse('')

    @unittest.skip('no test') # 跳过这个用例类
    class TestCase02(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_assertTrue</span><span class="hljs-params">(self)</span>:</span>
        self.assertTrue(<span class="hljs-string">''</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_assertFalse</span><span class="hljs-params">(self)</span>:</span>
        self.assertFalse(<span class="hljs-string">''</span>)
    

    if name == 'main':
    unittest.main()

    看结果:

    M:	ests>python36 myMain.py
    sFss
    ======================================================================
    FAIL: test_assertTrue (__main__.TestCase01)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "demo0.py", line 27, in test_assertTrue
        self.assertTrue('')
    AssertionError: '' is not true
    

    ----------------------------------------------------------------------
    Ran 4 tests in 0.001s

    FAILED (failures=1, skipped=3)

    毋庸置疑,在结果中,总共4个用例,一个用例类被跳过,另一个用例类中跳过一个方法,那么就是执行4个用例,跳过3个

    再探unittest.main
    回到顶部

    在解释器的Libunittest框架内,主要目录和文件,故事将会在这里展开。

    **Libunittest
        ├─test		# 目录
        ├─case.py
        ├─loader.py
        ├─main.py
        ├─mock.py
        ├─result.py
        ├─runner.py
        ├─signals.py
        ├─suite.py
        ├─util.py
        ├─__init__.py
        └─__main__.py
    

    现在,我们在脚本中执行这样一段代码:

    import unittest
    

    class TestStringMethods(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_upper</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(<span class="hljs-string">'foo'</span>.upper(), <span class="hljs-string">'FOO'</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_isupper</span><span class="hljs-params">(self)</span>:</span>
        self.assertTrue(<span class="hljs-string">'FOO'</span>.isupper())
    

    if name == 'main':
    unittest.main()

    当我们在终端执行:

    M:tests>python36 myMain.py -v
    test_isupper (__main__.TestStringMethods) ... ok
    test_upper (__main__.TestStringMethods) ... ok
    

    ----------------------------------------------------------------------
    Ran 2 tests in 0.001s

    OK

    unittest源码是这样执行的.........

    main.py文件中。
    main = TestProgram

    class TestProgram(object):
        # defaults for testing
        module=None
        verbosity = 1
        failfast = catchbreak = buffer = progName = warnings = None
        _discovery_parser = None
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span><span class="hljs-params">(self, module=<span class="hljs-string">'__main__'</span>, defaultTest=None, argv=None,
                    testRunner=None, testLoader=loader.defaultTestLoader,
                    exit=True, verbosity=<span class="hljs-number">1</span>, failfast=None, catchbreak=None,
                    buffer=None, warnings=None, *, tb_locals=False)</span>:</span>
        print(argv)  <span class="hljs-comment"># ['myMain.py', '-v']</span>
        self.parseArgs(argv)   <span class="hljs-comment"># 检查参数 </span>
        self.runTests()   <span class="hljs-comment"># 执行测试用例集</span>
    

    main = TestProgram

    首先可以看到,main = TestProgram,所以,unittest.main()相当于unittest.TestProgram()。类加括号是实例化的过程,所以,我们将目光集中在__init__方法中,为实例化对象添加属性我们先略过,主要来看在这里都是执行了哪些方法。
    可以看到主要做了两件事,self.parseArgs(argv)检查终端是否有参数传入,是有参数-v的。完事执行self.runTests()
    先来研究检查参数的self.parseArgs方法做了什么?

    main.py: TestProgram.parseArgs

    class TestProgram(object):
        def parseArgs(self, argv):
            self.createTests()
    

    parseArgs经过一系列的操作,我们来到该方法的最后一行,self.createTests(),见名知意,这家伙是要创建用例集啊,看看具体怎么玩的。
    main.py: TestProgram.createTests

    class TestProgram(object):
        def createTests(self):
        	# self.testNames: None
        	# self.module: <module '__main__' from 'myMain.py'>
            if self.testNames is None:
                self.test = self.testLoader.loadTestsFromModule(self.module)
            else:
                self.test = self.testLoader.loadTestsFromNames(self.testNames, self.module)
    

    首先判断self.testNames是不是为None,这个参数是TestProgram.__init__(defaultTest=None)中的defaultTest参数,我们并没有传参,所以是None,那么就执行if条件。在if条件中执行了self.testLoader.loadTestsFromModule(self.module)方法,并传递了self.module参数,该参数其实就是我们运行的脚本文件名。
    我们看看这个self.testLoader.loadTestsFromModule方法做了什么。
    loader.py: TestLoader.loadTestsFromModule
    loadTestsFromModule方法位于unittest框架下的loader.pyTestLoader类中。

    class TestLoader(object):
        """ 根据各种标准生成测试用例集 """
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">loadTestsFromModule</span><span class="hljs-params">(self, module, *args, pattern=None, **kws)</span>:</span>
        <span class="hljs-string">"""返回给定模块中用例类(可能有多个用例类)中的用例 suite """</span>
    
        tests = []
        <span class="hljs-comment"># dir(module)获取 myMain.py中所有顶级属性,包括类名、函数名、变量名</span>
        <span class="hljs-comment"># 循环判断每一个属性并判断是否是case.TestCase的派生类</span>
        <span class="hljs-keyword">for</span> name <span class="hljs-keyword">in</span> dir(module):
            obj = getattr(module, name)
            <span class="hljs-comment"># 如果是case.TestCase的派生类,就添加到tests的列表中</span>
            <span class="hljs-comment"># 但在添加之前,要做类型检查判断</span>
            <span class="hljs-keyword">if</span> isinstance(obj, type) <span class="hljs-keyword">and</span> issubclass(obj, case.TestCase):
                tests.append(self.loadTestsFromTestCase(obj))
        <span class="hljs-comment"># module:myMain.py</span>
        <span class="hljs-comment"># module中没有 load_tests,所以 load_tests为None</span>
        load_tests = getattr(module, <span class="hljs-string">'load_tests'</span>, <span class="hljs-keyword">None</span>)
        tests = self.suiteClass(tests)
        <span class="hljs-comment"># 因为load_tests为None,所以if语句不会执行,</span>
        <span class="hljs-keyword">if</span> load_tests <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">None</span>:
            <span class="hljs-keyword">try</span>:
                <span class="hljs-keyword">return</span> load_tests(self, tests, pattern)
            <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
                error_case, error_message = _make_failed_load_tests(
                    module.__name__, e, self.suiteClass)
                self.errors.append(error_message)
                <span class="hljs-keyword">return</span> error_case
        <span class="hljs-comment"># tests: &lt;unittest.suite.TestSuite tests=[&lt;unittest.suite.TestSuite tests=[&lt;__main__.TestStringMethods testMethod=test_isupper&gt;, &lt;__main__.TestStringMethods testMethod=test_upper&gt;]&gt;]&gt;</span>
        <span class="hljs-keyword">return</span> tests  <span class="hljs-comment"># 用例集 suite</span>
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">loadTestsFromTestCase</span><span class="hljs-params">(self, testCaseClass)</span>:</span>
        <span class="hljs-string">"""返回testCaseClass中包含的所有测试用例的 suite"""</span>
        <span class="hljs-comment"># testCaseClass:是myMain.py中的用例类名 &lt;class '__main__.TestStringMethods'&gt;</span>
        <span class="hljs-keyword">if</span> issubclass(testCaseClass, suite.TestSuite):
            <span class="hljs-keyword">raise</span> TypeError(<span class="hljs-string">"Test cases should not be derived from "</span>
                            <span class="hljs-string">"TestSuite. Maybe you meant to derive from "</span>
                            <span class="hljs-string">"TestCase?"</span>)
        <span class="hljs-comment"># 获取testCaseClass中的所有以prefix指定的用例名</span>
        testCaseNames = self.getTestCaseNames(testCaseClass)
        <span class="hljs-comment"># print(testCaseClass, testCaseNames)  # &lt;class '__main__.TestStringMethods'&gt; ['test_isupper', 'test_upper']</span>
        <span class="hljs-comment"># 很明显,咱们的脚本中没有runTest</span>
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> testCaseNames <span class="hljs-keyword">and</span> hasattr(testCaseClass, <span class="hljs-string">'runTest'</span>):
            testCaseNames = [<span class="hljs-string">'runTest'</span>]
        <span class="hljs-comment"># 这就很明显了 self.suiteClass(map(testCaseClass, testCaseNames)) 在生成用例集的suite</span>
        loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames))
        <span class="hljs-comment"># loaded_suite:&lt;unittest.suite.TestSuite tests=[&lt;__main__.TestStringMethods testMethod=test_isupper&gt;, &lt;__main__.TestStringMethods testMethod=test_upper&gt;]&gt;</span>
        <span class="hljs-comment"># loaded_suite.countTestCases(): 2</span>
        <span class="hljs-keyword">return</span> loaded_suite  <span class="hljs-comment"># 返回用例集 suite</span>
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">getTestCaseNames</span><span class="hljs-params">(self, testCaseClass)</span>:</span>
        <span class="hljs-string">"""
            返回在testCaseClass中找到的方法名的排序序列
        """</span>
    
        <span class="hljs-comment"># self.testMethodPrefix:test</span>
        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">isTestMethod</span><span class="hljs-params">(attrname, testCaseClass=testCaseClass,
                         prefix=self.testMethodPrefix)</span>:</span>
            <span class="hljs-keyword">return</span> attrname.startswith(prefix) <span class="hljs-keyword">and</span> 
                   callable(getattr(testCaseClass, attrname))
    
        testFnNames = list(filter(isTestMethod, dir(testCaseClass)))
        <span class="hljs-keyword">if</span> self.sortTestMethodsUsing:
            testFnNames.sort(key=functools.cmp_to_key(self.sortTestMethodsUsing))
        <span class="hljs-keyword">return</span> testFnNames  <span class="hljs-comment"># ['test_isupper', 'test_upper']</span>
    

    loader.py中的TestLoader中一共做了三件事:

    • main.py: TestProgram.createTests方法触发了loader.py: TestLoader.loadTestsFromModule方法执行,在这个方法中,首先循环判断取出测试脚本中的所有的用例类。
    • 然后在循环判断中,如果判断测试脚本中的类是case.TestCase的派生类,就调用loader.py: TestLoader.loadTestsFromTestCase方法调用loader.py: TestLoader.getTestCaseNames并将用例类传递进去,该方法获取到传过来的用例类名,然后去这个用例类中去找所有prefix开头的用例,然后以列表的形式返回给loader.py: TestLoader.loadTestsFromTestCase方法。
    • loader.py: TestLoader.loadTestsFromTestCase方法拿到用例列表后,生成用例集suite并返回调用者。

    程序在loader.py执行完毕,回到main.py: TestProgram.createTests中。
    main.py: TestProgram.createTests成功完成任务,生成了用例集 suite。程序再次回到了调用main.py: TestProgram.createTests的方法中——
    main.py: TestProgram.parseArgs,然后main.py: TestProgram.parseArgs方法也执行完毕。程序继续回到调用处——main.py: TestProgram.__init__方法中。
    此时,创建用例集的suite完成。
    程序由此继续往下执行。
    main.py: TestProgram.runTests
    有了用例集就要执行了,往下看。

    class TestProgram(object):
        def runTests(self):
            # 实例化时没有传参,所以 self.catchbreak: None
            if self.catchbreak:
                installHandler()
            # self.testRunner同样没有传参,为None
            if self.testRunner is None:
                # runner.TextTestRunner是runner.py中的TextTestRunner对象
                self.testRunner = runner.TextTestRunner
    
        <span class="hljs-keyword">if</span> isinstance(self.testRunner, type):
            <span class="hljs-keyword">try</span>:
                <span class="hljs-keyword">try</span>:
                    testRunner = self.testRunner(verbosity=self.verbosity,
                                                 failfast=self.failfast,
                                                 buffer=self.buffer,
                                                 warnings=self.warnings,
                                                 tb_locals=self.tb_locals)
                <span class="hljs-keyword">except</span> TypeError:
                    <span class="hljs-comment"># didn't accept the tb_locals argument</span>
                    testRunner = self.testRunner(verbosity=self.verbosity,
                                                 failfast=self.failfast,
                                                 buffer=self.buffer,
                                                 warnings=self.warnings)
            <span class="hljs-keyword">except</span> TypeError:
                <span class="hljs-comment"># didn't accept the verbosity, buffer or failfast arguments</span>
                testRunner = self.testRunner()
        <span class="hljs-keyword">else</span>:
            <span class="hljs-comment"># it is assumed to be a TestRunner instance</span>
            testRunner = self.testRunner
            <span class="hljs-comment"># 实例化runner.py中的TextTestRunner类得到testRunner对象</span>
        <span class="hljs-comment"># testRunner.run(self.test)依次执行每一个用例</span>
        <span class="hljs-comment"># 将结果收集到self.result中</span>
        <span class="hljs-comment"># self.test:&lt;unittest.suite.TestSuite tests=[&lt;unittest.suite.TestSuite tests=[&lt;__main__.TestStringMethods testMethod=test_isupper&gt;, &lt;__main__.TestStringMethods testMethod=test_upper&gt;]&gt;]&gt;</span>
    
        self.result = testRunner.run(self.test)
        <span class="hljs-keyword">if</span> self.exit:
            <span class="hljs-comment"># self.result.wasSuccessful(): &lt;bound method TestResult.wasSuccessful of &lt;unittest.runner.TextTestResult run=2 errors</span>
    

    =0 failures=0>>

            sys.exit(<span class="hljs-keyword">not</span> self.result.wasSuccessful())
    

    执行用例没啥好说的,调用了runner.py: TextTestRunner.run方法依次执行每个用例并收集结果。

    runner.py: TextTestRunner.run

    class TextTestResult(result.TestResult):
        """ 一个测试结果类,它可以将格式化的文本结果打印到流中 """
    class TextTestRunner(object):
        """ 以文本形式显示结果的测试运行器 """
        def __init__(self, stream=None, descriptions=True, verbosity=1,
                     failfast=False, buffer=False, resultclass=None, warnings=None,
                     *, tb_locals=False):
            """ 构造一个TextTestRunner. """
            # sys.stderr:  <_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>
            # sys.stderr将结果输出到屏幕
            if stream is None:
                stream = sys.stderr
            self.stream = _WritelnDecorator(stream)  # _WritelnDecorator:文件类的装饰对象
            self.descriptions = descriptions
            self.verbosity = verbosity
            self.failfast = failfast
            self.buffer = buffer
            self.tb_locals = tb_locals
            self.warnings = warnings
            if resultclass is not None:
                # self.resultclass: TextTestResult
                self.resultclass = resultclass
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_makeResult</span><span class="hljs-params">(self)</span>:</span>
        <span class="hljs-comment"># print(self.stream, self.descriptions, self.verbosity)  # &lt;unittest.runner._WritelnDecorator object at 0x0373D690&gt; True 2</span>
        <span class="hljs-comment"># 返回 TextTestResult 实例化对象</span>
        <span class="hljs-keyword">return</span> self.resultclass(self.stream, self.descriptions, self.verbosity)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">run</span><span class="hljs-params">(self, test)</span>:</span>
        <span class="hljs-string">"运行给定的测试用例或测试套件"</span>
        <span class="hljs-comment"># result: TextTestResult 实例化对象</span>
        result = self._makeResult()
        registerResult(result)
        <span class="hljs-string">'''
        failfast是 TextTestRunner 的一个属性,缺省为False
        作用: 如果failfast为True,一旦测试集中有测试案例failed或发生error立即终止当前整个测试执行,跳过剩下所有测试案例,也就是实现“短路测试”
        '''</span>
        result.failfast = self.failfast  <span class="hljs-comment"># self.failfast: False</span>
        result.buffer = self.buffer
        result.tb_locals = self.tb_locals
        <span class="hljs-keyword">with</span> warnings.catch_warnings():
            <span class="hljs-keyword">if</span> self.warnings:
                <span class="hljs-comment"># if self.warnings is set, use it to filter all the warnings</span>
                warnings.simplefilter(self.warnings)
                <span class="hljs-comment"># if the filter is 'default' or 'always', special-case the</span>
                <span class="hljs-comment"># warnings from the deprecated unittest methods to show them</span>
                <span class="hljs-comment"># no more than once per module, because they can be fairly</span>
                <span class="hljs-comment"># noisy.  The -Wd and -Wa flags can be used to bypass this</span>
                <span class="hljs-comment"># only when self.warnings is None.</span>
                <span class="hljs-keyword">if</span> self.warnings <span class="hljs-keyword">in</span> [<span class="hljs-string">'default'</span>, <span class="hljs-string">'always'</span>]:
                    warnings.filterwarnings(<span class="hljs-string">'module'</span>,
                            category=DeprecationWarning,
                            message=<span class="hljs-string">r'Please use assertw+ instead.'</span>)
            startTime = time.time()
            <span class="hljs-comment"># result: TextTestResult</span>
            <span class="hljs-comment"># TextTestResult中并没有 startTestRun,但是父类的 TestResult 中有</span>
            startTestRun = getattr(result, <span class="hljs-string">'startTestRun'</span>, <span class="hljs-keyword">None</span>)
            <span class="hljs-keyword">if</span> startTestRun <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">None</span>:
                <span class="hljs-comment"># 执行TestResult的startTestRun</span>
                startTestRun()
            <span class="hljs-keyword">try</span>:
                <span class="hljs-comment"># BaseTestSuite执行了 __call__ 方法,test加括号等于执行了 BaseTestSuite 的 run 方法</span>
                test(result)
            <span class="hljs-keyword">finally</span>:
                <span class="hljs-comment"># 用例执行完毕,触发 TestResult 的 stopTestRun 方法</span>
                stopTestRun = getattr(result, <span class="hljs-string">'stopTestRun'</span>, <span class="hljs-keyword">None</span>)
                <span class="hljs-keyword">if</span> stopTestRun <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">None</span>:
                    stopTestRun()
            stopTime = time.time()
        timeTaken = stopTime - startTime
        result.printErrors()
        <span class="hljs-keyword">if</span> hasattr(result, <span class="hljs-string">'separator2'</span>):
            self.stream.writeln(result.separator2)
        run = result.testsRun
        self.stream.writeln(<span class="hljs-string">"Ran %d test%s in %.3fs"</span> %
                            (run, run != <span class="hljs-number">1</span> <span class="hljs-keyword">and</span> <span class="hljs-string">"s"</span> <span class="hljs-keyword">or</span> <span class="hljs-string">""</span>, timeTaken))
        self.stream.writeln()
    
        expectedFails = unexpectedSuccesses = skipped = <span class="hljs-number">0</span>
        <span class="hljs-keyword">try</span>:
            results = map(len, (result.expectedFailures,
                                result.unexpectedSuccesses,
                                result.skipped))
        <span class="hljs-keyword">except</span> AttributeError:
            <span class="hljs-keyword">pass</span>
        <span class="hljs-keyword">else</span>:
            expectedFails, unexpectedSuccesses, skipped = results
    
        infos = []
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> result.wasSuccessful():
            self.stream.write(<span class="hljs-string">"FAILED"</span>)
            failed, errored = len(result.failures), len(result.errors)
            <span class="hljs-keyword">if</span> failed:
                infos.append(<span class="hljs-string">"failures=%d"</span> % failed)
            <span class="hljs-keyword">if</span> errored:
                infos.append(<span class="hljs-string">"errors=%d"</span> % errored)
        <span class="hljs-keyword">else</span>:
            self.stream.write(<span class="hljs-string">"OK"</span>)
        <span class="hljs-keyword">if</span> skipped:
            infos.append(<span class="hljs-string">"skipped=%d"</span> % skipped)
        <span class="hljs-keyword">if</span> expectedFails:
            infos.append(<span class="hljs-string">"expected failures=%d"</span> % expectedFails)
        <span class="hljs-keyword">if</span> unexpectedSuccesses:
            infos.append(<span class="hljs-string">"unexpected successes=%d"</span> % unexpectedSuccesses)
        <span class="hljs-keyword">if</span> infos:
            self.stream.writeln(<span class="hljs-string">" (%s)"</span> % (<span class="hljs-string">", "</span>.join(infos),))
        <span class="hljs-keyword">else</span>:
            self.stream.write(<span class="hljs-string">"
    "</span>)
        <span class="hljs-keyword">return</span> result
    

    总结:

    • 收集用例。
    • 根据用例生成测试集。
    • 运行测试集。

    自定义删除用例方法
    回到顶部

    我们之前学习unittest.makeSuite时,学过两个添加用例的方法,但是我讲过删除用的方法了吗?并没有!现在,我们已经剖析了源码,知道了添加用例是addTestaddTests干的。
    suite.py: BaseTestSuite:

    class BaseTestSuite(object):
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">addTest</span><span class="hljs-params">(self, test)</span>:</span>
        <span class="hljs-comment"># sanity checks</span>
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> callable(test):
            <span class="hljs-keyword">raise</span> TypeError(<span class="hljs-string">"{} is not callable"</span>.format(repr(test)))
        <span class="hljs-keyword">if</span> isinstance(test, type) <span class="hljs-keyword">and</span> issubclass(test,
                                                 (case.TestCase, TestSuite)):
            <span class="hljs-keyword">raise</span> TypeError(<span class="hljs-string">"TestCases and TestSuites must be instantiated "</span>
                            <span class="hljs-string">"before passing them to addTest()"</span>)
        self._tests.append(test)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">addTests</span><span class="hljs-params">(self, tests)</span>:</span>
        <span class="hljs-keyword">if</span> isinstance(tests, str):
            <span class="hljs-keyword">raise</span> TypeError(<span class="hljs-string">"tests must be an iterable of tests, not a string"</span>)
        <span class="hljs-keyword">for</span> test <span class="hljs-keyword">in</span> tests:
            self.addTest(test)
    

    可以看到,addTest是一个一个添加,而addTests则是for循环调用addTest添加,本质上一样的。
    让我们将目光聚集到addTest中,可以看到使用的是self._test.append(test)。现在,我们的删除方法也有了——把添加方法复制一份,改几个字即可:

    class BaseTestSuite(object):
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">addTest</span><span class="hljs-params">(self, test)</span>:</span>
        <span class="hljs-comment"># sanity checks</span>
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> callable(test):
            <span class="hljs-keyword">raise</span> TypeError(<span class="hljs-string">"{} is not callable"</span>.format(repr(test)))
        <span class="hljs-keyword">if</span> isinstance(test, type) <span class="hljs-keyword">and</span> issubclass(test,
                                                 (case.TestCase, TestSuite)):
            <span class="hljs-keyword">raise</span> TypeError(<span class="hljs-string">"TestCases and TestSuites must be instantiated "</span>
                            <span class="hljs-string">"before passing them to addTest()"</span>)
        self._tests.append(test)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">removeTest</span><span class="hljs-params">(self, test)</span>:</span>
        <span class="hljs-comment"># sanity checks</span>
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> callable(test):
            <span class="hljs-keyword">raise</span> TypeError(<span class="hljs-string">"{} is not callable"</span>.format(repr(test)))
        <span class="hljs-keyword">if</span> isinstance(test, type) <span class="hljs-keyword">and</span> issubclass(test,
                                                 (case.TestCase, TestSuite)):
            <span class="hljs-keyword">raise</span> TypeError(<span class="hljs-string">"TestCases and TestSuites must be instantiated "</span>
                            <span class="hljs-string">"before passing them to addTest()"</span>)
        self._tests.remove(test)
    

    没错,你没看错,就是把addTest复制一份,方法名改为removeTest,完事把self._tests.append(test)改为self._tests.remove(test)就行了。

    调用也类似:

    import unittest
    

    class TestStringMethods(unittest.TestCase):

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_upper</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(<span class="hljs-string">'foo'</span>.upper(), <span class="hljs-string">'FOO'</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_isupper</span><span class="hljs-params">(self)</span>:</span>
        self.assertTrue(<span class="hljs-string">'FOO'</span>.isupper())
    

    if name == 'main':
    case = TestStringMethods('test_upper')
    suite = unittest.TestSuite()
    suite.addTest(case) # suite中有一个test_upper用例
    print(suite.countTestCases()) # 1
    suite.removeTest(case) # 删除掉它
    print(suite.countTestCases()) # 0

    将执行结果输出到文件
    回到顶部

    我们尝试着讲用例执行结果输出到文件中。

    import unittest
    class TestStringMethods(unittest.TestCase):
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_upper</span><span class="hljs-params">(self)</span>:</span>
        self.assertEqual(<span class="hljs-string">'foo'</span>.upper(), <span class="hljs-string">'FOO'</span>)
    
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_isupper</span><span class="hljs-params">(self)</span>:</span>
        self.assertTrue(<span class="hljs-string">'FOO'</span>.isupper())
    

    if name == 'main':
    f = open(r'M: ests 1.txt', 'w', encoding='utf-8')
    suite = unittest.makeSuite(TestStringMethods)
    unittest.TextTestRunner(stream=f).run(suite)

    生成用例报告
    回到顶部

    如上小节中,虽然能将结果输出到某个文件中,但更多的是根据模板生成报告,这里就来研究一下,如何生成模板报告。

    参见:https://www.cnblogs.com/Neeo/articles/7942613.html

    发送测试报告邮件
    回到顶部

    参见:https://www.cnblogs.com/Neeo/articles/11478853.html

    unittest.mock
    回到顶部

    参见:https://www.cnblogs.com/Neeo/articles/11511103.html

    小结:在unittest中,我们需要掌握的几个类:

    • unittest.TestCase:所有测试用例的基类,给定一个测试用例方法的名字,就会返回一个测试用例实例。
    • unittest.TestSuite:组织测试用例的用例集,支持测试用例的添加和删除。
    • unittest.TextTestRunner:测试用例的执行,其中Text是以文本的形式显示测试结果,测试结果会保存到TextTestResult中。
    • unittest.TextTestResult:保存测试用例信息,包括运行了多少个测试用例,成功了多少,失败了多少等信息。
    • unittest.TestLoader:加载TestCase到TESTSuite中。
    • unittest.defaultTestLoader:等于unittest.TestLoader()
    • unittest.TestProgram:TestProgram类名被赋值给了main变量,然后通过unittest.main()的形式调用。
    有志者,事竟成,破釜沉舟,百二秦关终属楚; 苦心人,天不负,卧薪尝胆,三千越甲可吞吴。 想到与得到中间还有两个字——做到。
  • 相关阅读:
    基于边缘计算网关的桥梁结构安全监测应用
    5G工业网关的边缘计算
    5G工业网关和5G工业路由器差异对比分析
    大型网站架构系列:消息队列(二)
    大型网站架构系列:分布式消息队列(一)
    [转]线程安全类的设计
    [转]runloop原理
    [转]深入理解RunLoop
    [转]iOS保持界面流畅的技巧和AsyncDisplay介绍
    [转]面试时如何优雅的谈论OC
  • 原文地址:https://www.cnblogs.com/huoxc/p/12846085.html
Copyright © 2020-2023  润新知