良许Linux教程网 干货合集 嵌入式代码单元测试如何完成?

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

在软件开发中,每次需求的变更一般都需要对代码进行修改,而修改后的代码需要进行功能测试。在进行功能测试之前,需要先进行代码的单元测试,以避免在潜在场景中出现问题。

通过使用测试框架,可以快速地完成代码的单元测试。这不仅可以覆盖之前已经测试过的场景,还可以快速反映出问题所在。

常用的C语言单元测试框架包括:

  • Unity:一个小型、开源的C语言测试框架,提供了用于测试的基本结构和函数。这个框架简单易用,通常用于嵌入式系统开发。
  • CUnit:一个面向C语言测试的框架,使用简单,支持自动化测试和手动测试。
  • Check:适用于C语言的单元测试框架,使用简单,支持管理测试套件和测试用例,便于维护测试组件。
  • Google Test:Google推出的C++测试框架,支持C语言,可以跨平台,具有丰富的断言库和Mocks。
  • cmocka:适用于C语言的单元测试框架,支持内存泄漏检测,支持Mock函数和Stub函数等高级用法。
  • criterion:基于C语言的单元测试框架,支持参数化测试和测试用例依赖,具有良好的性能和易用性。

Unity是其中一个例子,其他的框架相信大家可以自行查阅,不同的单元测试框架适用于不同的开发需求和场景。开发者可以根据自己的项目要求选择最适合的框架。

使用Unity进行单元测试非常简单,只需要复制Unity源码目录下的unity.cunity.hunity_internals.h三个文件到工程目录下即可编译。然后在测试文件代码中包含unity.h文件即可开始使用。

https://github.com/ThrowTheSwitch/Unity/releases

简单的示例

完成功能函数的验证

#include 
#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 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

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

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

轻量级通用扩展库

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

介绍

  1. 支持多种容器实现,包括通用队列(包括不定长队列)、栈、双向链表和动态数组功能

    双向链表节点可动态创建(需要在初始化分配内存)或静态添加 动态数组在初始化分配的内存中最大限度地使用,支持随机访问(连续地址)

  2. 支持定义序列化/反序列化的结构体功能

    使用到了Boost库中的PP库功能宏语法;确保两边都需要保持头文件结构体定义一致

  3. 移植了部分 C++ Boost库中的PP库功能

    通过宏语法实现复杂的宏语言,灵活进行使用,在编译的时候生成自己期望的代码

软件架构

目录说明

├─cot
│  ├─include
│  │  ├─container     // 容器实现头文件
│  │  ├─preprocessor  // 移植Boost库中的PP库头文件
│  │  └─serialize     // 序列化/反序列化实现头文件
│  └─src
│      ├─container    // 容器实现源文件
│      └─serialize    // 序列化/反序列化实现源文件
├─test
│  ├─container        // 容器实现测试代码
│  └─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\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 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;
}

序列化/反序列化功能使用说明

可以定义一个公共头文件

#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 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;
}

以上就是良许教程网为各位朋友分享的Linu系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你 !

137e00002230ad9f26e78-265x300
本文由 良许Linux教程网 发布,可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。
良许

作者: 良许

良许,世界500强企业Linux开发工程师,公众号【良许Linux】的作者,全网拥有超30W粉丝。个人标签:创业者,CSDN学院讲师,副业达人,流量玩家,摄影爱好者。
上一篇
下一篇

发表评论

联系我们

联系我们

公众号:良许Linux

在线咨询: QQ交谈

邮箱: yychuyu@163.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

关注微博
返回顶部