行為驅動開發在 Python 開發測試中的應用

發表於2015-10-28

行為驅動開發 (BDD) 簡介

行為驅動開發是什麼?

說到行為驅動開發(BDD),無可避免的要提到敏捷裡面的測試驅動開發(TDD),TDD 的主要思想是“程式碼即文件”,其倡導的流程是根據設計編寫測試-> 實現設計的功能 -> 用測試程式碼驗證 -> 重構實現程式碼 -> 改善設計 -> 再次回到根據改善的設計編寫測試。

圖 1 基於 TDD 的專案開發流程

BDD,即行為驅動程式設計,是 TDD 的一個改進版本,BDD 本質上也是 TDD,但是比 TDD 要更加自然一些,更 DSL 化。系統業務專家以及測試開發人員一起合作,分析出軟體的需求,然後像故事一樣描述出一個個具體可執行的 Behavior;之後開發者負責填充這些故事的內容,測試人員負責檢驗故事結果的正確性。一個比較簡單的案例即使用者登入場景測試:

圖 2 使用者登入場景測試

作為普通使用者,我在登入網站時,就牽扯到使用者名稱和密碼的校驗。同樣的故事就因此會有不同的場景發生:

圖 3 非法使用者登入場景

如果是一個合法使用者來發出請求,那麼又將是另外一個場景:

圖 4 合法使用者登入場景

有了這些場景描述,測試人員可以通過一些 BDD 測試框架將上面的故事轉成測試程式碼, 開發者實現產品功能,保證所有的測試案例成功通過。

為什麼要進行行為驅動開發?

如上可以看出,行為驅動開發的根基是一種“通用語言”,類似於用一個最小化的詞彙表來定義,其中這些詞彙具有準確無誤的表達能力和一致的含義。

as 指明系統行為是為什麼角色而定義的。

In order to 和 I want 定義了行為的範圍,表明了想達到的目的,想做的事情。

Given 給出了準備要測試的物件和測試環境。

When 呼叫要測試的業務方法。

Then 對測試結果進行驗證

這種通用語言同時被需求,開發,測試和客戶等專案相關人員用來定義系統的行為。這樣子大家看到的描述一致,內容一致,最大程度的交付出符合使用者期望的產品,避免表達不一致帶來的問題。

圖 5 使用行為驅動開發的開發狀態圖

古人云“工欲善其事,必先利其器”,因此在詳細介紹基於 BDD 思想的 Python 開發測試過程之前,我們先來看一下如何搭建一個有效易用且穩定的測試框架。

Python BDD 測試框架的搭建

Lettuce 簡介

為什麼在這裡要提到 Lettuce 呢?看它的官網介紹,這個框架在 Python 語言的測試應用堪比 Ruby 語言領域的 Cucumber,它可以對 Python 專案進行自動化測試,值得注意的是這裡的測試用例是純文字的,具有很好的可讀性。這樣的話,即使是不懂程式設計的人也可以站在系統或使用者的角度來寫測試用例,並且在專案開發過程中,這個自動化測試用例可以很容易的修改、擴充,開發人員也能很快的調整,從而保證測試用例順利通過。

搭建 Lettuce 測試框架

前面已經說了很多 Lettuce 的作用和好處,那麼下面我們來看下如何在自己的環境上搭建這樣一個測試框架。接下來筆者將給出 Redhat Linux + Python2.6 這樣的環境下的搭建步驟。

首先,Python 上安裝 package 的時候,一般都會建立一個 Virtualenv。

如何來理解 Virtualenv 呢?Virtualenv是一個 Python 環境管理工具,它可以建立獨立的 Python 環境,多個 Python 相互獨立,互不影響。簡單的來說,你可以將它看做是一個個隔離的沙盒,每一個沙盒之間相對獨立,保持著一個相對獨立乾淨的環境。可能這樣說,你還沒有理解到它的實際好處,試想一下,你之前有一個專案 A 需要依賴 Django1.2.5, 而現在又有一個新的專案 B,它需要依賴 Django1.3,這時除了把它們放到不同的沙盒裡面,你還可以重新搭一套 Python 環境,甚至於需要換臺機器,這樣沙盒的好處就顯而易見的了,應該沒有人會不喜歡這麼簡單的方法。好的,既然喜歡,那麼如何來安裝並啟用呢?

1.install virtualenv

easy_install virtualenv-1.6.4.tar.gz

2.create virtual environment (vevn)

virtualev [vevn_folder] –no-site-packages

3.enter into [venv_folder]

source bin/active

僅需要以上 3 個命令就可以安裝並啟用 Virtualenv 了,值得注意的是,這個 Virtualenv 看上去就是個資料夾 [vevn_folder],可以很容易地刪除、重建等等,但在每次使用前都需要將它啟用。

另外我們這裡使用了 easy_install 命令,這是 setuptools 帶的一個命令,其中 setuptools 是一個非常好的應用於 Python 的安裝工具,使用它,可以使用如上很簡單的命令來安裝所需要的 package。如下就是安裝 setuptools 和 Virtualenv 的步驟圖:

圖 6 安裝 setuptools

