延遲處理 tearDown 的例外保護#

待辦清單:

  • Invoke test method
  • Invoke setUp first
  • Invoke tearDown afterward
  • Invoke tearDown even if the test method fails
  • Run multiple tests
  • Report collected results
  • Log string in WasRun

作者原本打算實作「即使 test method 拋出例外,tearDown() 仍然被呼叫」的功能。然而,要讓測試運作需要捕捉例外,如果在實作中犯了錯誤,例外就不會被回報,我們也看不到錯誤。

重點: 測試的實作順序很重要。選擇下一個要實作的測試時,應該找一個能教你一些東西你有信心能讓它運作的測試。如果讓一個測試通過了但下一個卡住,可以考慮回退兩步。

引入 TestResult#

我們希望看到任意數量測試的執行結果,例如:"5 run, 2 failed, TestCaseTest.testFooBar - ZeroDivideException, MoneyTest.testNegation - AssertionError"。如果測試沒有被呼叫或結果沒有被回報,至少我們有機會發現錯誤。

TestCase.run() 回傳一個 TestResult 物件來記錄執行結果:

def testResult(self):
  test= WasRun("testMethod")
  result= test.run()
  assert("1 run, 0 failed" == result.summary())

先用 Fake Implementation 開始:

class TestResult:

  def summary(self):
    return "1 run, 0 failed"

TestCase.run() 回傳 TestResult

def run(self):
  self.setUp()
  method = getattr(self, self.name)
  method()
  self.tearDown()
  return TestResult()

逐步實體化#

測試通過後,開始將 summary() 的假實作逐步實體化(make real)。首先讓執行次數成為符號常數:

def __init__(self):
  self.runCount= 1
def summary(self):
  return "%d run, 0 failed" % self.runCount

runCount 不應是常數,而是透過計算得出。初始化為 0,每次執行測試時遞增:

def __init__(self):
  self.runCount= 0
def testStarted(self):
  self.runCount= self.runCount + 1
def summary(self):
  return "%d run, 0 failed" % self.runCount

必須實際呼叫這個新方法:

def run(self):
  result= TestResult()
  result.testStarted()
  self.setUp()
  method = getattr(self, self.name)
  method()
  self.tearDown()
  return result

測試失敗的計數#

可以用同樣方式將失敗次數的常數字串 "0" 變成變數,但目前的測試不要求這樣做。所以改寫另一個測試:

def testFailedResult(self):
  test= WasRun("testBrokenMethod")
  result= test.run()
  assert("1 run, 1 failed", result.summary)

其中:

def testBrokenMethod(self):
  raise Exception

待辦清單更新:

  • Invoke test method
  • Invoke setUp first
  • Invoke tearDown afterward
  • Invoke tearDown even if the test method fails
  • Run multiple tests
  • Report collected results
  • Log string in WasRun
  • Report failed tests

第一個注意到的問題是我們沒有捕捉 WasRun.testBrokenMethod 拋出的例外。我們希望捕捉例外並在結果中記下測試失敗。先把這個測試擱置。

本章回顧#

  • 用 Fake Implementation 開始,然後透過用變數取代常數逐步實體化
  • 寫了另一個測試
  • 當那個測試失敗時,寫了一個更小規模的測試來支撐失敗測試的運作