# Python unittest
unittest是專門做單元測試(unit test)的模組,其主要提供一個單元測試的框架(Framework),讓每一位利用Python撰寫程式的人都能輕鬆對程式進行測試。
## 如何建立unittest
### unittest模組中有4個重要的名詞
* Test fixture: 初始化,指的是進行一項測試前所需進行的準備工作,例如要測試資料庫的讀寫時,所需要的 Test fixture 就有可能是建立資料庫連線、建立測試用的資料表等。
* Test case: Test case 是最小的測試單位,每一個 Test case 只會用來測試一項很小的程式邏輯(或者函式)是否如預期般運作正常。
* Test suite: Test suite 則是多個 Test case 或者 Test suite 的集合,專門用來測試需要多個單元測試同時進行的測試項目,例如測試音樂播放軟對於各種格式音訊檔案的支援度,就可能需要 mp3, midi, wav 等多種音訊格式的 Test case 一起進行測試,此種由多個 Test case 組成的測試,就可稱為 Test suite 。
* Test runner: 在 unittest 模組中,Test runner 負責的工作是測試的執行與測試結果的呈現。
### 實作方法
1. import unittest
2. 所有的測試類別都必須繼承 unittest.TestCase,才能夠自動被Test runner載入並執行。
3. 測試類別中的單元測試方法必須以 **test** 開頭作為命名,沒有以 test 開頭的話,就不會被認為是 Test case 而不會被執行!
4. 測試類別中的單元測試方法無法被指定順序。
5. unittest 模組的 assert 相關方法,用來評斷測試的結果是否如同我們預期的結果,如果有任何不在預期的結果發生,就代表測試沒有通過。
6. unittest 模組每一次執行測試方法的順序是 setUp > test 開頭的測試方法 > tearDown
**以下範例是測試compare_string函式,而test_compare_string 與 test_compare_hex_string 則是 Test case,也就是測試的最小單位, compare_string 必須要通過這 2 個 Test case 才會被認為有通過測試。**
```python=
import unittest
def compare_string(s1, s2): # 測試目標
if s1 == s2:
return True
else:
return False
class MyFirstTest(unittest.TestCase):
def setUp(self): # Test fixture
# 可以把一些變數初始化以供測試用
self.default_greeting = u"Hello"
def test_compare_string(self):
test_greeting = "Hello"
self.assertTrue(compare_string(self.default_greeting, test_greeting))
def test_compare_hex_string(self):
hex_greeting = b"\x48\x65\x6c\x6c\x6f"
self.assertFalse(compare_string(self.default_greeting, hex_greeting))
if __name__ == '__main__':
# 載入了所撰寫的 Test case
tests = unittest.TestLoader().loadTestsFromTestCase(MyFirstTest)
# Test runner,執行所撰寫的測試
unittest.TextTestRunner(verbosity=2).run(tests)
===result===
Ran 2 tests in 0.001s
OK
```
當程式隨著功能的增加而越來越龐大時,測試的項目就會理所當然隨之增加,因此測試類別就有可能越來越多,最後我們就需要針對一些相關的測試進行分類的管理。此時,我們就需要 Test suite 來進行管理, Test suite 的範例如下所示。
```python=
import unittest
def compare(v1, v2):
if v1 == v2:
return True
else:
return False
class MyFirstTest(unittest.TestCase):
def setUp(self):
self.default_greeting = u"Hello"
def test_compare_string(self):
test_greeting = u"HellO"
self.assertFalse(compare(self.default_greeting, test_greeting))
def test_compare_hex_string(self):
hex_greeting = b"\x48\x65\x6c\x6c\x6f"
self.assertFalse(compare(self.default_greeting, hex_greeting))
class MySecondTest(unittest.TestCase):
def test_compare_int_bool(self):
v1 = 1
v2 = True
self.assertTrue(compare(v1, v2))
def test_compare_type(self):
v1 = type(1)
v2 = type(True)
self.assertFalse(compare(v1, v2))
if __name__ == '__main__':
test1 = unittest.TestLoader().loadTestsFromTestCase(MyFirstTest)
test2 = unittest.TestLoader().loadTestsFromTestCase(MySecondTest)
# Test suite
suite = unittest.TestSuite()
suite.addTests(test1)
suite.addTests(test2)
# 以 TextTestRunner 執行此一 Test suite 中的所有單元測試方法
unittest.TextTestRunner(verbosity=2).run(suite)
===result===
Ran 4 tests in 0.002s
OK
```
### 測試也需要善後工作- tearDown
測試過程難免會需要一些 Test fixture ,例如產生設定檔、連結資料庫、產生檔案等等,而這些 Test fixture 也可能需要在測試完成後進行善後與清除,此時可以選擇覆寫 unittest 模組的
tearDown 方法, tearDown 會在每一個測試方法執行完之後被呼叫( setUp 則相反),因此可以把測試的善後與清除的工作實作在 tearDown 方法中。
```python=
import unittest
def compare_string(s1, s2):
if s1 == s2:
return True
else:
return False
class MyFirstTest(unittest.TestCase):
def setUp(self):
print("setUp!")
self.default_greeting = u"Hello"
def test_compare_string(self):
test_greeting = "Hello"
print(test_greeting)
self.assertTrue(compare_string(self.default_greeting, test_greeting))
def test_compare_hex_string(self):
hex_greeting = b"\x48\x65\x6c\x6c\x6f"
print(hex_greeting)
self.assertFalse(compare_string(self.default_greeting, hex_greeting))
def tearDown(self):
print("tearDown!")
if __name__ == '__main__':
unittest.main()
===result===
Ran 2 tests in 0.002s
OK
setUp!
b'Hello'
tearDown!
setUp!
Hello
tearDown!
```
### More
* Test Discover: 除了能夠將性質相似的 Test case 放在同一個類別或檔案之外,也可以將這些測試程式碼以資料夾進行分類管理,再搭配 unittest 的 test discover 功能,就能夠執行指定的資料夾內的所有測試程式。
* Skipping tests: 針對測試方法的執行進行條件的設定,例如滿足特定條件就略過測試。
* Expected failures: 可以使用在某些預期必定會無法通過的測試方法上,使得無法通過的測試方法不會被視為沒有通過此一測試方法。
## 參考
[官網](https://docs.python.org/3/library/unittest.html)
[Python unittest](https://myapollo.com.tw/2016/09/17/python-unittest-1/)