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_TRUE | TRUE |
| 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_INT | expected == 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_WITHIN | diff(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_WITHIN | ArrayToArray | for i range(num_elements): diff(expected, actual[i]) <= delta |
结构和字符串
| 接口 | 底层 | 说明 |
|---|---|---|
| TEST_ASSERT_EQUAL_PTR | UnityAssertEqualNumber | 指针比较 |
| TEST_ASSERT_EQUAL_STRING | UnityAssertEqualString | 字符串比较 |
| TEST_ASSERT_EQUAL_STRING_LEN | UnityAssertEqualStringLen | 字符串比较(不超过指定长度) |
| TEST_ASSERT_EQUAL_MEMORY | UnityAssertEqualMemory | 内存比较,按字节对比指定长度 |
数组
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_ARRAY | UnityAssertEqualIntArray | ArrayToArray | for i range(num_elements): if expected[i] == actual[i] |
| TEST_ASSERT_EACH_EQUAL_INT | UnityAssertEqualIntArray | ArrayToVal | for 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_INT | UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, NULL) |
| TEST_ASSERT_EQUAL_INT_MESSAGE | UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, (message)) |
