2018-08-07(16)单元测试(下)
Python基础语法(16)
单元测试
测试用例的核心工作原理 : test case(测试用例); test suite(测试套件), test runner(测试运行器); test fixture(测试装备)
-
TestCase:
就是一个完整的测试流程,包括测试前准备环境的搭建(setUp),执行测试代码(run),以及测试后环境的还原(tearDown)。单元测试的本质就在这里:一个测试用例是一个完整的测试单元,通过运行这个测试单元,可以对某个问题进行验证。
-
TestSuite
就是多个测试用例的集合,而且TestSuit之间可以互相嵌套。
- TestLoader用来加载TestCase到TestSuite中,其中有几个loadTestsFrom__()方法,就是从各个地方寻找TestCase,创建它们的实例,然后add到TestSuite中,再返回一个TestSuit实例。
-
TextTestRunner是用来执行测试用例的,其中的run(test)会执行TestSuit/TestCase中的run(result)方法。
测试结果会保存到TextTestResult实例中,包括运行了多少测试用例,成功了多少,失败了多少等信息。
-
使用fixture来堆测试用例环境进行搭建和摧毁。
代码实例
test_func.py
<pre spellcheck="false" class="md-fences md-end-block contain-cm modeLoaded" lang="python" contenteditable="false" cid="n30" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: normal; display: block; break-inside: avoid; text-align: left; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(223, 226, 229); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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</pre>
tese_func.py
<pre spellcheck="false" class="md-fences md-end-block contain-cm modeLoaded" lang="python" contenteditable="false" cid="n33" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: normal; display: block; break-inside: avoid; text-align: left; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(223, 226, 229); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import unittest # 导入unittest模块
from week4.unit1.test_func import *
class TestFunc(unittest.TestCase): # 测试用例
'''test test_func.py'''
每个测试方法均以test开头,否则是不被unittest识别的。
def test_add(self):
'''test method add(a,b)'''
self.assertEqual(3,add(1,2))
self.assertNotEqual(2,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(2.5, divide(5,2))
if name == "main":
unittest.main()
</pre>
代码中,TestFunc就是一个测试用例,测试用例每有一个test开头的方法,再load的时候便会生成一个TestCase实例。
之后我们通过TestLoader加载TestCase到TestSuite,用TextTestRunner来运行TestSuite。运行结果保存再TestTestResult中,我们通过命令行或者unittest.main()执行时,main会调用到TextTestRunner中的run来执行,
或者我们可以通过直接使用TextTestRunner来执行用例。
再Runner执行时,默认将执行结果说出到控制台,但是我们可以设置输出到其它文件,再文件中查看结果。(HTMLTestRunner,通过它可以将结果输出到HTML中,生成较为漂亮的报告)
TestSuite
通过该方法来控制用例的执行,添加到TestSuite中的case会按照添加的顺序执行。
还可以再Test Suite中添加多个测试文件,
代码实例:
<pre spellcheck="false" class="md-fences md-end-block contain-cm modeLoaded" lang="python" contenteditable="false" cid="n51" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: normal; display: block; break-inside: avoid; text-align: left; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(223, 226, 229); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import unittest
from test_func import TestFunc
if name == "main":
suite = unittest.TestSuite()
tests = [TestFunc("test_add), TestFunc("test_minus"), TestFunc("test_divide")]
suite.addTests(tests)
runner = unittest.TextTestRunner(verbosity = 2)
runner.run(suite)</pre>
通过下面的方法也可以传入测试用例
<pre spellcheck="false" class="md-fences md-end-block contain-cm modeLoaded" lang="python" contenteditable="false" cid="n54" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: normal; display: block; break-inside: avoid; text-align: left; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(223, 226, 229); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"># 添加单个TestCase
suite.addTest(TestCalculateFunc("test_multi")) # 注意,是addTest,不是addTests,留神s的区别
通过使用loadTestFromName() 括号内传入"模块名.TestCase名"
suite.addTests(unittest.TestLoader().loadTestsFromName("test_func.TestFunc")) # 添加单个
suite.addTests(unittest.TestLoader().loadTestsFromNames([test_func.TestFunc])) # 添加多,注意s和中括号的差别
通过使用loadTestsFromTestCase() 传入TestCase
suite.addTests(unittest.TestLoader().loadTestFromTestCase(TestFunc))
runner = unittes.TextTestRunner(verbosity = 2) # verbosity = 1 是打印的报告不如2的详细。
runner.run(suite)</pre>
注意: 用TestLoader无法对case进行排序
将测试结果写入文件
可以将测试结果写入文件,生成测试报告,对模块质量提供依据。
代码实例:
<pre spellcheck="false" class="md-fences md-end-block contain-cm modeLoaded" lang="python" contenteditable="false" cid="n65" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: normal; display: block; break-inside: avoid; text-align: left; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(223, 226, 229); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">if name == "main":
suit = unittest.TestSuite()
suite.addTests(unittest.TestLoader().loadTestFromTestCase(TestCalculateFunc))
with open("UnittestTestReport.txt","a") as f:
runner = unittest.TextTestRunner(stream = f, verbosity = 2) # 将测试报告写入文件,可以设置verbosity
runner.run(suite)</pre>
unittest中的参数化
首先需要安装nose_parameterized模块 pip instuall nose_parameterized
方法模块:
<pre spellcheck="false" class="md-fences md-end-block contain-cm modeLoaded" lang="python" contenteditable="false" cid="n73" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: normal; display: block; break-inside: avoid; text-align: left; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(223, 226, 229); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">def login(username,password):
if username == "Eric" and password == "123456":
return True
else:
return False</pre>
测试套件模块:
<pre spellcheck="false" class="md-fences md-end-block contain-cm modeLoaded" lang="python" contenteditable="false" cid="n76" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: normal; display: block; break-inside: avoid; text-align: left; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(223, 226, 229); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">from parameterized import parameterized
class TestCalculateFunc(unittest.TestCase):
'''test func.py'''
num = 1
@parameterized.expand([
["eric", "123456", True], # 可以是list,也可以是元组
["", "123456", True],
["eric", "12345", True],
["lingling", "123456"]
])</pre>
测试用例模块
<pre spellcheck="false" class="md-fences md-end-block contain-cm modeLoaded" lang="python" contenteditable="false" cid="n79" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: normal; display: block; break-inside: avoid; text-align: left; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(223, 226, 229); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">def test_login(self, username, password, flag):
这里的参数对应上述列表里的元素,运行时会遍历上述二维列表
''' 登录 '''
res = login(username,password)
self.assertEqual(res,flag)</pre>
setUp() , tearDown()
setUp() 和tearDown()两个方法(本质上是重写了TestCase的这两个方法),
这两个方法在每个测试方法执行前以及执行后执行一次,setUp用来为测试准备环境,tearDown用来清理环境,为之后的测试做准备。
代码实例:
<pre spellcheck="false" class="md-fences md-end-block contain-cm modeLoaded" lang="python" contenteditable="false" cid="n89" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: normal; display: block; break-inside: avoid; text-align: left; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(223, 226, 229); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">def setUp(self):
print("do something before test.Prepare environment")
def tearDown(self):
print("do something after test.Clean up")</pre>
这两个方法要写在测试用例里面。如果想要在每个case执行时只各自执行一次,为了达到这样的效果,我们可以把这两个方法设置为类方法。
setUpClass() 和 tearDownClass()再给它们加上类修饰符修饰。
代码实例:
<pre spellcheck="false" class="md-fences md-end-block contain-cm modeLoaded" lang="python" contenteditable="false" cid="n96" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: normal; display: block; break-inside: avoid; text-align: left; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(223, 226, 229); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">class TestCalculateFunc(unittest.TestCase):
@classmethod
def setUpClass(cls): # 注意,此时为setUpClass(), 不是setUp()
print("do something before test.Prepare environment")
@classmethod
def tearDownClass(cls):
print("do something after test.Clean up")
"""Test calculate_func.py"""</pre>
跳过case
-
skip装饰器
<pre spellcheck="false" class="md-fences md-end-block contain-cm modeLoaded" lang="python" contenteditable="false" cid="n104" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: normal; display: block; break-inside: avoid; text-align: left; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(223, 226, 229); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;">@unittest.skip("I don't want to run this case")
def test_divides(self):
···</pre>skip装饰器一共有三个:
-
unittest.skip(reason)
-
unittest.skiplf(condition,reason)
-
unittest.skipUnless(condition,reason)
skip无条件跳过
skipif是当condition为True时跳过(只有当条件满足时才跳过)
skipUnless是当condition为False时跳过(只有当条件满足时才不跳过)
-
HTMLTestRunner ——输出漂亮的报告
通过下载报告库即可使用
HTMLTestRunner是一个第三方的unittest HTML报告库,首先我们下载HTMLTestRunner.py,并放到当前目录下,或者你的’C:\Python27\Lib’下,就可以导入运行了。
下载地址:
[Python3.x版本
总结
-
unittest是python自带的单元测试框架,以后写单元测试时可以拿来用。
-
unittest的流程:
要被测试的方法==>测试用例==》测试套装==》执行
-
当class继承unittest.TestCase后就会变成一个TestCase,其中以test开头的方法再load时会被加载为一个真正的TestCase
-
verbosity可以控制测试报告输出的样式,1为一般报告,2为详细报告
-
可以通过addTest和addTests向suite中添加case或suite,可以使用TestLoader的loadTestForm()方法
-
可以使用setUp(),tearDown(), setUpClass(), tearDownClass()来进行用例执行前的环境布置和执行后的环境清理。
-
可以使用skip,skipIf, skipUnless装饰器跳过某个case。或者使用TestCase.skipTest()
-
再输出方法unittest.TextTestRunner(verbosity=2)中添加stream可以将报告输出到文件。TextTestRunner输出txt报告,HTMLTestRunner输出html报告。