2013年9月26日 星期四

Round 4: Forget main; unit testing is better

第三輪結束時,我們找到一個議題:讓test code脫離 main函數存在。按照round 3的做法,test code寄生在main之下,則innerProduct函數的test code可如下:




第四輪將分離這些test code。

U步驟:檢視test code,可了解做的是針對innerProduct函數的局部測試,分別為可計算內積與不可計算內積的兩種情形。我們採取的方式是將結果輸出到console,然後自行判讀執行結果是否符合期望。用同樣的方法做更多局部測試,將出現下面的現象:
  • 當我們做的局部測試更多時,console輸出將會越來越多,不易判讀結果是否符合期望。
  • 為了省寫些test code,你可能會重複使用某些變數,例如前列中的向量u出現在兩個測試裡。你必須很確定u第一次被使用後,仍然有原來的值,否則第二次測試的test code本身就很難說是對的了。
根據測試分類,我們所做的測試屬於單元測試(unit testing),每一個目的不同的測試則稱為一個test case,所以前述test code含兩個 test case。

執行一個單元測試的test case,具備下列四步驟:
  1. 建立測試用資料
  2. 呼叫待測函數,得到結果值
  3. 比對預期值與結果值
  4. 撤掉測試用資料
C++程式單元測試框架如CppUnit、CppUnitLite等提供對test case依上述四步驟自動執行機制必要的library(例如第三步驟的比對),並提供組織test case的具體方法。使用後,將能一舉解決這些問題。 我將使用簡單易用的CppUnitLite

SP4:以CppUnitLite重作test case。

SP2與SP4二選一,我們將先解SP4。


D步驟:將SP4分割成兩個task:

T9:安裝CppUnitLite。
T10:搬移(與新增)test cases。

C步驟
依序進行T9(參考資料)與T10。完成T10後,原先在main的test code改變如下:

...
#include "C:\Program Files (x86)\Dev-Cpp\MinGW64\include\cppunitlite\TestHarness.h"
...

int main(int argc, char** argv) {
TestResult tr;
TestRegistry::runAllTests(tr);
/* 
... what main should do here
*/
return 0;
}

TEST (computable, innerProduct){
  double a[2]={0,1};
  double b[2]={1,0};
  vector u(2,a);
  vector v(2,b);
  double prod;
  CHECK_EQUAL(true,innerProduct(prod,u,v));
  DOUBLES_EQUAL(0,prod,0.00001);
}

TEST (dimension_error, innerProduct){
  double a[2]={0,1};
  double c[3]={1,2,3};
  vector u(2,a);
  vector w(3,c);
  double prod;
  CHECK_EQUAL (true,innerProduct(prod,u,w))
}

main 中兩行紅色的code使用CppUnitLite中的API,執行所有test case(這裡只有兩個)。

蓄意讓第二個test case驗證失敗: 向量u的 dimension為2,向量w的 dimension為3,所以innerProduct還傳的實際值(return value)為false,但我將期望值設為true執行後,console畫面如下:



看畫面第一列,我們的得到驗證失敗的原因(Failure: "expected true but was false")、發生的地方(line 123 in main.cpp)。值得注意的是第一個test case驗證通過,故無任何訊息。最後總計失敗的次數,共計一次

比較原test code執行後console畫面,好壞立見:



console上每一列輸出,都需要靠你自己判讀是否正確。如果判定為有錯,你得自己尋找錯誤發生在哪裡。想像你跑了100個測試,這個判讀/尋找的任務看起來還挺辛苦且易錯的!

L步驟:OK,test code與main函數已分開再次以programmer的觀點進行回顧。現在,這個功能簡單的程式達到120 Lines的規模。在裡面進行修改、新增code,經常需滾動IDE編輯視窗,programmer犯錯的機率升高。此外,啟動unit testing的API仍然與佔據main函數,需與main該做的事互相切換。

© Y C Cheng, 2013. All rights reserved.

沒有留言:

張貼留言