適合於嵌入式系統的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