unittest
unittest
unittest 是 Python 自带的类 Junit 单元测试框架。
像 Junit 之于 Java 一样,unittest 可用于单元测试,也可用于 Web 自动化测试甚至接口测试。unittest 支持测试用例/测试用例集的查找、组装,还可以在测试用例/测试用例集内共享数据,也支持根据条件筛选测试用例执行,以及自动化生成测试报告。
使用 unittest 可以快速搭建自动化测试框架进行测试。
unittest 核心组成
Test Fixture
Test Fixture 通常用来做测试用例的准备或者清理工作。比如测试开始前的数据准备或者测试结束后的数据清理等。Python 通过 setUp()、tearDown()、setUpClass()、tearDownClass() 这 4 个钩子函数(Hook)来实现测试的准备和清理工作。
Test Case
Test Case 是 unittest 的最小单元,一个 Test Case 就是一个测试用例,通常 Test Case 会继承 TestCase 这个基类。
Test Suite
Test Suite 是测试套件,就是我们常说的测试用例集,它可以包含一个或多个测试用例。
Test Loader
Test Loader 用来从提供的类(classes)和模块(modules)中生成测试用例集,默认情况下unittest 会提供一个 default test loader。
Test Runner
Test Runner 是测试执行器,用来进行测试用例的执行和测试结果的输出。
unittest 运行原理
知道了 unittest 的 5 大核心类,我们看下 unittest 的运行原理,如图所示:
Test Cases包括一个或者多个 TestCase 类,其中保存了具体的测试过程,你可以在测试类里使用 Test Fixture,例如setUp()、tearDown() 进行测试开始前的准备和结束后的清理工作。
TestSuite包括一个或者多个 TestSuite 类,其中 TestSuite 包括了一个或多个 TestCase,也可以包括其他 TestSuite。TestSuite 通过 addTest() 或者 addTests() 方法把一个个的测试用例或者测试用例集(TestSuite)组装起来成为一个新的测试用例集。
TestLoader类加载本地或从外部文件中定义好的 TestCase 或者 TestSuites。
TestRunner包括TextTestRunner类, 它提供了运行测试的标准平台。测试运行可以通过 unittest.main() 或者 python -m unittest xxx.py 来运行。
Test Results Collector包括 TestResults 类,它为测试结果提供了一个标准容器,它存储运行的测试用例状态,例如 errors、failures、skipped,测试的结果可以直接在 Console 输出,也可以为通过其他形式输出,例如 Text、result、output。
unittest的使用
unittest简单使用
1 | import unittest |
定义了一个测试类 TestSample,它继承自 unittest.TestCse 类,如果使用 unittest 框架,测试类必须要继承unittest.TestCse 类,且测试用例默认以 test 开头(实际上这个可以更改)。
测试用例有2个,分别为 test_equal 和 test_not_equal。注意测试用例在 unittest 里的表现形式是一个类方法。
执行结果:
TestFixture 的使用
如果想在测试用例或者测试用例集开始前,执行某些操作, 在测试用例或者测试用例集结束后再执行另外一些操作,那么应该使用 Test Fixture。
1 | import unittest |
TestFixture包括如下4个方法
- setUp()
- setUp()方法在每一个测试用例执行测试前都会执行。
- setUpClass()
- setUpClass()方法仅在整个测试类开始执行前执行.setUpClass()方法必须使用 @classmethod 来装饰。
- tearDown()
- tearDown()方法在每一个测试用例执行后都会执行。
- tearDownClass()
- tearDownClass()方法仅在整个测试类结束执行后执行.tearDownClass()方法必须使用 @classmethod 来装饰。
setUp() 和 setUpClass() 通常用来进行测试前的准备工作。例如,访问数据库获得测试用例需要的数据等。
tearDown() 和 tearDownClass() 通常用来进行测试后的清理工作。例如,测试结束后删除测试产生的数据,将被测试系统恢复至之前的状态等。
执行结果如下:
运行指定文件夹下的测试用例
在真实工作中,我们常常需要仅运行某一个测试类,或者某一个文件夹下的测试用例。此时,可以利用 unittest 的 main 函数来指定 module 运行。
unittest.main 的语法:
1 | unittest.main(module='__main__', defaultTest=None, argv=None, testRunner=None, testLoader=unittest.defaultTestLoader, exit=True, verbosity=1, failfast=None, catchbreak=None, buffer=None, warnings=None) |
其各个参数的含义如下:
- module:指定待运行的 module,默认是“main”;
- defaultTest:单个测试的名字或者多个测试名字的组合(必须要 iterable);
- argv:传递给程序的一组变量,如果没有指定,那么系统默认使用 sys.argv;
- testRunner:指定 unittest 的 test runner,可以是 test runner 类本身或者 test runner 类实例。默认情况下,main 函数会调用 sys.exit(),并且会在屏幕上显示测试运行错误或者成功的提示;
- testLoader:必须是 TestLoader 类实例,默认是defaultTestLoader;
- exit:默认是 True,即测试运行完调用 sys.exit(),在交互模式下使用时可指定为 False;
- verbosity:用于控制显示在 console 里的 log 等级,有 0、1、 2 三种,一般默认为等级 1,其中等级 2 显示的 log 最详细。
下面来看一个 discover 的例子, 假设我们的项目结构如下:
1 | python-test |
test_one.py的内容如下:
1 | import unittest |
test_two.py的内容如下:
1 | import unittest |
three.py内容如下:
1 | import unittest |
main.py的内容如下:
1 | import importlib.util |
执行结果如下:
可以看到,os.path.join(os.path.dirname(-file-), ‘tests’) 这个命令获取了 tests 这个文件夹的路径,然后我通过 get_module_name_string 这个方法,把 tests 文件夹下的所有 module 的string 获取出来(放到 mod_string_list 中去),接着我遍历每一个获取的 module string,把它导入并加入到 unittest 的 suites 中去,最后我指定了 runner 并且运行。
观察发现,只有test_one.py和test_two.py中的测试用例被执行了,而three.py中的测试用例没有被执行。
这是为什么呢?注意参数 TestLoader 默认情况下,Test Loader 仅仅会查找所有以“test”开头的 .py 文件,并且在运行测试用例时,仅会默认运行以“test”开头的测试方法,因为 three.py 是以“three”开头的并不是以“test”开头的,所以它没有执行。
动态查找测试用例运行
除去直接使用 unittest.main 方式加载 module 运行外,unittest 还支持通过 TestLoader 下的 discover 方法去查找测试用例。
语法如下:
1 | unittest.TestLoader.discover(start_dir, pattern='test*.py', top_level_dir=None) |
unittest 允许你从某个文件夹开始,递归查找所有符合筛选条件的测试用例,并且返回一个包含这些测试用例的 TestSuite 对象,unittest.TestLoader.discover 支持的参数如下:
- start_dir:起始文件夹的路径;
- pattern(匹配模式):默认搜索所有以“test”开头的测试文件,并把这些文件里的以“test”开头的测试用例挑选出来;
- top_level_dir(根目录):测试模块必须从根目录导入,如果 start_dir 的位置不是根目录,那么必须显式指定 top_level_dir。
依旧是以下目录:
1 | python-test |
其他文件内容不变,把 main.py 文件用 discover 的方式改写如下:
1 | import os |
执行结果如下:
运行后发现结果跟用 unittest.main 的方式一致。
按需组装测试用例
从上面的例子看到,所有以test开头的py文件且以test开头的方法都会被执行,那些又没的以test开头的我们如何执行呢?
在 unittest 中,testSuite 的组装,可以用上述的方式直接 discover,也可以用 unittest.TestSuite.addTest() 方式来添加测试用例到 TestSuite,指定测试用例执行。
还是以上例子:
1 | python-test |
其他文件内容不变,main.py更改成如下:
1 | import unittest |
执行结果如下:
在本次测试中,挑选了TestOne测试类中的testAssertNotEqual方法、TestTwo测试类中的equal_test方法和Three类中的testAssertEqual方法,将他们组装到一个TestSuite里运行。
通过 suit.addTest() 的方式,就可以按照需要实现把不同文件下的测试用例组装到同一个 suite 执行的操作。
破除默认 pattern,随心所欲命名测试文件
在以上的例子中,three.py文件下的测试用例都没有被执行,其原因就是 unittest 有默认的查找 pattern 如下:
- 查找测试文件,默认查找“test*.py”;
- 查找测试用例,默认查找“test*”。
我们可以通过更改查找 pattern 的方式来执行所有的测试用例,仍以上述项目为例:
1 | python-test |
其他文件不变,更改 main.py 为:
1 | import os |
执行结果如下:
我们把默认的 pattern 更改为”*.py“,这样任何在 tests 文件夹下的 py 文件都可以被查找到。可以看到 three.py 下运行了测试用例testAssertEqual,但是test_two下的“equal_test”这个方法没有运行,那是因为方法“testMethodPrefix”在起作用。
我们来更改下测试方法的默认查找方式, 更改 main.py 为如下:
1 | import os |
执行结果如下:
执行的测试用例只有equal_test,可以发现testMethodPrefix改变了查找测试用例的默认方式。
忽略测试用例执行
unittest 还支持忽略执行某些测试用例,只要在要忽略的测试用例上加上如下装饰器即可:
- @unittest.skip() 执行时直接忽略掉被装饰的测试用例;
- @unittest.skipIf() 如果 skipIf 里的条件成立,执行时直接忽略掉被装饰的测试用例;
- @unittest.skipUnless() 永久在执行时忽略被装饰的测试用例,除非 skipUnless 里的条件成立;
- @unittest.expectedFailure期望被装饰的测试用例是失败的,如果是失败的,则此条测试用例将被标记为测试通过。
下面来通过一组测试来显示如何忽略测试用例执行:
1 | import unittest |
执行结果:
unittest框架创建测试的步骤
- 编写一个测试类,这个测试类必须继承 TestCase 这个基类, 测试类所对应的 .py 文件默认要以 test 开头;
- 在这个测试类下面写你的测试方法,每个测试方法应该包括一个测试的完整步骤,测试方法要默认以 test 开头;
- 通过 unittest.main()、runner.run() 或者 python -m 的方式来调用这些测试用例。