--- title: Testing tags: learning notes, testing, Python --- # Testing in Python 要測試Python程式,最簡單的方式就是加入print()陳述式。Python Idle的Read-Evaluate-Print Loop(REPL)可以快速的編輯修改。 ## Testing your code ### Automated vs Manual Testing * 探索測試(exploratory testing)就是手動測試的一種形式。為了完成完整的手動測試,你必須列出整個應用程式的功能清單,可能是可以接受輸入的形式、或是預期的結果,每當程式有被修改的時候,就要再重新逐條檢查 * 自動化測試可以藉由腳本執行你的測試計畫(想測試的部分, 想測試的順序, 預期的結果) ### Unit Tests vs Integration Tests * 想像你正在測試一台車子的大燈,測試多個部件稱為 **Integration testing**,可以測試部件間的協調結果 - 打開大燈(known as the **test step**) - 去外面看或問朋友燈有沒有亮(known as the **test assertion**) * Integration testing的難點是若它沒有給出正確的結果,那會非常難診斷出是哪一個環節出錯,就像大燈沒亮,燈泡壞了、電池壞了、發電機壞了、電腦壞了都有可能 * **Unit test**是一種比較小的測試,確認單一部件是否正常運作,好處是可以幫你隔離出壞掉的部分,增加修復的速度 * 下列就是一個test case,一個判斷(assertion)然後在CLI執行 ```python= def test_sum(): assert sum([1, 2, 3]) == 6, "Should be 6" if __name__ == "__main__": test_sum() print("Everything passed") ``` ```shell= $ python3 test_sum.py Everything passed ``` ### Choosing a Test Runner Test runner是設計來跑測試、確認輸出、診斷測試還有提供工具進行debugging ### Basic example ```python= import unittest class TestStringMethods(unittest.TestCase): def test_upper(self): self.assertEqual('foo'.upper(), 'FOO') def test_isupper(self): self.assertTrue('FOO'.isupper()) self.assertFalse('Foo'.isupper()) def test_split(self): s = 'hello world' self.assertEqual(s.split(), ['hello', 'world']) # check that s.split fails when the separator is not a string with self.assertRaises(TypeError): s.split(2) if __name__ == '__main__': unittest.main() ``` * 定義的方法名稱習慣開頭加test,這個慣例才能代表測試項 * 每一項測試的重點在: - `assertEuqal()`➡︎ 確認預期結果(expected result) - `assertTrue()`or`assertFalse()`➡︎ 確認條件(condition) - `assertRaises()`➡︎ 確認特定例外被發起(specific exception gets raised) * 用上述這些而不是單單用assert陳述式,才方便儲存結果和產出報告 * 會在每一個測試函式前呼叫`setUp()`,在每一個測試函式後呼叫`tearDown()`,目的是為了配置和釋出外部資源,請見[Organizing test code](https://docs.python.org/3/library/unittest.html#organizing-tests) ### Organizing test code * Unit test由test case組成,必須要寫出類別[TestCase](https://docs.python.org/3/library/unittest.html#unittest.TestCase)的子類別,或使用[FunctionTestCase](https://docs.python.org/3/library/unittest.html#unittest.FunctionTestCase)(屬於TestCase的子類別),才是test case * test case必須要可以自己被隔離獨立使用,或是與其他隨意數量的test case一起組合使用 * 最簡單的子類別就是運作單一個測試方法,如以下: ```python= import unittest class DefaultWidgetSizeTestCase(unittest.TestCase): def test_default_widget_size(self): widget = Widget('The widget') self.assertEqual(widget.size(), (50, 50)) ``` * 類別TestCase提供很多判斷(assert)工具來進行確認和回報錯誤 * 測試也可以有很多,組合也可以重複,我們可以運作測試之前,[setUp()](https://docs.python.org/3/library/unittest.html#unittest.TestCase.setUp)這個方法會被測試框架自動呼叫,來分解組合的程式 * 同樣地,測試完成後運作[tearDown()](https://docs.python.org/3/library/unittest.html#unittest.TestCase.tearDown)方法進行整理 ```python= import unittest class WidgetTestCase(unittest.TestCase): def setUp(self): self.widget = Widget('The widget') def tearDown(self): self.widget.dispose() ``` * 這樣的運作環境被稱為*test fixture*,測試夾具 >> A test fixture represents the **preparation** needed to perform one or more tests, and any associate cleanup actions. This may involve, for example, creating temporary or proxy databases, directories, or starting a server process. * 建議你還可以根據測試的特性做群組測試(group tests),module unittest有提供*test suite*這個機制,參考類別[TestSuite](https://docs.python.org/3/library/unittest.html#unittest.TestSuite) >> A test suite is a collection of test cases, test suites, or both. It is used to aggregate tests that should be executed together. ### Re-using old test code * TestCase的子類別FunctionTestCase的特色是將就有的測試方法打包起來 ### Skipping tests and expected failures * unittest module支援跳過單獨或是整個類別的測試,支援標示"expected failure",程式失敗時,就不會在類別[TestResults](https://docs.python.org/3/library/unittest.html#unittest.TestResult)被記錄 * 跳過一個測試項可以使用skip()裝飾器或是skip()裝飾器的變種 >> TestCase.skipTest() within a setUp() or test method, or raising SkipTest directly. * 待補 ### Distinguishing test iterations using subtests