3A 模式#
當你開始撰寫測試時,會發現一個常見模式(Bill Wake 為此創造了 3A 這個術語):
- Arrange — 建立物件
- Act — 刺激它們
- Assert — 檢查結果
第一步 Arrange 在不同測試間往往相同,而 Act 和 Assert 則各自不同。例如有 7 和 9 兩個數,加法期望 16、減法期望 2、乘法期望 63——刺激和預期結果各不相同,但 7 和 9 不變。
效能 vs. 隔離#
如果這個模式在不同層級重複出現,我們面臨一個問題:多常建立新的測試物件?兩個約束互相衝突:
- 效能(Performance):希望測試盡快執行,若多個測試使用類似物件,希望只建立一次
- 隔離(Isolation):希望一個測試的成敗不影響其他測試。若測試共享物件且某測試修改了物件,後續測試可能改變結果
注意: 測試耦合(test coupling)有明顯的惡劣效果——一個測試失敗導致接下來十個都失敗(即使程式碼是正確的)。更隱微的問題是測試順序依賴:A 在 B 前面跑都正常,但 B 在 A 前面跑時 A 就失敗;甚至更糟——B 測試的程式碼其實有錯,但因為 A 先跑了,測試卻通過了。
結論:不要走測試耦合這條路。假設我們能讓物件建立足夠快,那就讓每個測試每次執行時都建立自己的物件。
實作 setUp#
待辦清單:
Invoke test method- Invoke setUp first
- Invoke tearDown afterward
- Invoke tearDown even if the test method fails
- Run multiple tests
- Report collected results
我們在 WasRun 中已經看過一種偽裝的形式——希望在執行測試前將旗標設為 false。為此寫一個測試:
def testSetUp(self):
test= WasRun("testMethod")
test.run()
assert(test.wasSetUp)執行後 Python 告訴我們沒有 wasSetUp 屬性。在 setUp 中設定它:
def setUp(self):
self.wasSetUp= 1但這個方法需要被呼叫。呼叫 setUp 是 TestCase 的責任:
def setUp(self):
pass
def run(self):
self.setUp()
method = getattr(self, self.name)
method()補充: 這次為了讓測試通過,需要同時修改兩個方法,在如此敏感的情境下步伐稍大。但它確實通過了。如果你想學到更多,可以思考如何做到每次只修改一個方法就讓測試通過。
利用 setUp 簡化測試#
立即利用新設施來簡化。首先,在 WasRun 的 setUp 中設定 wasRun 旗標:
def setUp(self):
self.wasRun= None
self.wasSetUp= 1然後簡化 testRunning,不再檢查執行前的旗標。這是一個常見模式——一個測試的簡化,必須以另一個測試正確執行為前提:
def testRunning(self):
test= WasRun("testMethod")
test.run()
assert(test.wasRun)兩個測試都建立了 WasRun 實例——正是之前提到的 fixture。可以在 setUp 中建立,並在各 test method 中使用。每個 test method 都在全新的 TestCaseTest 實例中執行,因此兩個測試不可能耦合:
class TestCaseTest(TestCase):
def setUp(self):
self.test= WasRun("testMethod")
def testRunning(self):
self.test.run()
assert(self.test.wasRun)
def testSetUp(self):
self.test.run()
assert(self.test.wasSetUp)待辦清單更新:
Invoke test methodInvoke setUp first- Invoke tearDown afterward
- Invoke tearDown even if the test method fails
- Run multiple tests
- Report collected results
本章回顧#
- 決定了測試撰寫的簡潔性暫時比效能更重要
- 測試並實作了
setUp() - 用
setUp()簡化了範例 test case - 用
setUp()簡化了檢查範例 test case 的測試(自我腦部手術的感覺越來越強烈了)