在計(jì)算機(jī)編程中永丝,單元測(cè)試(英語:Unit Testing)又稱為模塊測(cè)試, 是針對(duì)程序模塊(軟件設(shè)計(jì)的最小單位)來進(jìn)行正確性檢驗(yàn)的測(cè)試工作。程序單元是應(yīng)用的最小可測(cè)試部件箭养。在過程化編程中慕嚷,一個(gè)單元就是單個(gè)程序、函數(shù)、過程等喝检;對(duì)于面向?qū)ο缶幊绦崂保钚卧褪欠椒ǎɑ悾ǔ悾┠铀怠⒊橄箢愒杼贰⒒蛘吲缮悾ㄗ宇悾┲械姆椒ā?/p>
通常來說,程序員每修改一次程序就會(huì)進(jìn)行最少一次單元測(cè)試损俭,在編寫程序的過程中前后很可能要進(jìn)行多次單元測(cè)試蛙奖,以證實(shí)程序達(dá)到軟件規(guī)格書要求的工作目標(biāo),沒有程序錯(cuò)誤杆兵;雖然單元測(cè)試不是什么必須的外永,但也不壞,這牽涉到項(xiàng)目管理的政策決定拧咳。
每個(gè)理想的測(cè)試案例獨(dú)立于其它案例;為測(cè)試時(shí)隔離模塊囚灼,經(jīng)常使用stubs骆膝、mock或fake等測(cè)試馬甲程序。單元測(cè)試通常由軟件開發(fā)人員編寫灶体,用于確保他們所寫的代碼匹配軟件需求和遵循開發(fā)目標(biāo)阅签。它的實(shí)施方式可以是非常手動(dòng)的(通過紙筆),或者是做成構(gòu)建自動(dòng)化的一部分蝎抽。
測(cè)試的通用規(guī)則:
- 測(cè)試單元應(yīng)該集中于小部分的功能政钟,并且證明它是對(duì)的。
- 每個(gè)測(cè)試單元應(yīng)該完全獨(dú)立樟结。
- 通過Mock去除依賴
- 盡量使測(cè)試單元快速運(yùn)行养交。
- 實(shí)現(xiàn)鉤子來持續(xù)集成
我們通過一個(gè)簡(jiǎn)單的python
程序及unittest
作為示例來為大家介紹如何進(jìn)行測(cè)試,這里推薦大家使用python3
來運(yùn)行示例瓢宦。
我們先創(chuàng)建一個(gè)將會(huì)使用的測(cè)試目錄
mkdir /tmp/TestHookTest
cd /tmp/TestHookTest
測(cè)試單元應(yīng)該集中于小部分的功能碎连,并且證明它是對(duì)的
下圖為unittest
包中包含的斷言
我們現(xiàn)在來寫一個(gè)通過用戶名獲得github信息的一個(gè)函數(shù),并對(duì)這個(gè)函數(shù)進(jìn)行測(cè)試
# test.py
import unittest
import json
import requests
def fetch_github_profile(username):
response = requests.get('https://api.github.com/users/' + username)
return response.json()
class SaveDataTest(unittest.TestCase):
def test_fetch_github_profile(self):
username = 'ZhangBohan'
data = fetch_github_profile('ZhangBohan')
self.assertEqual(data['login'], username)
通過python3 -m unittest test
運(yùn)行
每個(gè)測(cè)試單元應(yīng)該完全獨(dú)立
每個(gè)都能夠單獨(dú)運(yùn)行驮履,除了調(diào)用的命令鱼辙,都需在測(cè)試套件中。要想實(shí)現(xiàn)這個(gè)規(guī)則玫镐,測(cè)試單元應(yīng)該加載最新的數(shù)據(jù)集倒戏,之后再做一些清理。
如果有數(shù)據(jù)庫依賴恐似,在每次測(cè)試前創(chuàng)建測(cè)試數(shù)據(jù)庫杜跷,結(jié)束后銷毀該數(shù)據(jù)庫,測(cè)試應(yīng)該有單獨(dú)的數(shù)據(jù)庫,不要在生產(chǎn)和開發(fā)環(huán)境測(cè)試葱椭,避免數(shù)據(jù)變化引起的測(cè)試失敗
通過Mock去除依賴
假設(shè)我們現(xiàn)在想把取得的用戶數(shù)據(jù)保存到本地捂寿,并測(cè)試是否正確保存
# test.py
import unittest
import json
import requests
def fetch_github_profile(username):
response = requests.get('https://api.github.com/users/' + username)
return response.json()
def save_data(data):
with open('data.json', 'w') as f:
f.write(json.dumps(data))
class SaveDataTest(unittest.TestCase):
def test_fetch_github_profile(self):
username = 'ZhangBohan'
data = fetch_github_profile('ZhangBohan')
self.assertEqual(data['login'], username)
def test_save_data(self):
data = fetch_github_profile('ZhangBohan')
save_data(data)
with open('data.json') as f:
file_data = json.loads(f.read())
self.assertIsNotNone(file_data)
self.assertEqual(data['id'], file_data['id'])
在這個(gè)測(cè)試中我們的test_save_data
中的data
依賴fetch_github_profile
中的返回?cái)?shù)據(jù),現(xiàn)實(shí)情況中會(huì)遇到更為復(fù)雜的依賴孵运,為了一個(gè)測(cè)試用例秦陋,我們可能需要構(gòu)建大量的初始化數(shù)據(jù)。我們可以通過mock來解除這個(gè)依賴治笨,讓test_save_data
專注于測(cè)試保存數(shù)據(jù)部分
# test.py
import unittest
import json
from unittest.mock import MagicMock
import requests
def fetch_github_profile(username):
response = requests.get('https://api.github.com/users/' + username)
return response.json()
def save_data(data):
with open('data.json', 'w') as f:
f.write(json.dumps(data))
FAKE_PROFILE_DATA = {
"login": "ZhangBohan",
"id": 2317407
}
class SaveDataTest(unittest.TestCase):
def test_fetch_github_profile(self):
username = 'ZhangBohan'
data = fetch_github_profile('ZhangBohan')
self.assertEqual(data['login'], username)
def test_save_data(self):
fetch_github_profile = MagicMock(return_value=FAKE_PROFILE_DATA)
data = fetch_github_profile('ZhangBohan')
save_data(data)
with open('data.json') as f:
file_data = json.loads(f.read())
self.assertIsNotNone(file_data)
self.assertEqual(data['id'], file_data['id'])
盡量使測(cè)試單元快速運(yùn)行
如果一個(gè)單獨(dú)的測(cè)試單元需要較長(zhǎng)的時(shí)間去運(yùn)行驳概,開發(fā)進(jìn)度將會(huì)延遲,測(cè)試單元將不能如期常態(tài)性運(yùn)行旷赖。有時(shí)候顺又,因?yàn)闇y(cè)試單元需要復(fù)雜的數(shù)據(jù)結(jié)構(gòu),并且當(dāng)它運(yùn)行時(shí)每次都要加載等孵,所以其運(yùn)行時(shí)間較長(zhǎng)稚照。把運(yùn)行吃力的測(cè)試單元放在單獨(dú)的測(cè)試組件中,并且按照需要運(yùn)行其它測(cè)試單元俯萌。
實(shí)現(xiàn)hook來持續(xù)集成
通過代碼提交的本地hook或者webhook來持續(xù)集成測(cè)試你的代碼果录。
舉個(gè)git本地hook的例子(這可假設(shè)你了解git hook的工作原理)。
> git init
> vim .git/hooks/pre-commit
在.git/hooks/pre-commit
文件中寫入
#!/bin/sh
cd /tmp/TestHookTest && python3 -m unittest test
執(zhí)行:
> chmod +x .git/hooks/pre-commit
> git add test.py
> git commit -m "test hook"
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
[master (root-commit) b390117] test hook
1 file changed, 9 insertions(+)
create mode 100644 test.py
在遠(yuǎn)程代碼倉庫部署的webhook能更好的測(cè)試全部代碼咐熙。