# 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/)