unittest中最核心的四個(gè)概念是:test case挽牢、test suite揣非、 test runner叛氨、 test fixture堕仔。
一個(gè)TestCase的實(shí)例就是一個(gè)測(cè)試用例。什么是測(cè)試用例呢晌区?就是一個(gè)完整的測(cè)試流程眷篇,包括測(cè)試前準(zhǔn)備環(huán)境的搭建(setUp)铃辖,執(zhí)行測(cè)試代碼(run),以及測(cè)試后環(huán)境的還原(tearDown)。元測(cè)試(unit test)的本質(zhì)也就在這里吗购,一個(gè)測(cè)試用例是一個(gè)完整的測(cè)試單元,通過(guò)運(yùn)行這個(gè)測(cè)試單元俘种,可以對(duì)某一個(gè)問(wèn)題進(jìn)行驗(yàn)證使鹅。
多個(gè)測(cè)試用例集合在一起,就是TestSuite遣总,而且TestSuite也可以嵌套TestSuite睬罗。
TestLoader是用來(lái)加載TestCase到TestSuite中的,其中有幾個(gè)loadTestsFrom__()方法旭斥,就是從各個(gè)地方尋找TestCase容达,創(chuàng)建它們的實(shí)例,然后add到TestSuite中垂券,再返回一個(gè)TestSuite實(shí)例花盐。
TextTestRunner是來(lái)執(zhí)行測(cè)試用例的,其中的run(test)會(huì)執(zhí)行TestSuite/TestCase中的run(result)方法菇爪。
測(cè)試的結(jié)果會(huì)保存到TextTestResult實(shí)例中算芯,包括運(yùn)行了多少測(cè)試用例,成功了多少凳宙,失敗了多少等信息熙揍。對(duì)一個(gè)測(cè)試用例環(huán)境的搭建和銷(xiāo)毀,是一個(gè)fixture氏涩。
一個(gè)class繼承了unittest.TestCase诈嘿,便是一個(gè)測(cè)試用例堪旧,但如果其中有多個(gè)以 test 開(kāi)頭的方法,那么每有一個(gè)這樣的方法奖亚,在load的時(shí)候便會(huì)生成一個(gè)TestCase實(shí)例淳梦,如:一個(gè)class中有四個(gè)test_xxx方法,最后在load到suite中時(shí)也有四個(gè)測(cè)試用例昔字。
到這里整個(gè)流程就清楚了:
寫(xiě)好TestCase爆袍,然后由TestLoader加載TestCase到TestSuite,然后由TextTestRunner來(lái)運(yùn)行TestSuite作郭,運(yùn)行的結(jié)果保存在TextTestResult中陨囊,我們通過(guò)命令行或者unittest.main()執(zhí)行時(shí),main會(huì)調(diào)用TextTestRunner中的run來(lái)執(zhí)行夹攒,或者我們可以直接通過(guò)TextTestRunner來(lái)執(zhí)行用例蜘醋。在Runner執(zhí)行時(shí),默認(rèn)將執(zhí)行結(jié)果輸出到控制臺(tái)咏尝,我們可以設(shè)置其輸出到文件压语,在文件中查看結(jié)果。
unittest實(shí)例
先來(lái)準(zhǔn)備一些待測(cè)方法:
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
簡(jiǎn)單示例:
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)
再新建一個(gè)文件编检,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)
執(zhí)行結(jié)果:
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
可以看到胎食,執(zhí)行情況跟我們預(yù)料的一樣:執(zhí)行了2個(gè)case,并且順序是按照我們添加進(jìn)suite的順序執(zhí)行的允懂。
上面用了TestSuite的 addTests()方法厕怜,并直接傳入了TestCase列表,我們還可以:
# 直接用addTest方法添加單個(gè)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()粥航,類(lèi)似,傳入列表
# loadTestsFromTestCase()生百,傳入TestCase
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMathFunc))
注意躁锡,用TestLoader的方法是無(wú)法對(duì)case進(jìn)行排序的,同時(shí)置侍,suite中也可以套suite映之。
將結(jié)果輸出到文件中
用例組織好了,但結(jié)果只能輸出到控制臺(tái)蜡坊,這樣沒(méi)有辦法查看之前的執(zhí)行記錄杠输,我們想將結(jié)果輸出到文件。很簡(jiǎn)單秕衙,看示例:
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()
上面整個(gè)測(cè)試基本跑了下來(lái)蠢甲,但可能會(huì)遇到點(diǎn)特殊的情況:如果我的測(cè)試需要在每次執(zhí)行之前準(zhǔn)備環(huán)境,或者在每次執(zhí)行完之后需要進(jìn)行一些清理怎么辦据忘?比如執(zhí)行前需要連接數(shù)據(jù)庫(kù)鹦牛,執(zhí)行完成之后需要還原數(shù)據(jù)搞糕、斷開(kāi)連接÷罚總不能每個(gè)測(cè)試方法中都添加準(zhǔn)備環(huán)境窍仰、清理環(huán)境的代碼吧。
這就要涉及到我們之前說(shuō)過(guò)的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() 兩個(gè)方法(其實(shí)是重寫(xiě)了TestCase的這兩個(gè)方法)驹吮,這兩個(gè)方法在每個(gè)測(cè)試方法執(zhí)行前以及執(zhí)行后執(zhí)行一次,setUp用來(lái)為測(cè)試準(zhǔn)備環(huán)境晶伦,tearDown用來(lái)清理環(huán)境.
如果想要在所有case執(zhí)行之前準(zhǔn)備一次環(huán)境碟狞,并在所有case執(zhí)行結(jié)束之后再清理環(huán)境,我們可以用 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."
跳過(guò)某個(gè)case
如果我們臨時(shí)想要跳過(guò)某個(gè)case不執(zhí)行怎么辦婚陪?unittest也提供了幾種方法:
-
skip裝飾器
- unittest.skip(reason)族沃、無(wú)條件跳過(guò)
- unittest.skipIf(condition, reason)、當(dāng)condition為T(mén)rue時(shí)跳過(guò)
- unittest.skipUnless(condition, reason)泌参,當(dāng)condition為False時(shí)跳過(guò)
@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))