軟件開發(fā)中,每次需求的變更基本都需要改寫代碼,而代碼變更后就需要進(jìn)行功能測試,當(dāng)然在功能測試之前需要代碼的單元測試,避免代碼改動后部分場景沒有驗(yàn)證,最后出現(xiàn)各種問題。
通過測試框架快速完成代碼的單元測試,不僅可以覆蓋之前測試的場景,也能快速反應(yīng)問題在哪里
常用的C語言測試框架有:
Unity:一個(gè)小型的,開源的C語言測試框架,提供了用于測試的基本結(jié)構(gòu)和函數(shù)。簡單好用,常用于嵌入式系統(tǒng)開發(fā)。
CUnit:一個(gè)面向C語言測試的框架,使用簡單,支持自動化測試和手動測試。
Check:適用于C語言的單元測試框架,使用簡單,支持測試套件、測試用例的管理,便于維護(hù)測試組件。
Google Test:Google推出的C++測試框架,支持C語言,可以跨平臺,具有豐富的斷言庫和Mocks。
cmocka:適用于C語言的單元測試框架,支持內(nèi)存泄漏檢測,支持Mock函數(shù)和Stub函數(shù)等高級用法。
criterion:基于C語言的單元測試框架,支持參數(shù)化測試和測試用例依賴,具有良好的性能和易用性。
Unity示例
這里介紹Unity,其他的大家感興趣可以自行查閱,不同的單元測試框架適用于不同的開發(fā)需求和場景。開發(fā)人員可以按照自己的項(xiàng)目要求選擇最適合的框架。
Unity最小可以只用到幾個(gè)文件即可完成,把Unity源碼目錄下的unity.c、unity.h、unity_internals.h三個(gè)文件復(fù)制至我們的工程目錄下進(jìn)行編譯即可,然后在測試文件代碼中包含unity.h
https://github.com/ThrowTheSwitch/Unity/releases
簡單的示例
完成功能函數(shù)的驗(yàn)證
?
#include?#include?"unity.h" void?setUp()?{ ????//?這里可以放置每個(gè)測試用例運(yùn)行前的初始化代碼 } void?tearDown()?{ ????//?這里可以放置每個(gè)測試用例運(yùn)行后的清理代碼 } int?Add(int?a,?int?b) { ????return?a?+?b; } void?test_AddFun(void) { ????TEST_ASSERT_EQUAL_UINT(6,?Add(1,?5)); ????TEST_ASSERT_EQUAL_UINT(4,?Add(-1,?5)); ????TEST_ASSERT_EQUAL_UINT(-6,?Add(-1,?-5)); } int?main() { ????UNITY_BEGIN();??//?啟動測試 ????RUN_TEST(test_AddFun); ????UNITY_END();??//?結(jié)束測試 ????return?0; }??
?
通過串口或終端打印內(nèi)容為:
?
C: est/test.c:47PASS ----------------------- 1?Tests?0?Failures?0?Ignored OK
?
其中,unity_internals.h文件中可以修改輸出終端,即UNITY_OUTPUT_CHAR宏的定義
?
/*------------------------------------------------------- ?*?Output?Method:?stdout?(DEFAULT) ?*-------------------------------------------------------*/ #ifndef?UNITY_OUTPUT_CHAR ??/*?Default?to?using?putchar,?which?is?defined?in?stdio.h?*/ ??#include???#define?UNITY_OUTPUT_CHAR(a)?(void)putchar(a) #else ??/*?If?defined?as?something?else,?make?sure?we?declare?it?here?so?it's?ready?for?use?*/ ??#ifdef?UNITY_OUTPUT_CHAR_HEADER_DECLARATION ????extern?void?UNITY_OUTPUT_CHAR_HEADER_DECLARATION; ??#endif #endif
?
其中自定義實(shí)現(xiàn)的C語言擴(kuò)展庫(cot)的容器功能函數(shù)都已通過Unity添加了對應(yīng)的單元測試用例,鏈接:
?
https://gitee.com/const-zpc/cot
?
輕量級通用擴(kuò)展庫
旨在打造一個(gè)C語言的通用擴(kuò)展庫。
介紹
支持多種容器實(shí)現(xiàn),包括通用隊(duì)列(包括不定長隊(duì)列)、棧、雙向鏈表和動態(tài)數(shù)組功能
雙向鏈表節(jié)點(diǎn)可動態(tài)創(chuàng)建(需要在初始化分配內(nèi)存)或靜態(tài)添加 動態(tài)數(shù)組在初始化分配的內(nèi)存中最大限度地使用,支持隨機(jī)訪問(連續(xù)地址)
支持定義序列化/反序列化的結(jié)構(gòu)體功能
使用到了Boost庫中的PP庫功能宏語法;確保兩邊都需要保持頭文件結(jié)構(gòu)體定義一致
移植了部分 C++ Boost庫中的PP庫功能
通過宏語法實(shí)現(xiàn)復(fù)雜的宏語言,靈活進(jìn)行使用,在編譯的時(shí)候生成自己期望的代碼
軟件架構(gòu)
目錄說明
?
├─cot │ ├─include │ │ ├─container // 容器實(shí)現(xiàn)頭文件 │ │ ├─preprocessor // 移植Boost庫中的PP庫頭文件 │ │ └─serialize // 序列化/反序列化實(shí)現(xiàn)頭文件 │ └─src │ ├─container // 容器實(shí)現(xiàn)源文件 │ └─serialize // 序列化/反序列化實(shí)現(xiàn)源文件 ├─test │ ├─container // 容器實(shí)現(xiàn)測試代碼 │ └─serialize // 序列化/反序列化測試代碼 └─unity // 單元測試框架代碼
?
使用說明
容器類功能使用說明
雙向鏈表使用方式demo
?
int main()
{
cotList_t list;
cotListItem_t nodeBuf[10];
cotList_Init(&list, nodeBuf, 10);
int data1 = 10;
int data2 = 20;
int data3 = 30;
// 頭部增加元素
cotList_PushFront(&list, &data1);
// 尾部增加元素
cotList_PushBack(&list, &data2);
// 插入元素
cotList_Insert(&list, cotList_End(&list), &data3);
// 使用迭代器遍歷所有元素
for_list_each(item, list)
{
printf(" = %d
", *item_ptr(int, item));
}
// 移除指定元素
cotList_Remove(&list, &data3);
// 根據(jù)添加移除元素
cotList_RemoveIf(&list, OnRemoveCondition);
cotList_t list2;
cotListItem_t nodeBuf2[3];
cotList_Init(&list2, nodeBuf2, 3);
// 鏈表內(nèi)存交換
cotList_Swap(&list1, &list2);
return 0;
}
?
動態(tài)數(shù)組使用方式demo
?
int main()
{
uint8_t buf[20];
cotVector_t vector;
cotVector_Init(&vector, buf, sizeof(buf), sizeof(uint32_t));
// 在尾部追加元素
uint32_t data = 42;
cotVector_Push(&vector, &data);
data = 56;
cotVector_Push(&vector, &data);
data = 984;
cotVector_Push(&vector, &data);
// 插入元素
uint32_t arrdata[2] = {125, 656};
cotVector_InsertN(&vector, 2, &arrdata, 2);
// 刪除兩個(gè)元素
cotVector_RemoveN(&vector, 1, 2);
// 根據(jù)添加刪除元素
cotVector_RemoveIf(&vector, OnVectorRemoveCondition);
// 打印數(shù)組中的數(shù)據(jù)內(nèi)容
for (int i = 0; i < cotVector_Size(&vector); i++)
{
printf("%02x ", cotVector_Data(&vector)[i]);
}
return 0;
}
?
雙向隊(duì)列(定長FIFO)使用方式demo
?
int main()
{
uint8_t buf[10];
cotQueue_t queue;
cotQueue_Init(&queue, buf, sizeof(buf), sizeof(int));
// 在尾部追加元素
int data = 42;
cotQueue_Push(&queue, &data, sizeof(data));
data = 895;
cotQueue_Push(&queue, &data, sizeof(data));
// 訪問元素
int *pData = (int *)cotQueue_Front(&queue);
printf("val = %d
", *pData);
// 彈出首個(gè)元素
cotQueue_Pop(&queue);
return 0;
}
?
隊(duì)列(不定長FIFO)使用方式demo
?
int main()
{
uint8_t buf[10];
cotIndQueue_t queue;
cotIndQueue_Init(&queue, buf, sizeof(buf));
// 在尾部追加元素
char data = 42;
cotIndQueue_Push(&queue, &data, sizeof(data));
int data1 = 80;
cotIndQueue_Push(&queue, &data, sizeof(data1));
long data2 = -400;
cotIndQueue_Push(&queue, &data, sizeof(data2));
// 訪問元素
size_t length;
int *pData = (int *)cotIndQueue_Front(&queue, &length);
printf("val = %d
", *pData, length);
// 彈出首個(gè)元素
cotIndQueue_Pop(&queue);
return 0;
}
?
單向棧使用方式demo
?
int main()
{
uint8_t buf[10];
cotStack_t stack;
cotStack_Init(&stack, buf, sizeof(buf), sizeof(int));
// 在頂部追加元素
int data = 42;
cotStack_Push(&stack, &data, sizeof(data));
data = 895;
cotQueue_Push(&stack, &data, sizeof(data));
// 訪問元素
int *pData = (int *)cotStack_Top(&stack);
printf("val = %d
", *pData);
// 彈出頂部元素
cotStack_Pop(&stack);
return 0;
}
?
序列化/反序列化功能使用說明
可以定義一個(gè)公共頭文件
?
#ifndef STRUCT_H
#define STRUCT_H
#include "serialize/serialize.h"
COT_DEFINE_STRUCT_TYPE(test_t,
((UINT16_T) (val1) (2))
((INT32_T) (val2) (1))
((UINT8_T) (val3) (1))
((INT16_T) (val4) (1))
((DOUBLE_T) (val5) (1))
((INT16_T) (val6) (1))
((STRING_T) (szName) (100))
((DOUBLE_T) (val7) (1))
((FLOAT_T) (val8) (1))
((STRING_T) (szName1) (100))
)
#endif // STRUCT_H
?
各個(gè)模塊引用頭文件使用
?
#include "struct.h"
int main()
{
uint8_t buf[100];
// 序列化使用demo
COT_DEFINE_STRUCT_VARIABLE(test_t, test);
test.val1[0] = 5;
test.val1[1] = 89;
test.val2 = -9;
test.val3 = 60;
test.val4 = -999;
test.val5 = 5.6;
test.val6 = 200;
test.val7 = -990.35145;
test.val8 = -80.699;
sprintf(test.szName, "test56sgdgdfgdfgdf");
sprintf(test.szName1, "sdfsdf");
int length = test.Serialize(buf, &test);
printf("Serialize:
");
for (int i = 0; i < length; i++)
{
printf("%02x %s", buf[i], (i + 1) % 16 == 0 ? "
" : "");
}
printf("
");
// 反序列化使用demo
test_t test2; // COT_DEFINE_STRUCT_VARIABLE(test_t, test2);
COT_INIT_STRUCT_VARIABLE(test_t, test2);
test2.Parse(&test2, buf);
printf("val = %d
", test2.val1[0]);
printf("val = %d
", test2.val1[1]);
printf("val = %d
", test2.val2);
printf("val = %d
", test2.val3);
printf("val = %d
", test2.val4);
printf("val = %lf
", test2.val5);
printf("val = %d
", test2.val6);
printf("name = %s
", test2.szName);
printf("val = %lf
", test2.val7);
printf("val = %f
", test2.val8);
printf("name = %s
", test2.szName1);
return 0;
}
?
審核編輯:湯梓紅
電子發(fā)燒友App



















評論