嵌入式,真的不需要單元測(cè)試?
前言
嵌入式行業(yè)摸爬滾打這幾年,遇見(jiàn)有規(guī)范單元測(cè)試的項(xiàng)目寥寥無(wú)幾。歸根到底,無(wú)非是公司希望快速迭代出產(chǎn)品,有問(wèn)題等客戶(hù)反饋再說(shuō)。當(dāng)然,也有人認(rèn)為是嵌入式行業(yè)都是小而美的產(chǎn)品居多,沒(méi)有到一定量級(jí)之前,玩不起單元測(cè)試這種配置。正如做個(gè)蛋炒飯,并不需要安排主廚、二廚一般。
不過(guò)出于對(duì)代碼穩(wěn)定性的追求,我認(rèn)為還是應(yīng)該著手了解一下單元測(cè)試的。畢竟,這是有效提高代碼說(shuō)服力的方式之一。
相信沒(méi)有真正體驗(yàn)過(guò)單元測(cè)試好處的讀者一看到"單元測(cè)試"這幾個(gè)字,可能會(huì)出現(xiàn)以下兩種反應(yīng)之一:
-
由于沒(méi)有單元測(cè)試的經(jīng)驗(yàn),因此對(duì)采用這一方法去保證軟件質(zhì)量很好奇,也迫切地想要了解這一方法在項(xiàng)目中的實(shí)施
-
曾經(jīng)使用單元測(cè)試但效果不好,因?yàn)樵谇度胧叫袠I(yè),時(shí)常要跟硬件打交道,單元測(cè)試很難檢測(cè)硬件問(wèn)題,所以往往一看到"單元測(cè)試"這幾個(gè)字的反應(yīng)就是"沒(méi)用"
如果讀者是第一種反應(yīng)那很好,本文就是科普單元測(cè)試的基本要點(diǎn)。如果讀者是第二種反應(yīng),那可能是對(duì)單元測(cè)試存在偏見(jiàn),本系列文章也會(huì)介紹mock測(cè)試、錯(cuò)誤注入等方式,使單元測(cè)試也能合理使用于嵌入式行業(yè)。
01
單元測(cè)試真的"無(wú)用"?
造成"單元測(cè)試無(wú)用論"的第一個(gè)原因是,運(yùn)用這一方法的時(shí)機(jī)不恰當(dāng)。不少項(xiàng)目在一開(kāi)始真正關(guān)心質(zhì)量的人很少,更談不上采用一整套的方法論去保證質(zhì)量了。產(chǎn)品在開(kāi)發(fā)出來(lái)后發(fā)現(xiàn)到處存在問(wèn)題,只會(huì)拆西墻補(bǔ)東墻根本就不能阻止問(wèn)題一而再,再而三地出現(xiàn)。于是,開(kāi)始想起單元測(cè)試。一聲令下,整個(gè)項(xiàng)目開(kāi)始做單元測(cè)試。單元測(cè)試以模塊為單位,需要先把項(xiàng)目拆分出來(lái)。如果你的項(xiàng)目代碼整體耦合程度較高的話(huà),單元測(cè)試根本無(wú)從說(shuō)起,拆分的工作會(huì)讓你痛苦不已。
單元測(cè)試是一項(xiàng)耗時(shí)的工作,但管理者卻往往希望在短期內(nèi)看到效果?;蛘邌卧獪y(cè)試還沒(méi)做到位管理層就等不及了,催你馬上開(kāi)始下一步的開(kāi)發(fā),結(jié)果只能是前功盡棄。正確的做法是:在項(xiàng)目的開(kāi)始之初就引入單元測(cè)試。對(duì)于以前沒(méi)有部署單元測(cè)試的項(xiàng)目,先只對(duì)新增加的、相對(duì)獨(dú)立的模塊做單元測(cè)試、并逐漸覆蓋老代碼。
第二個(gè)導(dǎo)致"單元測(cè)試無(wú)用論"的原因是,方法沒(méi)有運(yùn)用到位。要保證單元測(cè)試的有效性一定要引入另一個(gè)概念--代碼覆蓋。關(guān)于代碼覆蓋,我以后會(huì)另外再寫(xiě)一篇文章介紹。只有將單元測(cè)試和代碼覆蓋結(jié)合在一起,綜合使用才能保證單元測(cè)試的效果。
02
最原始的"單元測(cè)試"
這里給讀者展示一下,不使用任何單元測(cè)試框架時(shí),是怎么做單元測(cè)試的。
下面簡(jiǎn)單以linux內(nèi)核鏈表為例:
struct list_head {
struct list_head *next, *prev;
};
/*定義一個(gè)結(jié)構(gòu)體,只含有表示前驅(qū)和后繼的指針,它就是我們的主角了*/
#define LIST_HEAD_INIT(name) { &(name), &(name) }
/*靜態(tài)初始化*/
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
/*動(dòng)態(tài)初始化*/
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
/*插入操作*/
/*刪除操作*/
/*合并操作*/
...
完整代碼很長(zhǎng),這里沒(méi)有必要全部貼出,能起演示作用就足夠了。
現(xiàn)在就以INIT_LIST_HEAD函數(shù)為例,來(lái)考慮如何為這個(gè)函數(shù)設(shè)計(jì)測(cè)試用例。INIT_LIST_HEAD函數(shù)的實(shí)現(xiàn)是如此的簡(jiǎn)單,以至于很容易讓人覺(jué)得為它設(shè)計(jì)單元測(cè)試是多余的。但是,從單元測(cè)試的角度看,只要不存在可行性問(wèn)題就不應(yīng)考慮因?yàn)楹?jiǎn)單而不對(duì)其進(jìn)行驗(yàn)證。而且,放棄對(duì)之進(jìn)行驗(yàn)證,以后會(huì)降低代碼覆蓋率。
做單元測(cè)試需要通過(guò)編寫(xiě)程序的方式來(lái)完成,所編寫(xiě)的用于測(cè)試的代碼又稱(chēng)為單元測(cè)試用例。
下面我們來(lái)簡(jiǎn)單實(shí)現(xiàn)一個(gè)INIT_LIST_HEAD函數(shù)的測(cè)試用例:
int main(int argc,char **argv)
{
struct list_head list;
/*避免函數(shù)沒(méi)有使用參數(shù)而引發(fā)waining*/
UNUSED(argc);
UNUSED(argv);
list.prev = (struct list_head*)0xaaaa;
list.next = (struct list_head*)0xbbbb;
INIT_LIST_HEAD(list);
/*檢查前指針*/
if(list.prev != list){
return -1;
}
/*檢查后指針*/
if(list.next != list){
return -1;
}
return 0;
}
這應(yīng)該是史上最簡(jiǎn)單的測(cè)試用例,功能非常簡(jiǎn)單,首先是故意將list結(jié)構(gòu)體中的各個(gè)指針變量初始化為一個(gè)隨機(jī)值。然后在調(diào)用完INIT_LIST_HEAD函數(shù)之后,檢查各成員是否被初始化為了list,以判斷INIT_LIST_HEAD函數(shù)是否正常工作了。注意:這個(gè)測(cè)試程序還有一個(gè)約定,返回-1代表測(cè)試失敗,返回0表示測(cè)試成功。
這個(gè)測(cè)試用例是基于我們對(duì)INIT_LIST_HEAD函數(shù)有足夠的了解之后編寫(xiě)的,這種測(cè)試方法在軟件測(cè)試領(lǐng)域有個(gè)正兒八經(jīng)的名字,叫白盒測(cè)試。
相信通過(guò)這個(gè)NIT_LIST_HEAD函數(shù)的測(cè)試用例,你已經(jīng)初步建立起了對(duì)單元測(cè)試的印象。但是千萬(wàn)不要以為單元測(cè)試僅此而已,這是我刻意簡(jiǎn)化的結(jié)果。要完整地掌握單元測(cè)試,還要好好學(xué)習(xí)一段時(shí)間。
目前,對(duì)于這個(gè)小小的單元測(cè)試案例,還有很多的不足,下面簡(jiǎn)單羅列了幾項(xiàng):
-
如果對(duì)于每一次檢查都采取直接寫(xiě)if語(yǔ)句的形式,將造成大量的冗余代碼,并且測(cè)試用例的編寫(xiě)效率也會(huì)很低。
-
通過(guò)觀察程序是否返回0或者是-1的方式來(lái)判斷所有的測(cè)試是否通過(guò)并不直觀,一旦出錯(cuò)也無(wú)法馬上判斷是那一步測(cè)試出了問(wèn)題。毫無(wú)疑問(wèn),我們需要更加直觀的方式來(lái)展示哪一步成功或者哪一步失敗。
-
一份嚴(yán)謹(jǐn)?shù)臏y(cè)試用例,會(huì)有大量的判定。如果一個(gè)測(cè)試程序存在100次判定,其中出現(xiàn)了3次失敗,那最終顯示一個(gè)百分比的測(cè)試通過(guò)率會(huì)比較直觀,比如可以顯示97%的測(cè)試成功了。
后面會(huì)進(jìn)一步介紹如何自己搭建一個(gè)簡(jiǎn)單實(shí)用的單元測(cè)試框架,來(lái)解決上面這些問(wèn)題。也會(huì)陸續(xù)展開(kāi)介紹mock方法、打樁、錯(cuò)誤注入、代碼覆蓋、動(dòng)態(tài)分析、靜態(tài)分析、性能優(yōu)化等內(nèi)容。
03
總結(jié)
正如很多其他技巧,比如打桌球、滑雪一樣,測(cè)試驅(qū)動(dòng)開(kāi)發(fā)也要花費(fèi)相當(dāng)長(zhǎng)時(shí)間來(lái)練習(xí)。許多開(kāi)發(fā)者已經(jīng)接受了這種技術(shù),而且再也不想回到從前“后期調(diào)試式編程”的方式去了。
它會(huì)使你的代碼:
?產(chǎn)生的bug更少
調(diào)試時(shí)間更短
完全可以通過(guò)提交你的單元測(cè)試案例,來(lái)證明你的項(xiàng)目可靠性。
猜你喜歡
干貨 | 函數(shù)宏的三種封裝方式
你寫(xiě)的程序很健壯?不妨測(cè)一下?
1024G 嵌入式資源大放送!包括但不限于C/C++、單片機(jī)、Linux等。在公眾號(hào)聊天界面回復(fù)1024,即可免費(fèi)獲?。?/span>
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!