3A 模式#

當你開始撰寫測試時,會發現一個常見模式(Bill Wake 為此創造了 3A 這個術語):

  1. Arrange — 建立物件
  2. Act — 刺激它們
  3. 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

但這個方法需要被呼叫。呼叫 setUpTestCase 的責任:

def setUp(self):
  pass
def run(self):
  self.setUp()
  method = getattr(self, self.name)
  method()

補充: 這次為了讓測試通過,需要同時修改兩個方法,在如此敏感的情境下步伐稍大。但它確實通過了。如果你想學到更多,可以思考如何做到每次只修改一個方法就讓測試通過。

利用 setUp 簡化測試#

立即利用新設施來簡化。首先,在 WasRunsetUp 中設定 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 method
  • Invoke 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 的測試(自我腦部手術的感覺越來越強烈了)