轉載時請表明出處
作者聯(lián)系方式:liuyuxin0829@qq.com
一、前言
??在大學的時候翼岁,寫代碼隨心所欲,想到什么就寫什么,只顧實現(xiàn)功能,也不會去驗證代碼的可行性和穩(wěn)定性娜扇,往往都會在在后續(xù)的使用過程中出現(xiàn)各種各樣的問題,然后再去捉蟲栅组,這樣寫出來的代碼質量差雀瓢,在后期又耗費大量的時間修復舊代碼bug。
??參加工作后笑窜,接觸到了單元測試致燥,在第一個月的考核項目(智能家居控制面板)中,通過請教同事和參考AWTK源碼中的單元測試代碼排截,磕磕絆絆得寫了一些單元測試,但由于沒有設計好項目基礎框架辐益,業(yè)務邏輯和用戶界面沒有完全分離断傲,因此只做了文件讀寫模塊和網絡通信模塊的單元測試。剛好在本周的培訓內容中又詳細講了單元測試的FIRST原則智政,以下記錄了單元測試的學習感悟认罩,并使用Google公司開源的C/C++單元測試框架——GTest進進行單元測試。
AWTK是ZLG開源的GUI框架:https://github.com/zlgopen/awtkS
二续捂、什么是單元測試垦垂?
??軟件測試包括單元測試、集成測試牙瓢、系統(tǒng)測試劫拗。
- 單元測試:對軟件設計的最小單位進行正確性測試,以檢驗程序單元是否滿足功能矾克、性能页慷、接口、設計規(guī)約等要求。
- 集成測試:將各個程序單元進行有序的酒繁、遞增的組合進行測試滓彰,以檢驗各個程序單元的配合情況。
- 系統(tǒng)測試:對集合了應用軟件州袒、系統(tǒng)軟件揭绑、硬件的產品進行測試,以驗證產品在實際應用中的功能郎哭、性能等特性他匪。
??根據傳統(tǒng)的開發(fā)模型,如瀑布模型彰居,軟件開發(fā)過程和軟件測試活動的關系可以反映為經典的軟件測試V模型人诚纸,如下圖:
??其中單元測試中的單元指軟件中承擔單一職責的單位,通常在程序中體現(xiàn)為一個函數(shù)陈惰、一個文件畦徘、一個類、一個模塊等抬闯。單元測試都是以自動化方式執(zhí)行井辆,所以在大量回歸測試的場景下更能帶來高收益。并且單元測試代碼里會提供函數(shù)的使用示例溶握,因為單元測試的具體表現(xiàn)形式就是對各種函數(shù)以各種不同輸入參數(shù)組合進行調用杯缺。
三、為什么需要單元測試睡榆?
??在我隨心所欲寫程序時萍肆,經常會遇到一些問題,例如:
- 編譯通過胀屿,但是要調試好久才能正常運行塘揣;
- 好不容易調試好了,但是一測試就會出一堆bug宿崭;
- 修復已有的bug亲铡,總會產生新的bug;
- bug難以重現(xiàn)葡兑,又無法定位奖蔓;
- 等等......
??以上問題總結一下,就是“你寫的代碼并不是你想要的結果”讹堤,而單元測試則是能保證“你寫的代碼是你想要的結果”的最有效辦法。
??單元測試階段發(fā)現(xiàn)的bug更容易定位蜕劝,并且由于單元測試自動化的特點檀头,更加方便重現(xiàn)bug轰异。在單元測試階段發(fā)現(xiàn)bug,立即修復嗤朴,不會將各種問題留到最后的系統(tǒng)測試階段股缸,讓代碼更可靠、更容易維護,減少后期測試凯力、維護的成本晨雳。
??總的來講氧吐,單元測試能夠提升代碼質量,減少程序整體的調試時間误墓。
四、如何做好單元測試杖刷?
??單元測試需要遵循FIRST原則:
F-FAST(快速原則):單元測試應該是可以快速運行的颓鲜,在各種測試方法中乐严,單元測試的運行速度是最快的,大型項目的單元測試通常應該在幾分鐘內運行完畢奖慌。
I-Independent(獨立原則):單元測試應該是可以獨立運行的松靡,單元測試用例互相無強依賴简僧,無對外部資源的強依賴。
R-Repeatabl(可重復原則):單元測試應該可以穩(wěn)定重復的運行击困,并且每次運行的結果都是穩(wěn)定可靠的涎劈。
S-Self Validating(自我驗證原則):單元測試應該是用例自動進行驗證的,不能依賴人工驗證阅茶。
T-Timely(及時原則):單元測試必須及時進行編寫蛛枚,更新和維護,以保證用例可以隨著業(yè)務代碼的變化動態(tài)的保障質量脸哀。
??下面會基于GTest框架來說明這五個原則蹦浦。
五、基于GTest進行單元測試
1撞蜂、快速了解GTest
??GTest全稱GoogleTest盲镶,是Google公司發(fā)布的一款非常優(yōu)秀的開源C/C++單元測試框架,已被應用于多個開源項目及Google內部項目中蝌诡,包括ChromeWeb瀏覽器溉贿、LLVM編譯器框架等。
??下載或克隆源碼后浦旱,可以看見目錄結構如下圖宇色,通常我們進行單元測試時需要用到目錄是include和src。配置工程需要做以下三件事:
包含目錄:[GTest目錄名]\googletest\include; [GTest目錄名]\googletest;
添加源文件:[GTest目錄名]\googletest\src\gtest-all.cc;
包含頭文件:#include<gtest/gtest.h>
??進行單元測試前我們需要了解兩個概念:測試用戶颁湖、測試用例集宣蠕。
測試用例:為了驗證代碼的行為與預期是否相符而進行的一系列活動,在單元測試中甥捺,這一系列的活動依靠代碼來完成抢蚀。
測試用例集:多個相似或相關的測試用例的集合,是為了方便我們對測試用例進行管理而產生的一個概念镰禾。通俗一點皿曲,測試用例集就是對測試用例進行分組。
TEST(IsLeapYearTest, leapYear) /* 用例集IsLeapYearTest吴侦,用例leapYear */
{
EXPECT_TRUE(IsLeapYear(2000)); /* 測試IsLeapYear函數(shù)谷饿,傳入參數(shù)2000 */
EXPECT_TRUE(IsLeapYear(1996));
}
??寫好測試用例后,需要運行測試用例妈倔,代碼如下:
int main(int argc, char** argv)
{
testing::FLAGS_gtest_filter = “*”; /* 選擇需要運行的用例 */
testing::InitGoogleMock(&argc, argv); /* 初始化測試框架 */
return RUN_ALL_TESTS(); /* 運行所選測試用例 */
}
??寫好測試用例后,GTest中可以用一下方式表示測試用例:
“用例集.用例”绸贡,例如: “IsLeapYearTest. leapYear ”
可以使用通配符“*”和“?”盯蝴,例如:“IsLeapYearTest.*”
使用“:”連接多個匹配條件毅哗,例如:“*. leapYear : *. commonYear”
使用“-”排除用例,例如:“-IsLeapYearTest.*”
2捧挺、斷言
??斷言可以理解為判斷一個值或多個值是否滿足指定條件虑绵,例如:
說明 | 斷言的宏調用 |
---|---|
判斷一個值是否為真 | EXPECT_TRUE(val) |
判斷一個值是否與期望值相等 | EXPECT_EQ(exp, val) |
判斷兩個值的大小 | EXPECT_LE(val1, val2) |
判斷一個字符串是否與期望值相等 | EXPECT_STREQ(exp, val) |
更多GTest中斷言的宏請查閱文檔:[GTest目錄名]\googletest\docs\primer.md
??當判定通過時,無輸出闽烙,如下圖:
??當判定失敗時翅睛,GTest會輸出斷言位置和失敗原因,如下圖:
??GTest中斷言的宏可以理解為兩類:ASSERT黑竞、EXPECT捕发。
ASSERT_*:當檢查點失敗時,退出當前函數(shù)(執(zhí)行return操作)很魂。
EXPECT_*:當檢查點失敗時扎酷,繼續(xù)往下執(zhí)行。
3遏匆、使用GTest說明FIRST原則
??看完以上內容法挨,應該對GTest有了簡單的認識,接下來就使用GTest框架來舉例說明FIRST原則幅聘。
(1)F-FAST(快速原則)
??在調試程序的過程中凡纳,需要多次運行單元測試去驗證被測試模塊是否正確,應該為了節(jié)省時間帝蒿,單元測試必須可以快速執(zhí)行荐糜。
TEST(Fs, basic) { /* 測試文件接口的基本功能 */
ASSERT_EQ(fs_test(os_fs()), RET_OK); /* 測試獲取文件系統(tǒng)對象函數(shù)os_fs() */
}
(2)I-Independent(獨立原則)
??單元測試可獨立運行,測試用例直接無依賴陵叽,對外部資源無依賴狞尔,測試順序不影響測試結果,測試過程中產生的外部資源文件需要在測試完成后銷毀巩掺。
TEST(Fs, read_part) { /* 測試文件接口的讀取功能 */
char buff[128];
uint32_t size = 0;
const char* str = "hello world";
const char* filename = "test.bin";
/* 測試前要創(chuàng)建被讀取的文件 */
file_write(filename, str, strlen(str));
char* ret = (char*)file_read(filename, &size);
/* 進行測試偏序,對讀取到的結果ret進行驗證 */
ASSERT_EQ(file_read_part(filename, buff, sizeof(buff), 0), strlen(str));
ASSERT_EQ(strcmp(ret, str), 0);
ASSERT_EQ(size, strlen(str));
/* 測試完成后刪除被讀取的文件,并釋放緩沖區(qū) */
file_remove(filename);
TKMEM_FREE(ret);
}
(3)R-Repeatabl(可重復原則)
??單元測試需要可以穩(wěn)定重復的運行胖替,每次得到的結果需要保持一致研儒,如果連測試結果都不穩(wěn)定,或者測試過程經常出現(xiàn)失敗的情況独令,那么單元測試也沒有意義了端朵。
TEST(RandomNumber, Compared) { /* 測試隨機數(shù)比較大小 */
ASSERT_TRUE((rand() % 100) > (rand() % 100)); /* 無法保證每次結果都一致 */
}
(4)S-Self Validating(自我驗證原則)
??單元測試由用例自動進行驗證的,不依賴人工驗證燃箭,這是因為人工驗證耗費不必要的時間冲呢,而且沒有辦法保證驗證結果的準確性,通常來說單元測試的自我驗證就是由測試程序直接告訴開發(fā)者通過或不通過招狸,不需要讓開發(fā)者通過輸出結果來判斷自己的測試用例是否通過敬拓。例如GTest的測試用例輸出邻薯,輸出OK表示通過,如下所示:
[ RUN ] Fs.basic /* RUN表示執(zhí)行Fs.basic測試用例 */
[ OK ] Fs.basic (4 ms) /* OK表示Fs.basic用例測試通過 */
[ RUN ] Fs.read_part
[ OK ] Fs.read_part (0 ms)
(5)T-Timely(及時原則)
??單元測試必須及時進行編寫乘凸,更新和維護厕诡,以保證用例可以隨著業(yè)務代碼的變化動態(tài)的保障質量。單元測試通常是在寫函數(shù)實現(xiàn)前就需要寫好的营勤,這樣能讓單元測試在開發(fā)者寫函數(shù)實現(xiàn)的過程中起到校驗的作用灵嫌,避免開發(fā)者犯錯。
TEST(Module, Function) { /* 測試某模塊的功能(函數(shù)) */
ASSERT_TRUE(function()); /* 先按照期望結果測試function()葛作,再去寫function()的實現(xiàn) */
}