適合於嵌入式系統的C語言單元測試框架:SCUNIT

適合於嵌入式系統的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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章