python 單元測試 -- unittest

@(python)

單元測試是對程序中的單個子程序抚垃、函數(shù)、過程進行的測試荆隘,面向白盒測試恩伺。
單元測試測試覆蓋常用子程序的輸入組合,邊界條件和異常處理臭胜,盡可能保證單元測試代碼簡潔莫其,避免單測本身代碼有 bug 影響對測試對象的測試結(jié)果癞尚。

python 提供單元測試框架 unittest,

簡單編寫一個模塊 calculator.py 耸三,作為單元測試對象

#!/usr/bin/env python
# coding=utf-8

def my_print(str):
    pass
    #print(str)

class Calculator():
    __version__ = 4
    def __init__(self, a, b):
        my_print("cal init")
        self.a = int(a)
        self.b = int(b)

    def __del__(self):
        my_print("cal del")

    def add(self):
        return self.a + self.b

    def sub(self):
        return self.a - self.b

    def mul(self):
        return self.a * self.b

    def div(self):
        return self.a / self.b

編寫測試用例 (test case)

如上乱陡, 我們?yōu)樵撃K編寫對應(yīng)的單元測試,取名 testCalculator.py :

#!/usr/bin/env python
# coding=utf-8
import unittest
from calculator import Calculator

class CalculatorTest(unittest.TestCase):

    def test_add_0(self):
        cal = Calculator(8, 4)
        result = cal.add()
        self.assertEqual(result, 12)

    def test_add_1(self):
        cal = Calculator(8, 4)
        result = cal.add()
        self.assertNotEqual(result, 12)
        
    def will_not_callme(self):
        print("lalalla")
        
if __name__ == "__main__":
    unittest.main()

簡單地編寫了兩個對模塊方法 add() 的測試用例仪壮。編寫單元測試憨颠,我們需要對應(yīng)測試的對象實現(xiàn)一個類,繼承 unittest.TestCase积锅。
測試類 CalculatorTest 中的測試用例都是以 test_爽彤, 其他方法在執(zhí)行腳本的時候框架不會直接調(diào)用執(zhí)行。
對應(yīng)目標模塊的各個方法編寫測試用例,使用斷言判斷結(jié)果,注意使用的斷言是 unittest.TestCase內(nèi)置的之宿,這樣才能保證不會由于某個用例斷言失敗而直接退出執(zhí)行厂抖。

執(zhí)行 運行結(jié)果如下,可以看到吞鸭,沒有通過的例子斷言了錯誤的行號,可以快速定位問題。

$  python testCalculator.py -v
test_add_0 (__main__.CalculatorTest) ... ok
test_add_1 (__main__.CalculatorTest) ... FAIL

