tearDown 的需求#

待辦清單:

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

有時測試需要在 setUp() 中配置外部資源。為了維持測試的獨立性,配置了外部資源的測試需要在完成前釋放它們,或許透過 tearDown() 方法。

從旗標到日誌#

最直覺的做法是再引入一個旗標來測試 tearDown。但那些旗標已經開始令人困擾,而且它們忽略了方法的一個重要面向:setUp() 在 test method 之前呼叫,tearDown() 在之後呼叫

flowchart LR
    A["setUp()"] --> B["testMethod()"]
    B --> C["tearDown()"]
    style A fill:#4CAF50,color:#fff
    style B fill:#2196F3,color:#fff
    style C fill:#FF9800,color:#fff

作者決定改變測試策略——改用一個**日誌字串(log)**記錄被呼叫的方法。透過持續附加到日誌,可以保留方法被呼叫的順序。

新增待辦項目:

  • 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

setUp 中初始化日誌:

def setUp(self):
  self.wasRun= None
  self.wasSetUp= 1
  self.log= "setUp "

修改 testSetUp 改為檢查日誌而非旗標:

def testSetUp(self):
  self.test.run()
  assert("setUp " == self.test.log)

接著可以刪除 wasSetUp 旗標。在 testMethod 中也記錄到日誌:

def testMethod(self):
  self.wasRun= 1
  self.log= self.log + "testMethod "

這會讓 testSetUp 失敗,因為實際日誌內容變成 "setUp testMethod "。修改預期值:

def testSetUp(self):
  self.test.run()
  assert("setUp testMethod " == self.test.log)

現在這個測試同時做了兩個測試的工作,因此可以刪除 testRunning 並重新命名 testSetUp

def testTemplateMethod(self):
  test= WasRun("testMethod")
  test.run()
  assert("setUp testMethod " == test.log)

補充: 注意 WasRun 實例只在一個地方使用了,所以之前把它放到 setUp 的手法必須撤回。基於少量使用就做重構、之後又得撤銷,這是很常見的。有些人會等到三、四次使用後才重構。作者偏好把思考力用在設計上,反射性地做重構而不擔心是否需要立即撤銷。

實作 tearDown#

準備好測試 tearDown() 了——先寫測試:

def testTemplateMethod(self):
  test= WasRun("testMethod")
  test.run()
  assert("setUp testMethod tearDown " == test.log)

測試失敗。讓它通過很簡單:

def run(self, result):
  result.testStarted()
  self.setUp()
  method = getattr(self, self.name)
  method()
  self.tearDown()
def setUp(self):
  self.log= "setUp "
def testMethod(self):
  self.log= self.log + "testMethod "
def tearDown(self):
  self.log= self.log + "tearDown "

出乎意料地,錯誤不是出在 WasRun,而是在 TestCaseTest——TestCase 中缺少空的 tearDown() 實作:

def tearDown(self):
  pass

技巧: 這次我們從使用正在開發的框架本身獲得了回饋價值。框架自身的測試發現了遺漏的預設實作。

待辦清單更新:

  • 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

本章回顧#

  • 將測試策略從旗標重構為日誌
  • 使用新的日誌策略測試並實作了 tearDown()
  • 發現一個問題並大膽地直接修復,而非回退(這是個好主意嗎?)