某一天幫同事 code review 後發現他重改花費很多時間,於是決定寫一下 Python 的 Unit test 教學來加速團隊的開發流程。
Unit test 顧名思義就是單元測試,何謂單元?對於手機來說,接收訊號、發射訊號、螢幕顯示、喇叭播放聲音…等等都是一個工作單元,而單元測試就是確認這些單元的功能是否如預期執行,才能確保使用者購買的產品是完整的。
在進行單元測試時,要確保測試單元和其他單元獨立,否則測試的就是兩個單元的正確性或是兩個單元協作行為。就軟體測試來說,單元測試通常都是以函式或方法為單位進行,你帶入函式固定的參數並預期函式應該要有的輸出,例如傳回預期的數值、產生的檔案、預測的資料等等。
範例
今天使用 python 的 unittest,這個套件主要包含四個部分:
- Test case:測試的最小單元。
- Test fixture:執行一或多個測試前必要的預備資源,以及相關的清除資源動作。
- Test suite:一組測試案例、測試套件或者是兩者的組合。
- Test runner:負責執行測試並提供測試結果的元件。
Test case
unittest 提供了一個類別 TestCase,透過繼承它來建立新的測試案例
1 | import unittest |
每個單元測試必須定義在一個以 test 開頭的方法中。並且以 self.assertEqual(expected, result) 驗證實際的輸出結果是否符合預期。
被測式的 check() 和 get_email() 只是簡單的函式,一個確認的型態是否正確,另一個是取得並驗證使用者的 email 是否為公司信箱。
Test fixture
可以在 TestCase 的子類別中定義 setUp() 與 tearDown() 函式,在每個單元測試開始前,都會執行 setUp(),而在每個單元測試結束後都會執行 tearDown()。
例如:某個測試環節中 setUp() 建立了一份 log 文件,測試結束後 tearDown() 會將 log 文件刪除。
Test suite
根據測試的需求,可以將不同的函式組合在一起進行測試,例如 FlaskTestCase 中有多個 test_xxx 函式,而今天我只想將 check() 和 get_email()組合再一起進行測試:
1 | suite = unittest.TestSuite() |
如果要套用所有 FlaskTestCase 類別中的 test 函式:
1 | suite = (unittest.TestLoader().loadTestsFromTestCase(FlaskTestCase)) |
也可以任意組合 test case,即使在不同的類別之中:
1 | suite = unittest.TestSuite() |
Test runner
可以在 code 裡面直接使用 TextTestRunner:
1 | suite = (unittest.TestLoader().loadTestsFromTestCase(FlaskTestCase)) |
或透過 main 函式來執行:
1 | unittest.main(verbosity=2) |
結語
對於產品來說進行 Unit test 是必不可少的,然而對於部分探索性研究的專案而言,Unit test 並非必須,因為撰寫 Unit test 需要把軟體會且可能遇到的 test case 定義清楚,並盡量測試,這個步驟會花費較多時間,而這也是為何多數企業會額外招募測試工程師進行 Unit test 的撰寫及 deploy。