适合于嵌入式系统的C语言单元测试框架:SCUNIT
本文博客链接:http://blog.csdn.net/jdh99,作者:jdh,转载请注明.
说明
主流语言都有测试框架,在嵌入式领域特别是资源紧缺的单片机工程中没有合适的测试框架。本文发布一种简单的C语言测试框架SCUNIT,可以应用于嵌入式领域。
测试环境
本框架是基于标准C语言编写,对平台无要求,测试环境如下:
- 单片机:STM32F407
- RTOS:RT-THREAD
测试用例测试的是基于链表的一个路由表结构。
API
void scunit_load(PrintFunc func);
void scunit_add_suite(char *name);
void scunit_add_test(char *name, SCUnitFunc func);
void SCUNIT_ASSERT(bool condition, char *tag);
void SCUNIT_ASSERT_MESSAGE(bool condition, char *tag, char *message);
void SCUNIT_PRINT(char *format, ...);
scuit_load函数是scuit模块载入函数,需要提供打印接口。比如在标准c语言环境,可以将printf作为参数传入。在单片机应用里,可以将串口打印函数传入。本文的测试用例,是将SEGGER RTT的打印函数作为参数传入。
/**
* @brief 使用RTT输出
*/
void console_rtt(char *format, ...)
{
static char log_buf[RT_CONSOLEBUF_SIZE];
va_list args;
va_start(args, format);
int length = rt_vsnprintf(log_buf, sizeof(log_buf) - 1, format, args);
if (length > RT_CONSOLEBUF_SIZE - 1) {
length = RT_CONSOLEBUF_SIZE - 1;
}
log_buf[length] = 0;
SEGGER_RTT_printf(0, log_buf);
va_end(args);
}
scunit_load(console_rtt);
源码
scunit.h
/**
* Copyright (c), 2019-2019
* @file cunit.h
* @brief 单元测试头文件
* @verbatim
* Change Logs:
* Date Author Notes
* 2019-08-31 jdh 新建
* @endverbatim
*/
#ifndef CUNIT_H
#define CUNIT_H
#include "stdio.h"
#include "stdbool.h"
#include "stdint.h"
#include "stdlib.h"
#include "string.h"
#include "stdarg.h"
typedef void (*PrintFunc)(char *format, ...);
typedef void (*SCUnitFunc)(void);
void scunit_load(PrintFunc func);
void scunit_add_suite(char *name);
void scunit_add_test(char *name, SCUnitFunc func);
void SCUNIT_ASSERT(bool condition, char *tag);
/**
* @brief 带信息输出的断言
* @param condition: 条件
* @param message: 断言失败时输出
*/
void SCUNIT_ASSERT_MESSAGE(bool condition, char *tag, char *message);
void SCUNIT_PRINT(char *format, ...);
#endif
scunit.c
/**
* Copyright (c), 2019-2019
* @file cunit.c
* @brief 单元测试主文件
* @verbatim
* Change Logs:
* Date Author Notes
* 2019-08-31 jdh 新建
* @endverbatim
*/
#include "scunit.h"
#define CUNIT_PRINT_SIZE_MAX 128
static PrintFunc _print = NULL;
static char *_func_name = NULL;
void scunit_load(PrintFunc func)
{
_print = func;
}
void scunit_add_suite(char *name)
{
_print("\nSuite:%s\n", name);
}
void scunit_add_test(char *name, SCUnitFunc func)
{
_func_name = name;
_print("-------------------->case:%s begin\n", _func_name);
func();
_print("-------------------->case:%s end\n", _func_name);
}
void SCUNIT_ASSERT(bool condition, char *tag)
{
if (condition) {
_print("%s tag:%s CUNIT_ASSERT pass\n", _func_name, tag);
} else {
_print("%s tag:%s CUNIT_ASSERT fail\n", _func_name, tag);
}
}
/**
* @brief 带信息输出的断言
* @param condition: 条件
* @param message: 断言失败时输出
*/
void SCUNIT_ASSERT_MESSAGE(bool condition, char *tag, char *message)
{
if (condition) {
_print("%s tag:%s CUNIT_ASSERT pass\n", _func_name, tag);
} else {
_print("%s tag:%s CUNIT_ASSERT fail:%s\n", _func_name, tag, message);
}
}
void SCUNIT_PRINT(char *format, ...)
{
static char buf[CUNIT_PRINT_SIZE_MAX];
va_list args;
va_start(args, format);
int length = vsnprintf(buf, sizeof(buf) - 1, format, args);
if (length > CUNIT_PRINT_SIZE_MAX - 1) {
length = CUNIT_PRINT_SIZE_MAX - 1;
}
buf[length] = 0;
_print(buf);
va_end(args);
}
测试代码
载入测试模块:
scunit_load(console_rtt);
测试用例:
/**
* Copyright (c), 2019-2019
* @file test_routing_table.c
* @brief 路由表测试主文件
* @verbatim
* Change Logs:
* Date Author Notes
* 2019-08-30 jdh 新建
* @endverbatim
*/
#include "test_routing_table.h"
#include "routing_table.h"
#include "test.h"
#define MAX_TEST_NUM 100
static void test_case0(void);
static void test_case1(void);
void test_routing_table_run(void)
{
scunit_add_suite("test_routing_table");
scunit_add_test("case0", test_case0);
scunit_add_test("case1", test_case1);
}
static void test_case0(void)
{
bool result = false;
uint8_t port = 0;
routing_table_add(0x1234567812345678, 1);
result = routing_table_query(0x1234567812345678, &port);
SCUNIT_ASSERT(result && port == 1, "1");
routing_table_delete(0x1234567812345678);
result = routing_table_query(0x1234567812345678, &port);
SCUNIT_ASSERT(result == false, "2");
routing_table_add(0x1234567812345679, 12);
result = routing_table_query(0x1234567812345679, &port);
SCUNIT_ASSERT(result && port == 12, "3");
}
static void test_case1(void)
{
uint64_t t1 = get_local_time_us();
for (uint32_t i = 0; i < MAX_TEST_NUM; i++) {
routing_table_add(i, i);
}
uint64_t t2 = get_local_time_us();
int delta = t2 - t1;
SCUNIT_PRINT("add %d consume time:%ldus single:%ldus\n", MAX_TEST_NUM, delta, delta / MAX_TEST_NUM);
bool result = false;
uint8_t port = 0;
routing_table_add(1, 5);
for (uint32_t i = 0; i < 5; i++) {
t1 = get_local_time_us();
routing_table_query(i, &port);
t2 = get_local_time_us();
delta = t2 - t1;
SCUNIT_PRINT("query ia:%x result:%d port:%d consume time:%ldus\n", i, result, port, delta);
}
t1 = get_local_time_us();
for (uint32_t i = 0; i < MAX_TEST_NUM; i++) {
routing_table_delete(i);
}
t2 = get_local_time_us();
delta = t2 - t1;
SCUNIT_PRINT("delete %d consume time:%ldus single:%ldus\n", MAX_TEST_NUM, delta, delta / MAX_TEST_NUM);
test_case0();
}
测试输出
0> Suite:test_routing_table
0> -------------------->case:case0 begin
0> case0 tag:1 SCUNIT_ASSERT pass
0> case0 tag:2 SCUNIT_ASSERT pass
0> case0 tag:3 SCUNIT_ASSERT pass
0> -------------------->case:case0 end
0> -------------------->case:case1 begin
0> add 100 consume time:2087us single:20us
0> query ia:0 result:0 port:0 consume time:34us
0> query ia:1 result:0 port:5 consume time:2us
0> query ia:2 result:0 port:2 consume time:33us
0> query ia:3 result:0 port:3 consume time:33us
0> query ia:4 result:0 port:4 consume time:33us
0> delete 100 consume time:1825us single:18us
0> case1 tag:1 SCUNIT_ASSERT pass
0> case1 tag:2 SCUNIT_ASSERT pass
0> case1 tag:3 SCUNIT_ASSERT pass
0> -------------------->case:case1 end