---
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