一、Unittest:
unittest是Python自带的单元测试框架,我们可以用其来作为我们自动化测试框架的用例组织执行框架
二、Unittest核心工作原理:
unittest中最核心的四个概念是:test case, test suite, test runner, test fixture
1. TestCase
一个TestCase的实例,就是一个测试用例。
测试用例:一个完整的测试流程,包括测试前的准备工作(setUp)、测试过程的代码、善后工作(tearDown)
单元测试的本质也就在这里,一个测试用例,也就是一个完整的测试单元
2. TestSuit
一个功能的验证往往需要多个测试用例,把多个测试用例集合在一起执行,就产生的测试套件的概念TestSuite。
TestSuite用来组装单个测试用例。
通过addTest()加载TestCase到TestSuite中,从而返回一个TestSuite实例
3. TestRunner
测试执行。一般单元测试框架中都会提供丰富的执行策略和执行结果。
在unittest单元测试框架中,通过TextTestRunner类提供的run()方法来执行测试套件/测试用例。
TestRunner可以使用图形界面、文本界面,或返回一个特殊的值等方式,来表示测试结果
4. TestFixture
对一个测试用例环境的搭建和销毁,就是一个fixture,通过覆盖TestCase的setUp()和tearDown()方法来实现。
比如,可以在setUp()中通过建立数据库连接来初始化,在tearDown()中清除数据库产生的数据,关闭连接等
流程:
一个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实例:
先准备一些待测的方法:
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
简单实例:
接下来我们为这些方法写一个测试
import unittest
from mathfunc import *
class TestMathFunc(unittest.TestCase):
"""Test mathfuc.py"""
def test_add(self):
"""Test method add(a, b)"""
self.assertEqual(3, add(1, 2))
self.assertNotEqual(3, add(2, 2))
def test_minus(self):
"""Test method minus(a, b)"""
self.assertEqual(1, minus(3, 2))
def test_multi(self):
"""Test method multi(a, b)"""
self.assertEqual(6, multi(2, 3))
def test_divide(self):
"""Test method divide(a, b)"""
self.assertEqual(2, divide(6, 3))
self.assertEqual(5, divide(6, 2))
if __name__ == '__main__':
unittest.main()
从上面也可以看出,测试的执行跟方法的顺序没有关系,test_divide写在了第4个,但是却是第2个执行的。
每个测试方法均以 test开头,否则是不被unittest识别的
四:组织TestSuite:
上面的代码示例了如何编写一个简单的测试,但有两个问题,问题一:我们怎么控制用例执行的顺序呢?(这里的示例中的几个测试方法并没有一定关系,但之后你写的用例可能会有先后关系,需要先执行方法A,再执行方法B),我们就要用到TestSuite了。我们添加到TestSuite中的case是会按照添加的顺序执行的
问题二:是我们现在只有一个测试文件,我们直接执行该文件即可,但如果有多个测试文件,怎么进行组织,总不能一个个文件执行吧
上栗子:
在文件夹中,新建一个test_suite.py
import unittest
from UTest.test_mathfunc import *
if __name__ == '__main__':
suite = unittest.TestSuite()
tests = [TestMathFunc("test_add"),TestMathFunc("test_minus"),TestMathFunc("test_divide")]
suite.addTests(tests)
runner = unittest.TextTestRunner()
runner.run(suite)
测试结果:
能够看到一共运行了3个测试,失败了1个,并且给出了失败原因,5 != 3 也就是说我们的divide方法是有问题的
说明:
1、在第一行给出了每一个用例执行的结果的标识,成功是 .,失败是 F,出错是 E,跳过是 S。
2、在unittest.TestTestRunner()中加 verbosity 参数可以控制输出的错误报告的详细程度,默认是 1,如果设为 0,则不输出每一用例的执行结果,即没有上面的结果中的第1行;如果设为 2,则输出详细的执行结果,如下:
可以看到,执行情况跟我们预料的一样:执行了三个case,并且顺序是按照我们添加进suite的顺序执行的。
上面用了TestSuite的 addTests() 方法,并直接传入了TestCase列表,我们还可以:
import unittest
from UTest.test_mathfunc import *
from UTest.mathfunc import *
if __name__ == '__main__':
suite = unittest.TestSuite()
# 添加多个Testcase至addTests
# tests = [TestMathFunc("test_add"),TestMathFunc("test_minus"),TestMathFunc("test_divide")]
# suite.addTests(tests)
# 直接用addTest添加单个测试用力
# suite.addTest(TestMathFunc('test_add'))
# 用addTests + TestLoader
# loadTestsFromName(),传入'模块名.TestCase名'
#suite.addTests(unittest.TestLoader().loadTestsFromName('test_mathfunc.TestMathFunc'))
# loadTestsFromNames(),类似,传入列表
#suite.addTests(unittest.TestLoader().loadTestsFromNames(["test_mathfunc.TestMathfunc"]))
# loadTestsFromTestCase()传入的是类
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMathFunc))
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
将结果输出到文件中
用例组织好了,但结果只能输出到控制台,这样没有办法查看之前的执行记录,我们想将结果输出到文件
修改test_suite.py:
mport unittest
from UTest.test_mathfunc import *
if __name__ == '__main__':
suite = unittest.TestSuite()
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMathFunc))
with open('UnittestTextReport.txt','a') as f:
runner = unittest.TextTestRunner(f,verbosity=2)
runner.run(suite)
执行此文件,可以看到,在同目录下生成了UnittestTextReport.txt,所有的执行报告均输出到了此文件中,这下我们便有了txt格式的测试报告了。
五:test fixture之setUp() tearDown()
如果我的测试需要在每次执行之前准备环境,或者在每次执行完之后需要进行一些清理怎么办?比如执行前需要连接数据库,执行完成之后需要还原数据、断开连接。总不能每个测试方法中都添加准备环境、清理环境的代码吧。
这就要涉及到我们之前说过的test fixture了,修改test_mathfunc.py:
mport unittest
from UTest.mathfunc import *
class TestMathFunc(unittest.TestCase):
'''
test_mathfunc.py
'''
def setUp(self):
print("setUp")
def tearDown(self):
print("tearDown")
def test_add(self):
print("test_add")
self.assertEqual(5,add(4,1))
self.assertNotEqual(4,add(3,2))
def test_minus(self):
print("test_minus")
self.assertEqual(6,minus(8,2))
def test_multi(self):
print("test_nulti")
self.assertEqual(6,multi(3,2))
self.assertEqual(5,multi(2.5,2))
def test_divide(self):
print("test_divide")
self.assertEqual(2,divide(6,3))
self.assertEqual(5,divide(6,2))
if __name__ == '__main__':
unittest.main()
我们添加了 setUp() 和 tearDown() 两个方法(其实是重写了TestCase的这两个方法),这两个方法在每个测试方法执行前以及执行后执行一次,setUp用来为测试准备环境,tearDown用来清理环境,已备之后的测试
测试结果:
可以看到setUp和tearDown在每次执行case前后都执行了一次
如果想要在所有case执行之前准备一次环境,并在所有case执行结束之后再清理环境,我们可以用 setUpClass() 与 tearDownClass():
class TestMathFunc(unittest.TestCase):
'''
test_mathfunc.py
'''
@classmethod
def setUpClass(cls) -> None:
print("setUpClass")
@classmethod
def tearDownClass(cls) -> None:
print("tearDown")
测试结果:
可以看到setUpClass以及tearDownClass均只执行了一次
跳过某个Case:
如果我们临时想要跳过某个case不执行怎么办?unittest也提供了几种方法:
1、skip装饰器:
@unittest.skip("俺不想执行这个用例")
def test_divide(self):
print("test_divide")
self.assertEqual(2,divide(6,3))
self.assertEqual(5,divide(6,2))
测试结果:
skip装饰器一共有三个 unittest.skip(reason)、unittest.skipIf(condition, reason)、unittest.skipUnless(condition, reason),skip无条件跳过,skipIf当condition为True时跳过,skipUnless当condition为False时跳过。
2、TestCase.skipTest()方法
def test_divide(self):
self.skipTest("俺不想执行这个用例")
print("test_divide")
self.assertEqual(2,divide(6,3))
self.assertEqual(5,divide(6,2))
测试结果:
总结:
1、unittest是Python自带的单元测试框架,我们可以用其来作为我们自动化测试框架的用例组织执行框架
2、unittest的流程:写好TestCase,然后由TestLoader加载TestCase到TestSuite,然后由TextTestRunner来运行TestSuite,运行的结果保存在TextTestResult中,我们通过命令行或者unittest.main()执行时,main会调用TextTestRunner中的run来执行,或者我们可以直接通过TextTestRunner来执行用例。
3、一个class继承unittest.TestCase即是一个TestCase,其中以 test 开头的方法在load时被加载为一个真正的TestCase。
4、verbosity参数可以控制执行结果的输出,0 是简单报告、1 是一般报告、2 是详细报告
5、可以通过addTest和addTests向suite中添加case或suite,可以用TestLoader的loadTestsFrom__()方法。
6、用 setUp()、tearDown()、setUpClass()以及 tearDownClass()可以在用例执行前布置环境,以及在用例执行后清理环境
7、我们可以通过skip,skipIf,skipUnless装饰器跳过某个case,或者用TestCase.skipTest方法