2017年4月1日 星期六

TDD(Test Driven Development)

www.throwtheswitch.org 設計了一套用來作單元測試的工具。對於使用 TDD(Test Driven Development)開發流程的人員,挺方便的,其中共包含四套工具,分別是 Unity,CMock,CExpection,Ceedling。

摘錄官方說明如下:
  • Unity - Curiously Powerful Unit Testing in C for C.
  • CMock - Automagical generation of stubs and mocks for Unity Tests.
  • CException - Lightweight exception handling for C.
  • Ceedling - Test build management.
以下將針對這幾個工具稍做介紹。


Unity
此模組是一個純 C 語言的單元測試架構,提供了許多測試函數,由函數名稱可以清楚的知道其用途,例如:
TEST_ASSERT_TRUE(condition) // 預期 condition = true, 否則 assert
TEST_ASSERT_FALSE(condition) // 預期 condition = false, 否則 assert
TEST_ASSERT(condition) // 與 TEST_ASSERT_TRUE() 相同
TEST_ASSERT_EQUAL_INT(expected, actual) // 若 actual != expected 就 assert
TEST_ASSERT_EQUAL_MEMORY(expected, actual, len) // 比較兩塊記憶體內容是否相同,若不同就 assert
此模組好處是簡單易用,下面是一個使用範例 
假設程式碼實作如下:
int a = 1;
TEST_ASSERT_EQUAL_INT(2, a);
TEST_ASSERT_EQUAL_HEX8(5, a);
TEST_ASSERT_EQUAL_UINT16(0x8000, a);
則輸出如下:
TestMyModule.c:15:test_One:FAIL:Expected 2 was 1
TestMyModule.c:23:test_Two:FAIL:Expected 0x05 was 0x01
TestMyModule.c:31:test_Three:FAIL:Expected 32768 was 1
詳細用法可參考 https://github.com/ThrowTheSwitch/Unity

CMock
如果有個工具能自動產生測試案例,直接幫忙使用各種 TEST_ASSERT_xxx協助檢查輸入輸出就好了。 
幸運的是 CMock 就是一個這樣的工具。CMock會分析 header file,自動幫忙生成一些檢查值的測試函數,而內部的檢查機制仍是使用 unity 定義的函數。 
摘錄官方說明如下
  • CMock generates pure C code from regular C headers. No special markup required.
  • CMock automatically generates your mock modules from simple headers, saving you from tediously creating your own stubs. 
Jordan Schaenzle 所發表的文章 The mock object approach to test-driven development,詳細地說明了Mock function,摘錄如下:
A mock is a stand-in for a real module. It doesn’t contain any real functionality but rather imitates a module’s interface. When used in a test, a mock intercepts calls between the module under test and the mocked module.
假設 Parse.h 宣告函數如下:
int ParseStuff(char* Cmd);
CMock 會協助生成 MockParse.c 與 MockParse.h ,並產生下列函數:
void ParseStuff_ExpectAndReturn(char* Cmd, int toReturn);
void ParseStuff_IgnoreAndReturn(int toReturn);
void ParseStuff_StubAndCallback(CMOCK_ParseStuff_CALLBACK Callback);
接著撰寫測試程式時,先預告預期會處理的參數與返回值,再呼叫測試程式,如下:
#include "MockParse.h" // 原本是 #include "Parse.h"
ParseStuff_ExpectAndReturn("NeatStuff", 1);
MyFunctionInvokeParseStuffSomeWhere(); // 內部會呼叫 ParseStuff()
原本呼叫 ParseStuff() 的地方,現在會變成先呼叫 ParseStuff_ExpectAndReturn()然後再呼叫 ParseStuff(),這樣的改變並不需要改變原本的程式碼,CMock 所產生的函數會協助我們作這個改變。 
參考資料:http://www.throwtheswitch.org/cmock

CException
此模組透過兩個檔案提供 expection hander,CException.h and CException.c,只要平台上能夠支援 setjmp/longjmp,就可以使用此模組。 
使用方式為 Try Catch Throw。 
實際使用範例可參考:http://www.throwtheswitch.org/cexception

Ceedling
此模組會使用 Unity,CMock 與 CExpection,可以與 Eclipse 或 sublime 等編輯器結合,並且可以針對不同的開發環境產生不同的執行程式(release binaries in target or test binaries in PC)。 
Matt Chernosky 提供了非常詳細的使用步驟
http://www.electronvector.com/blog/add-unit-tests-to-your-current-project-with-ceedling 
至於是否需要使用 Ceedling,或是只使用 Unity即可?可以根據此篇作個選擇。
http://www.throwtheswitch.org/decide-o-tron

參考資料:
https://www.ibm.com/developerworks/cn/linux/l-tdd/ 
https://www.ibm.com/developerworks/cn/linux/l-cppunit/index.html
https://www.slideshare.net/hugolu/ss-31191146
http://m.sanmin.com.tw/product/index/99o155l8n108m88k101b69z108k121memgqoe1881rnk