基於Python的測試驅動開發實戰

發表於2015-04-15

近年來測試驅動開發(TDD)受到越來越多的關注。這是一個持續改進的過程,能從一開始就形成規範,幫助提高程式碼質量。這是切實可行的而非天馬行空的。

TDD的全過程是非常簡單的。藉助TDD,程式碼質量會得到提升,同時可以讓你保持清晰的思路。TDD與敏捷開發可謂強強聯合,特別是在進行結對程式設計的時候。本文主要介紹了TDD的核心概念,還有結合nosetest單元測試包進行Python示例簡析。另外還會介紹一些Python備用包。

TDD是什麼?

使用該方法可讓你少走前人的彎路

顧名思義,TDD即進行程式設計時先把測試部分寫好,當發現不能通過時,再進行程式設計以使測試通過。然後在這基礎上適當地調整測試程式碼以實現更多功能,最後再編寫程式碼使之實現。

TDD看起來非常像一個環,首先是要不斷調整測試程式碼,然後是編碼,改進,最後直至完成。先實現測試部分的做法會使你自然養成把問題放在首位的思維習慣。當真正去構建程式碼時,就不得不想清楚該如何把設計做好;比方說,該方法有何返回值?當遇到異常時該怎麼辦?諸如此類。

以這樣的方式進行開發,意味著要想出不同的程式碼實現路徑,並在測試中進行實踐。這樣做可使你少走前人的彎路:陷入一個問題後寫出毫不相關的解決方案。

該過程可描述如下:

  • 寫出一個缺陷單元測試
  • 使該單元測試通過
  • 重構

與敏捷開發結合

TDD與敏捷開發並行不悖甚至1+1遠大於2,這裡指的是程式碼質量而不是數量。

“這意味著結對雙方都會參與其中,著重於當前工作,然後在每個環節進行互檢。”

然而在結對程式設計時TDD是單獨進行的。如果能把雙方的開發流程混合好,互相都能理解就最好不過了。例如,其中一人寫出單元測試,當測試通過後,另外一人可以編寫不同的測試以之通過。

任何時候結對雙方都可以互換角色,每半天或天。這意味著結對雙方都會參與其中,每人都把精力放在當前任務上,然後在每個環節進行交叉互檢。這難道不是一個雙贏的做法嗎?

TDD也可以是行為驅動開發過程中的組成部分,同樣地,首先寫出測試,只不過這裡指的是接受測試。這樣有助於把工作從頭到尾都保持規範。

單元測試語法

進行單元測試時,使用到的Python方法如下:

  • assert: 編寫個人宣告的基本方式
  • assertEqual(a,b):檢查a和b的是否等價
  • assertNotEqual(a,b):檢查a和b的是否非等價
  • assertIn(a,b):檢查是否存在b中
  • assertNotIn(a,b): 檢查是否不存在b中
  • assertFalse(a):檢查a的值是否為False
  • assertTrue(a):檢查a的值是否為Ture
  • assertIsInstance(a,TYPE):檢查a是否為“TYPE”型別
  • assertRaises(ERROR,a,args):以引數args呼叫a時,檢查是否會出現ERROR

以上是實際當中使用頻率最高的方法,更多的方法請查閱Python單元測試文件

安裝並使用Python Nose

進行下面的練習前,請把nosetest測試執行包安裝好。使用標準pip語句進行安裝是最直接的做法。此外在專案中使用VirtualEnv(Python虛擬環境)也是不錯的做法,因為它可確保所有包在不同專案中是獨立的。假如對pip或VirtualEnv瞭解不多,不妨先查閱相關文件:VirtualEnv,PIP

pip語句十分簡潔:

安裝完成後,可以執行單個測試檔案

或者可以直接執行資料夾中的檔案組

這裡要注意的是每個測試方法都應以“test_”為開頭,這樣nosetest執行機才能正確識別出目標測試檔案。

可選引數

下面介紹幾個有用的命令列引數:

  • -v:輸出更多資訊,包括正在執行的測試檔名;
  • -s或-nocapture:進行PRINT語句輸出,一般情況下這是隱藏的。開啟後可方便除錯;
  • –nologcapture:輸出日誌資訊;
  • –rednose:一個可選外掛,請點選這裡下載,輸出帶顏色的輸出資訊;
  • –tags=TAGS:指定要執行的測試檔案,而不是整個測試檔案組。

例項分析和測試驅動方法

接下來結合一個簡單的計算器類例子例如相加/相減,來講述Python單元測試和TDD概念。對於add相加功能,會嘗試編寫一個缺陷測試。

在一個空白專案中,首先建立兩個python包app和test。然後在每個檔案裡建立兩個名為_init_.py空白檔案。這是Phthon工程的標準結構,完成後可以擁有一個可匯入的檔案結構。如果需要了解更多有關文件架構的資訊,請查閱Python包說明文件。 在測試目錄裡建立一個test_calulator.py檔案,其程式碼如下:

