@(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
--