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

  1. 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装饰器一共有三个:

    1. unittest.skip(reason)

    2. unittest.skiplf(condition,reason)

    3. 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报告。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,264评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,549评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,389评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,616评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,461评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,351评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,776评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,414评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,722评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,760评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,537评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,381评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,787评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,030评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,304评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,734评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,943评论 2 336

推荐阅读更多精彩内容