說明:

  • 首先,從Python標準庫裡匯入標準的unittest模組
  • 接著,建立一個含有不同測試用例的類
  • 最後,建立以“test_”為開頭的一個測試方法

完成後可著手編寫測試程式碼了。執行方法前要先對計算器進行初始化,初始化完成後便可呼叫add方法,並把結果存入變數result中。完成後,使用unittest的assertEqual方法來確保add方法正常執行。

現在可以啟動nosetest來執行測試檔案了。程式碼如下:

標準的Python檔案執行方式為$ python test_calculator.py,相比之下本文使用的nosetests方法功能更豐富,例如可以執行目錄中的全部測試檔案。

執行後可見出錯的原因是沒有匯入Caculator。因為還沒有建立呢!建立的方法是在app目錄下建立calculator.py檔案,然後匯入:

把Caculator構建好之後,再次執行看會出現什麼結果:

很明顯,add方法返回了錯誤的值,因為還沒有為它指定行為。幸好nosetest會指出出錯的位置,方便進行修改。稍作改動後,測試便可通過了:

雖然通過了,但是圍繞該方法還可以做更多的工作。

沉迷於某個案例很容易造成短視

如果進行非數字型資料相加會導致什麼後果呢?事實上Python是允許字串或其它型別進行相加的,但在我們的例子裡不允許。接著嘗試就這個例子加入另一個缺陷測試,然後使用assertRaises方法來判斷是否有異常丟擲:

以上程式碼中,檢查了是否引起了ValueError錯誤,其實還可以進行更多的檢測,不過在這裡不作深入講述。此外,setup()方法用於推入計算物件。下面再看看nosetest會反饋什麼資訊:

顯然nosetests告訴我們ValueError沒有被丟擲。現在我們有了一個新的缺陷測試,接著嘗試編碼進行解決:

程式碼中使用了isinstance方法是為了確保輸入的是數字型資料。

由於兩個變數的型別有多種組合,為了進行完整的測試,所以需要把可能出現的組合進行統籌並進行處理:

至此我們可以執行所有的測試了,所要實現的需求也都滿足了。

其它的單元測試包

py.test

pytest的作用與nosetest類似,不過可以在單獨的區域裡輸出資訊,這意味著能夠使我們很快地看清楚命令列中出現的列印資訊。這對於只執行單個測試的情況是很有用的。

安裝pytest的方式與nosetest差不多,命令是$ pip install pytes。執行的命令是$ pip install pytes或者指定要執行的測試檔案$ py.test test/calculator_tests.py。

pytest執行後的結果如下。注:只有程式碼含有錯誤或異常的情況下,pytest才會進行輸出。

單元測試

如果不想安裝額外的包並想保持一個純淨的標準庫結構,使用Python內建的unittest單元測試包是不錯的選擇。其使用方法如下:

使用python calculator_tests.py執行後,看會得到什麼結果:

使用PDB進行除錯

以TDD方式開發,經常會遇到來自程式碼或測試的問題。有時這些錯誤又是比較隱蔽的。因此,需要配合使用高明的除錯技術。

以TDD方式進行開發出現問題時可能難以發現

幸運地,有不少的辦法來解決這些問題。其中最簡單的方式是透過增添print語句實現“斷點”輸出。

結合print語句進行除錯

加法通過後,可以嘗試進行減法除錯。把app/calculator.py中的add部分程式碼作如下改動:

這裡不妨嘗試使用print語句進行輸出,來監視值是怎樣變化的。

現在可以使用nosetest來執行並檢視結果,可見這樣的工整輸出結構,對除錯是十分有幫助的。

PDB進階除錯

如果遇到更復雜的除錯環節,僅僅依靠print語句是不夠的。其中最經常使用的進階除錯工具是pdb(Python Debugger)。該工具包含在標準庫中,使用的時候只需加入一行程式碼到“斷點”位置。請看下面的程式碼:

請注意,如果使用nosetest執行測試,請務必使用-s標記,否則nosetest會繼續對輸出進行抓取,這樣會使pdb無法正常執行。如果是使用unittest或pytest則無需這樣做。

如果測試停止並有pdb提示,請使用list命令來進行當前程式碼定位。

出現提示後是可以進行互動操作的,比方說想在這個時候檢閱x和y的值:

如果想了解更多命令,可以鍵入help來檢視。經常使用的命令如下所示:

  • n: 步進到下個執行
  • list: 顯示當前位置
  • args: 顯示在當前執行點上用到的變數
  • continue:執行程式碼直至結束
  • jump <line number>: 執行並跳轉到行號位置
  • quit/exit:停止pdb

寫在最後

TDD模式十分有趣同時能幫助提高程式碼質量。不論是大型團隊還是個人開發,TDD都可運用其中。此外,成功的缺陷測試設計是非常有滿足感的。所以,不妨從今天起嘗試把TDD引入到日常工作中,親身體驗試驗前後會有什麼變化。

相關文章