【unittest单元测试框架】(2)关于 unittest 还需要知道的


关于 unittest 还需要知道的

1、测试用例的执行顺序

  测试用例的执行顺序涉及多个层级:多个测试目录 > 多个测试文件 > 多个测试类 >多个测试方法(测试用例)。unittest 提供的 main()方法和 discover()方法是按照什么顺序查找测试用例的呢?   我们先运行一个例子,再解释 unittest 的执行策略。
# -*- coding:utf-8 -*-
# filename: test_unittest_order.py
# author: hello.yin
# create time: 2021/11/16 15:25

import unittest


class Test_B(unittest.TestCase):

    def setUp(self):
        print("class B")

    def test_c(self):
        print("ccc")

    def test_a(self):
        print("aaa")


class Test_A(unittest.TestCase):

    def setUp(self):
        print("class A")

    def test_b(self):
        print("bbb")


if __name__ == "__main__":
    unittest.main(verbosity=2)

 执行结果:

Testing started at 15:44 ...
C:\Users\yzp\PycharmProjects\base_practice\Scripts\python.exe "C:\Program Files\JetBrains\PyCharm 2018.2\helpers\pycharm\_jb_unittest_runner.py" --path D:/00test/base_practice/practice/test_unittest_order.py

Launching unittests with arguments python -m unittest D:/00test/base_practice/practice/test_unittest_order.py in D:\00test\base_practice\practice


Ran 3 tests in 0.001s

OK
class A
bbb
class B
aaa
class B
ccc

Process finished with exit code 0
  无论执行多少次,结果都是一样的。通过上面的结果,相信你已经找到 main()方法执行测试用例的规律了。   因为unittest默认根据ASCII码的顺序加载测试用例的(数字与字母的顺序为0~9,A~Z,a~z),所以 TestAdd 类会优先于 TestBdd 类被执行,test_aaa()方法会优先于 test_ccc()方法被执行,也就是说,它并不是按照测试用例的创建顺序从上到下执行的。   discover()方法和 main()方法的执行顺序是一样的。对于测试目录与测试文件来说,上面的规律同样适用。test_aaa.py 文件会优先于 test_bbb.py 文件被执行。所以,如果想让某个测试文件先执行,可以在命名上加以控制。   除命名外,有没有其他办法控制测试用例的执行顺序呢?答案是肯定的,前面也有介绍,我们可以声明测试套件 TestSuite 类,通过 addTest()方法按照一定的顺序来加载测试用例。

   修改上面的例子如下。

# -*- coding:utf-8 -*-
# filename: test_unittest_order.py
# author: hello.yin
# create time: 2021/11/16 15:25

import unittest


class Test_B(unittest.TestCase):

    def setUp(self):
        print("class B")

    def test_c(self):
        print("ccc")

    def test_a(self):
        print("aaa")


class Test_A(unittest.TestCase):

    def setUp(self):
        print("class A")

    def test_b(self):
        print("bbb")


if __name__ == "__main__":

    suit = unittest.TestSuite()
    suit.addTest(Test_A("test_b"))
    suit.addTest(Test_B("test_a"))
    suit.addTest(Test_B("test_c"))
    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(suit) 

执行结果:

Testing started at 15:58 ...
C:\Users\yzp\PycharmProjects\base_practice\Scripts\python.exe "C:\Program Files\JetBrains\PyCharm 2018.2\helpers\pycharm\_jb_unittest_runner.py" --path D:/00test/base_practice/practice/test_unittest_order.py
Launching unittests with arguments python -m unittest D:/00test/base_practice/practice/test_unittest_order.py in D:\00test\base_practice\practice



Ran 3 tests in 0.002s

OK
class A
bbb
class B
aaa
class B
ccc

Process finished with exit code 0
  现在的执行顺序与 addTest()方法加载测试用例的顺序相同。不过,当测试用例非常多时,不推荐用这种方法创建测试套件,原因前面也有说明,最好的方法是通过命名控制执行顺序。如果测试用例在设计时不产生相互依赖,那么测试用例的执行顺序就没那么重要了。

 2、执行多级目录的测试用例

当测试用例的数量达到一定量级时,就要考虑目录划分,比如规划如下测试目录。 test_project ├──/test_case/ │ ├── test_bbb/ │ │ ├── test_ccc/ │ │ │ └── test_c.py │ │ └── test_b.py │ ├── test_ddd/ │ │ └── test_d.py │ └── test_a.py └─ run_tests.py      对于上面的目录结构,如果将 discover()方法中的 start_dir 参数定义为“./test_case”目录,那么只能加载 test_a.py 文件中的测试用例。如何让 unittest 查找 test_case/下子目录中 的测试文件呢?方法很简单,就是在每个子目录下放一个__init__.py 文件。__init__.py 文件的作用是将一个目录标记成一个标准的 Python 模块。

