unity

C语言的单元测试框架

核心

数据结构

truct UNITY_STORAGE_T
{
    const char* TestFile;               // 文件名
    const char* CurrentTestName;        // 测试函数名
#ifndef UNITY_EXCLUDE_DETAILS
    const char* CurrentDetail1;
    const char* CurrentDetail2;
#endif
    UNITY_LINE_TYPE CurrentTestLineNumber;  // 测试函数在源码中的行号
    UNITY_COUNTER_TYPE NumberOfTests;       // 测试函数总计
    UNITY_COUNTER_TYPE TestFailures;        // 测试失败总计
    UNITY_COUNTER_TYPE TestIgnores;         // 测试忽略总计
    UNITY_COUNTER_TYPE CurrentTestFailed;   // 当前测试失败
    UNITY_COUNTER_TYPE CurrentTestIgnored;  // 当前测试忽略
#ifdef UNITY_INCLUDE_EXEC_TIME
    UNITY_TIME_TYPE CurrentTestStartTime;   // 记录每个测试开始时的时间
    UNITY_TIME_TYPE CurrentTestStopTime;    // 记录每个测试结束时的时间
#endif
#ifndef UNITY_EXCLUDE_SETJMP_H
    jmp_buf AbortFrame;                     // 用于测试失败时跳出的位置
#endif
};

输出测试日志格式如下

F:\unicstl\test\test_queue.c:671:test_queue_new:PASS

-----------------------
1 Tests 0 Failures 0 Ignored
OK

函数

#define RUN_TEST(...) RUN_TEST_AT_LINE(__VA_ARGS__, __LINE__, throwaway)
#define RUN_TEST_AT_LINE(func, line, ...) UnityDefaultTestRun(func, #func, line)

RUN_TEST宏定义本质上是调用的如下函数,其中用到了__LINE__以及预处理操作符##可以将宏定义的参数转化为字符串面量,也即字符串化。

void UnityDefaultTestRun(UnityTestFunction Func, const char* FuncName, const int FuncLineNum)
{
    // 当前函数名,行号,测试计数
    Unity.CurrentTestName = FuncName;
    Unity.CurrentTestLineNumber = (UNITY_LINE_TYPE)FuncLineNum;
    Unity.NumberOfTests++;
    UNITY_CLR_DETAILS();
    UNITY_EXEC_TIME_START();
    
    if (TEST_PROTECT())
    {
        setUp();
        Func();
    }
    if (TEST_PROTECT())
    {
        tearDown();
    }
    UNITY_EXEC_TIME_STOP();
    UnityConcludeTest();
}

其中TEST_PROTECT宏定义如下

#define TEST_PROTECT() (setjmp(Unity.AbortFrame) == 0)

#define TEST_ABORT() longjmp(Unity.AbortFrame, 1)

如果在测试执行过程中发生异常或调用 UnityFail() 函数, Unity 测试框架可能会调用 longjmp(Unity.AbortFrame, 1)来恢复之前保存的执行环境。

这样,setjmp 将在 TEST_PROTECT() 中返回非零值(通常是 1), 从而跳过 setUp()、Func() 或 tearDown()的执行, 避免在不安全的状态下继续执行或进行清理工作。

常用接口

对外的接口都可以从Unity.h查到,就不详细描述了。这里主要分析代码的底层实现

调用前后

void setUp(void);
void tearDown(void);

简单判断

// 若条件不满足,则打印行数和提示消息,
#define UNITY_TEST_ASSERT(condition, line, message) \
    do { if (condition) { /* nothing*/ } else { UNITY_TEST_FAIL((line), (message)); } } while (0)

// 若指针为空,则报错
#define UNITY_TEST_ASSERT_NULL(pointer, line, message) \
    UNITY_TEST_ASSERT(((pointer) == NULL),  (line), (message))
接口说明
TEST_ASSERT布尔判断
TEST_ASSERT_TRUETRUE
TEST_ASSERT_NULL指针为空

等于判断

// sytle: 支持的类型:int8、int32,uint,hex,char等
void UnityAssertEqualNumber(const UNITY_INT expected,
                            const UNITY_INT actual,
                            const char* msg,
                            const UNITY_LINE_TYPE lineNumber,
                            const UNITY_DISPLAY_STYLE_T style);
// 输出: 
// 行数: Expected <expected> was <actual>. <msg>
接口判断
TEST_ASSERT_EQUAL_INTexpected == actual

比特判断