======================================================================
FAIL: test_add_1 (__main__.CalculatorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "aa.py", line 16, in test_add_1
    self.assertNotEqual(result, 12)
AssertionError: 12 == 12

----------------------------------------------------------------------
Ran 2 tests in 0.002s

FAILED (failures=1)

內(nèi)置的斷言

Method Checks that New in
assertEqual(a, b) a == b
assertNotEqual(a, b) a != b
assertTrue(x) bool(x) is True
assertFalse(x) bool(x) is False
assertIs(a, b) a is b 3.1
assertIsNot(a, b) a is not b 3.1
assertIsNone(x) x is None 3.1
assertIsNotNone(x) x is not None 3.1
assertIn(a, b) a in b 3.1
assertNotIn(a, b) a not in b 3.1
assertIsInstance(a, b) isinstance(a, b) 3.2
assertNotIsInstance(a, b) not isinstance(a, b) 3.2

測試初始化和清理(test fixture)

看到上面的例子硫痰,每次寫一個測試用例都要重新定義一個測試實例 cal, 顯得很重復(fù)窜护,但是直接在測試類初始化函數(shù)定義的話又怕用例之間相互干擾效斑。還有就是,有些測試柱徙,需要測試前構(gòu)建測試場景缓屠,測試結(jié)束后清理。
類似以上的問題护侮,unittest 提供了幾個方法實現(xiàn)藏研。

  • setUp() : 執(zhí)行每個測試用例前都會調(diào)用,執(zhí)行準備
  • tearDown() : 執(zhí)行完每個用例后都會調(diào)用概行,執(zhí)行清理

對應(yīng)上面兩個方法蠢挡,下面兩個在測試類函數(shù)開始和結(jié)束調(diào)用

  • setUpClass()
  • tearDownClass()

測試套件 (test suit)

測試套件是多個測試用例的集合
結(jié)合上面內(nèi)容,看個相對完整的測試套件 :

#!/usr/bin/env python
# coding=utf-8

import sys
import unittest
from calculator import Calculator

class CalculatorTest(unittest.TestCase):

    def setUp(self):
        self.cal = Calculator(8, 4)
        #assert 1 == 2, "test if setUp error"

    def tearDown(self):
        self.cal = None

    def test_add(self):
        '''
        des : test add
        '''
        result = self.cal.add()
        self.assertEqual(result, 12)
        # if use python builtin assert , it will stop run
        # assert result == 12, "add error"

    def test_sub(self):
        result = self.cal.sub()
        self.assertEqual(result, 4)

    def test_mul(self):
        result = self.cal.mul()
        self.assertEqual(result, 32)

    def test_div(self):
        result = self.cal.div()
        self.assertEqual(result, 2)

    @unittest.skip('just skip')
    def test_div_1(self):
        result = self.cal.div()
        self.assertEqual(result, 3)

    @unittest.skipIf(Calculator.__version__ < 5, 'not support this library')
    def test_div_2(self):
        result = self.cal.div()
        self.assertEqual(result, 2)

    @unittest.skipIf(Calculator.__version__ < 2, 'not support this library')
    def test_div_3(self):
        result = self.cal.div()
        self.assertEqual(result, 2)

    @unittest.skipUnless(sys.platform.startswith('win'),  'windows')
    def test_div_4(self):
        result = self.cal.div()
        self.assertEqual(result, 2)

    @unittest.skipUnless(sys.platform.startswith('linux'),  'linux')
    def test_div_5(self):
        result = self.cal.div()
        self.assertEqual(result, 2)

def suite1():
    ## 執(zhí)行測試用例構(gòu)建條件
    suite = unittest.TestSuite()
    suite.addTest(CalculatorTest("test_add"))
    suite.addTest(CalculatorTest("test_sub"))
    return suite

def suite2():
    # 指定前綴構(gòu)建測試套件
    suite = unittest.makeSuite(CalculatorTest, 'test')
    return suite

if __name__ == "__main__":
    ## 運行用例方法1
    #my_print("Test suit : run some testcase")
    #runner = unittest.TextTestRunner()
    #runner.run(suite1())

    ## 運行用例方法2
    #my_print("Test suit : run all testcase")
    #runner.run(suite2())

    ## 運行用例方法3
    # python ./testCalculator.py -v
    # python ./testCalculator.py -v testCalculator.test_add
    my_print("Run all testcase dircetly")
    unittest.main()

關(guān)注的點:

  • 測試用例指定條件凳忙,在不符合條件的情況下跳過不執(zhí)行(見最后幾個帶修飾器的用例业踏,對于跨平臺什么實用)
  • 所有測試用例執(zhí)行順序與其在類中的定義順序沒有關(guān)系,不能依靠這個先后關(guān)系涧卵;并且不同用例之間最好不要相互依賴勤家。

如上,運行所有測試用例柳恐,或者指定某個測試用例運行伐脖。

$ python testCalculator.py -v
test_add (__main__.CalculatorTest) ... ok
test_div (__main__.CalculatorTest) ... ok
test_div_1 (__main__.CalculatorTest) ... skipped 'just skip'
test_div_2 (__main__.CalculatorTest) ... skipped 'not support this library'
test_div_3 (__main__.CalculatorTest) ... ok
test_div_4 (__main__.CalculatorTest) ... skipped 'windows'
test_div_5 (__main__.CalculatorTest) ... ok
test_mul (__main__.CalculatorTest) ... ok
test_sub (__main__.CalculatorTest) ... ok

----------------------------------------------------------------------
Ran 9 tests in 0.001s

OK (skipped=3)

$ python testCalculator.py -v CalculatorTest.test_add
test_add (__main__.CalculatorTest) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

多個測試套件運行

$ python -m unittest discover 
### 結(jié)合一下參數(shù)热幔, python自動匹配運行所有符合條件的測試用例
-v, --verbose
Verbose output
 
-s, --start-directory directory
Directory to start discovery (. default)
 
-p, --pattern pattern
Pattern to match test files (test*.py default)
 
-t, --top-level-directory directory
Top level directory of project (defaults to start directory)

或者直接新建一個文件,import 所有測試類

使用 mock

前面提到讼庇,單元測試測試最小單元绎巨,但是有時候測試,遇到測試模塊需要依賴于其他模塊蠕啄,一個是不確定依賴的模塊是否有問題场勤;另一個是依賴模塊容易構(gòu)造獲取,沒有實現(xiàn)歼跟。比如網(wǎng)絡(luò)消息分析包和媳,需要接收網(wǎng)絡(luò)字節(jié)之類的。
這種情況下哈街,就需要對依賴的模塊進行 mock留瞳,虛擬一個依賴模塊供我們測試。

如上面的例子骚秦,假如原理的 add() 還沒有實現(xiàn)

def  add(self):
    pass

測試用例類似如下她倘, 對其進行mock

from unittest import mock

    def test_add(self):
        result = self.cal.add()
        self.assertNotEqual(result, 12)
        self.cal.add = mock.Mock(return_value=12)
        result = self.cal.add()
        self.assertEqual(result, 12)

大概的意思,詳細根據(jù)實際需求了解使用
參考 python mock

--

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末骤竹,一起剝皮案震驚了整個濱河市帝牡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蒙揣,老刑警劉巖靶溜,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異懒震,居然都是意外死亡罩息,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門个扰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瓷炮,“玉大人,你說我怎么就攤上這事递宅∧锵悖” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵办龄,是天一觀的道長烘绽。 經(jīng)常有香客問我,道長俐填,這世上最難降的妖魔是什么安接? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮英融,結(jié)果婚禮上盏檐,老公的妹妹穿的比我還像新娘歇式。我一直安慰自己,他們只是感情好胡野,可當(dāng)我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布材失。 她就那樣靜靜地躺著,像睡著了一般给涕。 火紅的嫁衣襯著肌膚如雪豺憔。 梳的紋絲不亂的頭發(fā)上额获,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天够庙,我揣著相機與錄音,去河邊找鬼抄邀。 笑死耘眨,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的境肾。 我是一名探鬼主播剔难,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼奥喻!你這毒婦竟也來了偶宫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤环鲤,失蹤者是張志新(化名)和其女友劉穎纯趋,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冷离,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡吵冒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了西剥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片痹栖。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖瞭空,靈堂內(nèi)的尸體忽然破棺而出揪阿,到底是詐尸還是另有隱情,我是刑警寧澤咆畏,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布南捂,位于F島的核電站,受9級特大地震影響鳖眼,放射性物質(zhì)發(fā)生泄漏黑毅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一钦讳、第九天 我趴在偏房一處隱蔽的房頂上張望矿瘦。 院中可真熱鬧枕面,春花似錦、人聲如沸缚去。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽易结。三九已至枕荞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間搞动,已是汗流浹背躏精。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鹦肿,地道東北人矗烛。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像箩溃,于是被迫代替她去往敵國和親瞭吃。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 單元測試 什么是單元 單元測試(unit testing)涣旨,是指對軟件中的最小可測試單元(一個模塊歪架、一個函數(shù)或者一...
    PPMac閱讀 6,501評論 0 19
  • unittest作為一個python中的基本模塊,是其他框架和工具的基礎(chǔ)霹陡,官方文檔神馬的最實用了:https://...
    cheneydc閱讀 8,121評論 0 3
  • 本文試圖總結(jié)編寫單元測試的流程和蚪,以及自己在寫單元測試時踩到的一些坑。如有遺漏穆律,純屬必然惠呼,歡迎補充。 目錄概覽: 編...
    蘇尚君閱讀 3,416評論 0 4
  • Startup 單元測試的核心價值在于兩點: 更加精確地定義某段代碼的作用峦耘,從而使代碼的耦合性更低 避免程序員寫出...
    wuwenxiang閱讀 10,090評論 1 27
  • 單元測試不是一個小工程剔蹋,需要多用些時間才能做好,不要希望通過這個文章就能掌握單元測試辅髓,這只是一個入門泣崩,需要自己動手...
    勇不言棄92閱讀 7,795評論 9 60