圖 7 安裝並啟用 Virtualenv

其次,我們需要清楚的是,Lettuce 安裝包有很多依賴,因此在安裝之前我們得將所有的依賴包裝好。

我們可以從網站上下載這些需要的包到本地,當然也可以直接用 setuptools 來安裝,如果本地沒有安裝包,它會自動下載 package 的最新版本來安裝。不管怎麼樣,需要注意的是,由於這些包之間存在依賴關係,因此需要按照特定順序來進行安裝。以下就是所有依賴包的順序安裝:

表 1 Lettuce 順序安裝包
包名 下載連結及安裝方式 簡要說明
Nose url:http://code.google.com/p/python-nose/
easy_install nose-1.1.2.tar.gz
Nose 是最流行的針對 Python 的測試庫之一
Mox url: http://code.google.com/p/pymox/
easy_install mox-0.5.3.tar.gz
Mox 是 Python 程式語言的單元測試框架
Sphinx url: https://pypi.python.org/pypi/Sphinx/1.0.7
Sphinx 有 4 個依賴包,需要先提前安裝依賴
easy_install docutils-0.8.tar.gz
easy_install Jinja2-2.6.tar.gz
easy_install lxml-2.3.tar.gz
easy_install Pygments-1.4.tar.gz
easy_install Sphinx-1.0.7.tar.gz
Sphinx 常用於生成文件,Lettuce 測試框架的測試結果顯示依賴於它
Lettuce url: https://pypi.python.org/pypi/lettuce/0.1.31
Lettuce 安裝時提示缺少 4 個依賴包,所以先安裝好這幾個包。
easy_install pycrypto-2.3.tar.gz
easy_install paramiko-1.9.0.tar.gz
easy_install Fabric-1.5.3.tar.gz
easy_install Jinja2-2.6.tar.gz
easy_install lettuce-0.1.31.tar.gz
Lettuce 是 Python 常用的 BDD 測試框架

安裝完之後可以做如下的簡單測試來確定是否安裝成功。

在命令列裡直接輸入 Lettuce, 如圖所示,如果顯示類似找不到 features 的提示資訊,那麼恭喜你已經成功安裝上了 Lettuce 測試框架。

圖 8 Lettuce 安裝驗證

到這裡,算是打好了基礎,回頭再看看,安裝時需要注意的有以下 2 個方面:

1. 如果建立的有 Virtualenv,那麼在做任何操作之前必須要先啟用它;

2. 對於 Python 來說,特別是要注意包之間的依賴,如果不清楚,可以先查查,實在不行,用 easy_install 這個工具來安裝,它會自動檢查依賴關係,如果缺失則會自動從網上下載最新的匹配版本並進行安裝,但遇到依賴關係複雜而網路狀況又不好的時候,最好手動下載放到本地目錄,就像本文中用到的包一樣。

基於 BDD 思想的 Python 開發測試過程

接下來這一章,我們希望通過生動詳細的案例來講述測試用例及測試指令碼的編寫和執行過程。比如說我們想自己實現一個計算整數 n 次方的計算器,當然這個程式可能比較簡單,但這裡為了更好的理解 BDD 的思想,我們假設它很複雜,剛開始不知道怎麼實現,我們就只好先寫測試用例,然後一步步的修改程式碼讓所有可能的用例通過。接下來本文將以 4 個迭代詳細描述該過程。

測試用例及測試指令碼的編寫

在寫測試用例時,就參照前面章節所介紹的,用一些簡單無異議的最小詞彙組來描述可能的使用者場景。

迭代 1

先給出一個 n 次方計算器程式的介面 computePow.py,如下所示,我們不知道如何實現它,所以讓計算結果都為-1。

圖 9 計算程式介面定義

接下來先來一條最簡單的測試用例 computePow.feature,如下圖所示,這個測試用例在上面首先說明了這是一個計算 n 次方的 feature,另外還簡單說明了作者,目的等,最重要的是用 3 個步驟很清楚的描述了一個使用者場景,即 1 的 0 次方為 1。

圖 10 測試用例描述

不用再多說,想必大家已經感受到這樣的測試用例的好處,簡單明瞭,易讀性高,資訊量大,便於維護。當然到此為止,這個看上去很清晰明瞭的測試用例只是對使用者來說是這樣的,計算機是如何理解的呢?這就需要我們對這些 step 做轉換,從而使程式正確理解使用者行為。如下所示我們定義了一個解釋指令碼 step.py:

圖 11 解釋指令碼

這個 step.py 需要和前面的測試用例 computePow.feature 結合來看,首先 step.py 裡面定義了 3 個方法,每個方法是和 factorial.feature 檔案裡面的使用者場景的每一個步驟對應,而這個對應規則就是靠方法上面的 @step(…) 所標識的正規表示式來匹配的,其中有正規表示式的地方,如 (\d+),表示是引數帶入,比如在方法 have_the_number 中,上面的匹配規則有兩個 (\d+),表示這裡有兩個數字引數,因此該方法也帶了兩個形式引數 bottomnumber 和 uppernumber。執行時,這兩個引數值將從測試用例對應的 step 中獲取,即 Given I have the bottomnumber 1 and the uppernumber 0,也就是說實際引數將是 1 和 0。另外需要注意的是我們強烈支援程式碼的可讀性,因此這裡 step.py 中的方法名都很容易理解。