void UnityAssertBits(const UNITY_INT mask,
                     const UNITY_INT expected,
                     const UNITY_INT actual,
                     const char* msg,
                     const UNITY_LINE_TYPE lineNumber)
{
    // 函数判断依据
    if ((mask & expected) != (mask & actual))
    {
        //...
    }
}
接口判断
TEST_ASSERT_BITS_HIGH(mask & actual) == expected
TEST_ASSERT_BITS_LOW(mask & actual) == 0
TEST_ASSERT_BIT_HIGH((1 << bit) & actual) == (1 << bit) 或者理解为(1 << bit) & actual) != 0
TEST_ASSERT_BIT_LOW((1 << bit) & actual) == 0

大小判断

void UnityAssertGreaterOrLessOrEqualNumber(const UNITY_INT threshold,
                                           const UNITY_INT actual,
                                           const UNITY_COMPARISON_T compare,
                                           const char *msg,
                                           const UNITY_LINE_TYPE lineNumber,
                                           const UNITY_DISPLAY_STYLE_T style)
接口说明
TEST_ASSERT_NOT_EQUAL_INT期望值不等于实际值通过,否则报错
TEST_ASSERT_GREATER_THAN大于实际值
TEST_ASSERT_LESS_THAN小于实际值
TEST_ASSERT_GREATER_OR_EQUAL大于等于实际值
TEST_ASSERT_LESS_OR_EQUAL小于等于实际值

范围判断

The difference between the expected and actual values is less than or equal to delta.

void UnityAssertNumbersWithin(const UNITY_UINT delta,
                              const UNITY_INT expected,
                              const UNITY_INT actual,
                              const char* msg,
                              const UNITY_LINE_TYPE lineNumber,
                              const UNITY_DISPLAY_STYLE_T style)
接口说明
TEST_ASSERT_INT_WITHINdiff(expected,actual) <= delta

针对数组中每个元素的范围判断,底层接口如下:

// 1. 如果元素个数为0,则报错。
// 2. 预期和实际的指针指向一样,则返回
// 3. 若有且仅有一个数组为空,则报错。
// 4. 按照元素大小,逐一比较。看是否在期望范围内
void UnityAssertNumbersArrayWithin(const UNITY_UINT delta,
                                   UNITY_INTERNAL_PTR expected,
                                   UNITY_INTERNAL_PTR actual,
                                   const UNITY_UINT32 num_elements,
                                   const char* msg,
                                   const UNITY_LINE_TYPE lineNumber,
                                   const UNITY_DISPLAY_STYLE_T style,
                                   const UNITY_FLAGS_T flags)
接口说明本质
UNITY_TEST_ASSERT_INT_ARRAY_WITHINArrayToArrayfor i range(num_elements): diff(expected, actual[i]) <= delta

结构和字符串

接口底层说明
TEST_ASSERT_EQUAL_PTRUnityAssertEqualNumber指针比较
TEST_ASSERT_EQUAL_STRINGUnityAssertEqualString字符串比较
TEST_ASSERT_EQUAL_STRING_LENUnityAssertEqualStringLen字符串比较(不超过指定长度)
TEST_ASSERT_EQUAL_MEMORYUnityAssertEqualMemory内存比较,按字节对比指定长度

数组

void UnityAssertEqualIntArray(UNITY_INTERNAL_PTR expected,
                              UNITY_INTERNAL_PTR actual,
                              const UNITY_UINT32 num_elements,
                              const char* msg,
                              const UNITY_LINE_TYPE lineNumber,
                              const UNITY_DISPLAY_STYLE_T style,
                              const UNITY_FLAGS_T flags)
接口底层说明本质
TEST_ASSERT_EQUAL_INT_ARRAYUnityAssertEqualIntArrayArrayToArrayfor i range(num_elements): if expected[i] == actual[i]
TEST_ASSERT_EACH_EQUAL_INTUnityAssertEqualIntArrayArrayToValfor i range(num_elements): if expected == actual[i]

浮点

默认不支持double,需要通过config文件宏定义来开启。

// 浮点大小判断,比较函数如下:
// 当判断是否相等时,delta不能直接为0,而是让delte=expected*1e-6,这样才不会因为精度问题导致判断失败。
#define UNITY_FLOAT_OR_DOUBLE_WITHIN(delta, expected, actual, diff)                           \
    if (UNITY_IS_INF(expected) && UNITY_IS_INF(actual) && (((expected) < 0) == ((actual) < 0))) return 1;   \
    if (UNITY_NAN_CHECK) return 1;                                                            \
    (diff) = (actual) - (expected);                                                           \
    if ((diff) < 0) (diff) = -(diff);                                                         \
    if ((delta) < 0) (delta) = -(delta);                                                      \
    return !(UNITY_IS_NAN(diff) || UNITY_IS_INF(diff) || ((diff) > (delta)))

其他

MESSAGE后缀的表示,需要添加自定义的错误信息,例如:

接口实现
TEST_ASSERT_EQUAL_INTUNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, NULL)
TEST_ASSERT_EQUAL_INT_MESSAGEUNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, (message))