unittest中最核心的四个概念是:test case、test suite、 test runner、 test fixture。
一个TestCase的实例就是一个测试用例。什么是测试用例呢?就是一个完整的测试流程,包括测试前准备环境的搭建(setUp),执行测试代码(run),以及测试后环境的还原(tearDown)。元测试(unit test)的本质也就在这里,一个测试用例是一个完整的测试单元,通过运行这个测试单元,可以对某一个问题进行验证。
多个测试用例集合在一起,就是TestSuite,而且TestSuite也可以嵌套TestSuite。
TestLoader是用来加载TestCase到TestSuite中的,其中有几个loadTestsFrom__()方法,就是从各个地方寻找TestCase,创建它们的实例,然后add到TestSuite中,再返回一个TestSuite实例。
TextTestRunner是来执行测试用例的,其中的run(test)会执行TestSuite/TestCase中的run(result)方法。
测试的结果会保存到TextTestResult实例中,包括运行了多少测试用例,成功了多少,失败了多少等信息。对一个测试用例环境的搭建和销毁,是一个fixture。
一个class继承了unittest.TestCase,便是一个测试用例,但如果其中有多个以 test 开头的方法,那么每有一个这样的方法,在load的时候便会生成一个TestCase实例,如:一个class中有四个test_xxx方法,最后在load到suite中时也有四个测试用例。
到这里整个流程就清楚了:
写好TestCase,然后由TestLoader加载TestCase到TestSuite,然后由TextTestRunner来运行TestSuite,运行的结果保存在TextTestResult中,我们通过命令行或者unittest.main()执行时,main会调用TextTestRunner中的run来执行,或者我们可以直接通过TextTestRunner来执行用例。在Runner执行时,默认将执行结果输出到控制台,我们可以设置其输出到文件,在文件中查看结果。
unittest实例
先来准备一些待测方法:
mathfunc.py
def add(a, b):
return a+b
def minus(a, b):
return a-b
def multi(a, b):
return a*b
def divide(a, b):
return a/b
简单示例:
test_mathfunc.py
# -*- coding: utf-8 -*-
import unittest
from .mathfunc import *
class TestMathFunc(unittest.TestCase):
"""Test mathfuc.py"""
def test_1(self):
"""Test method add(a, b)"""
print(111)
assert 4 == add(1, 2)
def test_2(self):
print(222)
def test_3(self):
print(333)
def test_4(self):
print(444)
if __name__ == '__main__':
unittest.main(verbosity=2)
再新建一个文件,test_suite.py:
# -*- coding: utf-8 -*-
import unittest
from unit_test.test_mathfunc import TestMathFunc
if __name__ == '__main__':
suite = unittest.TestSuite()
tests = [TestMathFunc("test_1"), TestMathFunc("test_2"),]
suite.addTests(tests)
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
执行结果:
test_2 (unit_test.test_mathfunc.TestMathFunc) ... ok
test_1 (unit_test.test_mathfunc.TestMathFunc)
Test method add(a, b) ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
222
111
Process finished with exit code 0
可以看到,执行情况跟我们预料的一样:执行了2个case,并且顺序是按照我们添加进suite的顺序执行的。
上面用了TestSuite的 addTests()方法,并直接传入了TestCase列表,我们还可以:
# 直接用addTest方法添加单个TestCase
suite.addTest(TestMathFunc("test_multi"))
# 用addTests + TestLoader
# loadTestsFromName(),传入'模块名.TestCase名'
suite.addTests(unittest.TestLoader().loadTestsFromName('test_mathfunc.TestMathFunc'))
suite.addTests(unittest.TestLoader().loadTestsFromNames(['test_mathfunc.TestMathFunc'])) # loadTestsFromNames(),类似,传入列表
# loadTestsFromTestCase(),传入TestCase
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMathFunc))
注意,用TestLoader的方法是无法对case进行排序的,同时,suite中也可以套suite。
将结果输出到文件中
用例组织好了,但结果只能输出到控制台,这样没有办法查看之前的执行记录,我们想将结果输出到文件。很简单,看示例:
test_suite.py:
# -*- coding: utf-8 -*-
import unittest
from test_mathfunc import TestMathFunc
if __name__ == '__main__':
suite = unittest.TestSuite()
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMathFunc))
with open('UnittestTextReport.txt', 'a') as f:
runner = unittest.TextTestRunner(stream=f, verbosity=2)
runner.run(suite)
test fixture之setUp() tearDown()
上面整个测试基本跑了下来,但可能会遇到点特殊的情况:如果我的测试需要在每次执行之前准备环境,或者在每次执行完之后需要进行一些清理怎么办?比如执行前需要连接数据库,执行完成之后需要还原数据、断开连接。总不能每个测试方法中都添加准备环境、清理环境的代码吧。
这就要涉及到我们之前说过的test fixture了,修改test_mathfunc.py:
def setUp(self):
print "do something before test.Prepare environment."
def tearDown(self):
print "do something after test.Clean up."
我们添加了 setUp() 和 tearDown() 两个方法(其实是重写了TestCase的这两个方法),这两个方法在每个测试方法执行前以及执行后执行一次,setUp用来为测试准备环境,tearDown用来清理环境.
如果想要在所有case执行之前准备一次环境,并在所有case执行结束之后再清理环境,我们可以用 setUpClass() 与 tearDownClass():
class TestMathFunc(unittest.TestCase):
"""Test mathfuc.py"""
@classmethod
def setUpClass(cls):
print "This setUpClass() method only called once."
@classmethod
def tearDownClass(cls):
print "This tearDownClass() method only called once too."
跳过某个case
如果我们临时想要跳过某个case不执行怎么办?unittest也提供了几种方法:
-
skip装饰器
- unittest.skip(reason)、无条件跳过
- unittest.skipIf(condition, reason)、当condition为True时跳过
- unittest.skipUnless(condition, reason),当condition为False时跳过
@unittest.skip("I don't want to run this case.") def test_divide(self): """Test method divide(a, b)""" print "divide" self.assertEqual(2, divide(6, 3)) self.assertEqual(2.5, divide(5, 2))
-
TestCase.skipTest()方法
class TestMathFunc(unittest.TestCase): """Test mathfuc.py""" ... def test_divide(self): """Test method divide(a, b)""" self.skipTest('Do not run this.') print "divide" self.assertEqual(2, divide(6, 3)) self.assertEqual(2.5, divide(5, 2))