嵌入式如何完成代码单元测试

科技   2024-11-11 16:16   山东  
软件开发中,每次需求的变更基本都需要改写代码,而代码变更后就需要进行功能测试。[付费]毕设项目资料包
当然,在功能测试之前需要代码的单元测试,避免代码改动后部分场景没有验证,最后出现各种问题。
通过测试框架快速完成代码的单元测试,不仅可以覆盖之前测试的场景,也能快速反应问题在哪里。
常用的C语言测试框架有:
  • Unity:一个小型的,开源的C语言测试框架,提供了用于测试的基本结构和函数。简单好用,常用于嵌入式系统开发。
  • CUnit:一个面向C语言测试的框架,使用简单,支持自动化测试和手动测试。
  • Check:适用于C语言的单元测试框架,使用简单,支持测试套件、测试用例的管理,便于维护测试组件。
  • Google Test:Google推出的C++测试框架,支持C语言,可以跨平台,具有丰富的断言库和Mocks。
  • cmocka:适用于C语言的单元测试框架,支持内存泄漏检测,支持Mock函数和Stub函数等高级用法。
  • criterion:基于C语言的单元测试框架,支持参数化测试和测试用例依赖,具有良好的性能和易用性。

1 Unity示例

这里介绍Unity,其他的大家感兴趣可以自行查阅,不同的单元测试框架适用于不同的开发需求和场景。开发人员可以按照自己的项目要求选择最适合的框架。
Unity最小可以只用到几个文件即可完成,把Unity源码目录下的unity.c、unity.h、unity_internals.h三个文件复制至我们的工程目录下进行编译即可,然后在测试文件代码中包含unity.h
https://github.com/ThrowTheSwitch/Unity/releases
1.1 简单的示例
完成功能函数的验证:
#include <stdio.h>#include "unity.h"

void setUp() { // 这里可以放置每个测试用例运行前的初始化代码}
void tearDown() { // 这里可以放置每个测试用例运行后的清理代码}
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(); // 结束测试
return 0;}
通过串口或终端打印内容为:
C:\test/test.c:47:test_AddFun:PASS
-----------------------1 Tests 0 Failures 0 IgnoredOK
其中,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 <stdio.h>  #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

其中,自定义实现的C语言扩展库(cot)的容器功能函数都已通过Unity添加了对应的单元测试用例,链接:

https://gitee.com/const-zpc/cot

2 轻量级通用扩展库

旨在打造一个C语言的通用扩展库。

2.1 介绍

  1. 支持多种容器实现,包括通用队列(包括不定长队列)、栈、双向链表和动态数组功能
    双向链表节点可动态创建(需要在初始化分配内存)或静态添加 动态数组在初始化分配的内存中最大限度地使用,支持随机访问(连续地址)
  2. 支持定义序列化/反序列化的结构体功能
    使用到了Boost库中的PP库功能宏语法;确保两边都需要保持头文件结构体定义一致
  3. 移植了部分 C++ Boost库中的PP库功能
    通过宏语法实现复杂的宏语言,灵活进行使用,在编译的时候生成自己期望的代码

2.2 软件架构

目录说明:
├─cot│  ├─include│  │  ├─container     // 容器实现头文件│  │  ├─preprocessor  // 移植Boost库中的PP库头文件│  │  └─serialize     // 序列化/反序列化实现头文件│  └─src│      ├─container    // 容器实现源文件│      └─serialize    // 序列化/反序列化实现源文件├─test│  ├─container        // 容器实现测试代码│  └─serialize        // 序列化/反序列化测试代码└─unity               // 单元测试框架代码

2.3 使用说明

(1)容器类功能使用说明

双向链表使用方式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\n", *item_ptr(int, item)); }
// 移除指定元素 cotList_Remove(&list, &data3);
// 根据添加移除元素 cotList_RemoveIf(&list, OnRemoveCondition);
cotList_t list2; cotListItem_t nodeBuf2[3]; cotList_Init(&list2, nodeBuf2, 3);
// 链表内存交换 cotList_Swap(&list1, &list2);
return 0;}
动态数组使用方式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);
// 删除两个元素 cotVector_RemoveN(&vector, 1, 2);
// 根据添加删除元素 cotVector_RemoveIf(&vector, OnVectorRemoveCondition);
// 打印数组中的数据内容 for (int i = 0; i < cotVector_Size(&vector); i++) { printf("%02x ", cotVector_Data(&vector)[i]); }
return 0;}
双向队列(定长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 \n", *pData);
// 弹出首个元素 cotQueue_Pop(&queue);
return 0;}
队列(不定长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 \n", *pData, length);
// 弹出首个元素 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 \n", *pData);
// 弹出顶部元素 cotStack_Pop(&stack);
return 0;}

(2)序列化/反序列化功能使用说明

可以定义一个公共头文件:
#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
各个模块引用头文件使用:
#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: \n");
for (int i = 0; i < length; i++) { printf("%02x %s", buf[i], (i + 1) % 16 == 0 ? "\n" : ""); }
printf("\n");

// 反序列化使用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\n", test2.val1[0]); printf("val = %d\n", test2.val1[1]); printf("val = %d\n", test2.val2); printf("val = %d\n", test2.val3); printf("val = %d\n", test2.val4); printf("val = %lf\n", test2.val5); printf("val = %d\n", test2.val6); printf("name = %s\n", test2.szName); printf("val = %lf\n", test2.val7); printf("val = %f\n", test2.val8); printf("name = %s\n", test2.szName1);
return 0;}

STM32嵌入式开发
STM32嵌入式,PCB电路图,C语言,单片机,Matlab,Linux
 最新文章