好了,至此一條簡單的測試用例已經完成,我們來讓它執行一下,進入到檔案所在目錄,使用命令:lettuce computePow.feature,執行結果如下圖所示。

圖 12 執行測試用例

執行結果顯示,總共執行了一個 feature 和一個 scenario,都執行失敗,scenario 中共有 3 個 step, 其中 2 個成功執行,還有一個失敗,從圖中可以看到失敗的地方以及具體堆疊資訊。

從這裡就可以體會 BDD 的好處,對於測試人員來說能很清楚的明白問題出在哪一步,可以找誰去溝通;而這些錯誤資訊也能很好的幫助開發人員解決問題;另外下面的統計數字也能很清楚地看到測試通過百分比。

既然測試用例跑失敗了,接下來我們就修改程式碼讓用例成功跑通。通過數學計算,我們知道 1 的 0 次方是 1,因此我們修改計算程式 computePow.py,讓它返回 1,修改完之後執行一下,發現這時測試用例已經完全跑過。如下所示:

圖 13 修改後計算程式介面

圖 14 再次執行測試用例

迭代 2

接下來,我們就可以考慮擴充套件測試用例,為讓測試更完備,在這裡我們增加了 3 個計算,分別是 3 的 0 次方為 1,2 的 1 次方為 2 和 5 的 0 次方為 5,新增用例如下所示:

圖 15 測試用例功能擴充套件

執行該用例之後,發現在 Scenario: pow(2, 1) 和 Scenario: pow(5, 1) 出錯,如下所示,該次執行有 2 個 scenario 成功執行,2 個失敗,從執行結果可以很容易地知道是哪一個步驟出錯。

圖 16 測試用例功能擴充套件後執行結果

因此我們修改計算程式 step.py,如下所示:

圖 17 第 3 次修改計算程式介面

再次執行命令 lettuce computePow.feature, 發現我們的這 4 個 Scenario 和 12 個 step 都能成功跑過。

迭代 3

到這裡我們才測試了 4 個簡單用例,接下來我們再增加 2 個稍微複雜些的用例,計算 2 的 3 次方為 8,3 的 2 次方為 9,新增用例如下所示:

圖 18 測試用例第 2 次功能擴充套件

執行該用例之後,發現在新增的 2 個用例處 Scenario: pow(2, 3),Scenario: pow(3, 2) 出錯,因此我們修改計算程式 computePow.py 以讓用例通過,如下所示:

圖 19 第 4 次修改計算程式介面

再次執行命令 lettuce computePow.feature, 發現我們的這 6 個 Scenario 和 18 個 step 都能順利跑過。

迭代 4

至此我們的計算器小程式已經順利跑過 6 個使用者場景,如果再新增用例的話,就可仿照上述 3 個迭代過程來新增,但是這有一個問題,隨著使用者場景增多,我們發現 computePow.feature 變的越來越長,難以管理,仔細觀察一下,發現每個場景的 step 很相似,也就說我們寫了很多重複的描述資訊,針對這一問題,Lettuce 測試框架可以用 scenarios outlines 來解決,如下圖所示:

圖 20 使用 scenarios outlines 描述測試用例

這樣的寫法可以使測試用例在依然能表達豐富的資訊的情況下變的很精簡,且更容易管理和擴充套件。

測試指令碼執行及自動化

測試指令碼如何執行在前一章的例項中已經提到,只需要簡單命令 lettuce computePow.feature 就可以。這樣它就會在當前路徑下搜尋與每一個 step 匹配的指令碼來解釋執行。注意的是,這裡的測試用例和測試指令碼的名字沒有關聯性。如果當前路徑下有多個測試用例檔案,也就是說有很多*.feature 檔案,就可以用 lettuce ./ 或者 lettuce *.feature 來批量執行。

到此想必大家已經對 BDD 在 Python 領域的開發測試過程以及測試框架搭建已經有一個形象的理解,在前面的例項中也體會到了這種方法的好處。最後要提到的自動化實質上是這種方法的另外一個好處,假設我們的計算器程式將分佈於不同的 OS 上,需要對這些環境都進行測試,又或者說我們的計算器程式需要升級用 Python 的更高版本等等,這樣我們就要將測試環境和開發環境分離,在我們的案例中,可以看到測試用例和計算器程式是彼此獨立的,這樣我們只用做很少的工作,譬如寫一些簡單的指令碼,然後在測試環境上通過 set 或 export 設定遠端開發環境的執行變數就能很輕易的讓它自動化執行。

總結

本文首先對行為驅動開發做了一個簡介,接下來通過例項圖文並茂地講述瞭如何將這一思想靈活運用到 Python 領域,同時中間還穿插了測試框架 Lettuce 的安裝及執行。在此,希望本文能起到一個拋磚引玉的作用,增進讀者對 Python 和 BDD 的瞭解,有興趣的讀者可以進行深入研究,將其靈活應用到專案實踐中。

相關文章