3、跳过测试和预期失败

  在运行测试时,有时需要直接跳过某些测试用例,或者当测试用例符合某个条件时跳过测试,又或者直接将测试用例设置为失败。unittest 提供了实现这些需求的装饰器。
unittest.skip(reason)
  无条件地跳过装饰的测试,需要说明跳过测试的原因。
unittest.skipIf(condition, reason)

   如果条件为真,则跳过装饰的测试。

unittest.skipUnless(condition, reason)
  当条件为真时,执行装饰的测试。
unittest.expectedFailure()

   不管执行结果是否失败,都将测试标记为失败。

# -*- coding:utf-8 -*-
# filename:test_skip.py
# author:hello.yin
# create time:2021/11/16 16:15

import unittest


class SkipTest(unittest.TestCase):

    @unittest.skip("直接跳过测试!")
    def test_a_skip(self):
        print("测试unittest.skip装饰器")

    @unittest.skipUnless(3 > 2, "当3大于2时执行测试!")
    def test_b_skipUnless(self):
        print("测试unittest.skipUnless装饰器")

    @unittest.skipIf(3 > 2, "当3大于2时跳过测试!")
    def test_c_skipIf(self):
        print("测试unittest.skipIf装饰器")

    @unittest.expectedFailure
    def test_d_expectedFailure(self):
        self.assertEqual(2, 2)


if __name__ == "__main__":
    unittest.main(verbosity=2)

执行结果:

Testing started at 16:25 ...
C:\Users\yzp\PycharmProjects\base_practice\Scripts\python.exe "C:\Program Files\JetBrains\PyCharm 2018.2\helpers\pycharm\_jb_unittest_runner.py" --path D:/00test/base_practice/practice/test_skip.py


Ran 4 tests in 0.002s


Launching unittests with arguments python -m unittest D:/00test/base_practice/practice/test_skip.py in D:\00test\base_practice\practiceFAILED (skipped=2, unexpected successes=1)

Skipped: 直接跳过测试!
测试unittest.skipUnless装饰器

Skipped: 当3大于2时跳过测试!

Failure
Test should not succeed since it's marked with @unittest.expectedFailure

Process finished with exit code 1
上面的例子创建了四条测试用例。 第一条测试用例通过@unittest.skip()装饰,直接跳过测试。 第二条测试用例通过@unittest.skipUnless()装饰,当条件为真时执行测试;3>2 条件为真(True),执行测试。 第三条测试用例通过@unittest.skipIf()装饰,当条件为真时跳过测试;3>2 条件为真(True),所以跳过测试。 第四条测试用例通过@unittest.expectedFailure 装饰,不管执行结果是否失败,都将测 试标记为失败,但不会抛出失败信息。 当然,这些方法同样适用于测试类,只需将它们针对测试类装饰即可
import unittest


@unittest.skip("直接跳过,不测试该测试类")
class MyTest(unittest.TestCase):
# ……

4、Fixture

我们可以把 Fixture 看作夹心饼干外层的两片饼干,这两片饼干就是 setUp/tearDown,中间的奶油就是测试用例。除此之外,unittest 还提供了更大范围的 Fixture,如测试类和模块的 Fixture。  setUpModule/tearDownModule:在整个模块的开始与结束时被执行。 setUpClass/tearDownClass:在测试类的开始与结束时被执行。 setUp/tearDown:在测试用例的开始与结束时被执行。 需要注意的是,setUpClass/tearDownClass 为类方法,需要通过@classmethod 进行装饰。另外,方法的参数为 cls。其实,cls 与 self 并没有什么本质区别,都只表示方法的第一个参数。
# -*- coding:utf-8 -*-
# filename: test_fixtrue.py
# author: hello.yin
# create time: 2021/11/16 16:37

import unittest


def setUpModule():
    print("======setUpModule======")


def tearDownModule():
    print("=======tearDownModule========")


class TestFixtrue(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        print("======setUpClass======")

    @classmethod
    def tearDownClass(cls):
        print("======tearDownClass===========")

    def setUp(self):
        print("====setUp========")

    def tearDown(self):
        print("======tearDown=======")

    def test_case1(self):
        print("testcase1")

    def test_case2(self):
        print("testcase2")


if __name__ == "__main__":
    unittest.main(verbosity=2)

 执行结果:

Testing started at 16:47 ...
C:\Users\yzp\PycharmProjects\base_practice\Scripts\python.exe "C:\Program Files\JetBrains\PyCharm 2018.2\helpers\pycharm\_jb_unittest_runner.py" --path D:/00test/base_practice/practice/test_fixtrue.py

Launching unittests with arguments python -m unittest D:/00test/base_practice/practice/test_fixtrue.py in D:\00test\base_practice\practice


Ran 2 tests in 0.002s

OK
======setUpModule======
======setUpClass======
====setUp========
testcase1
======tearDown=======
====setUp========
testcase2
======tearDown=======
======tearDownClass===========
=======tearDownModule========
Process finished with exit code 0