CuTest 单元测试框架
CuTest 简介
Section titled “CuTest 简介”Cutest 是一个轻量级的 C/C++ 单元测试框架,旨在提供简单、易用的测试功能。它的主要特点包括:
- 简洁性:Cutest 以简洁的语法使得编写测试用例变得容易,降低了学习曲线。
- 灵活性:支持多种测试风格,可以根据需要进行定制。
- 单头文件:Cutest 仅包含一个头文件,便于集成到现有项目中。
- 断言支持:提供多种断言宏,帮助验证代码的行为。
- 报告生成:在测试执行后,Cutest 可以生成详细的测试报告,便于查看测试结果。
CuTest 解析
Section titled “CuTest 解析”https://github.com/viys/cutest
cutest 官网无法下载到源码, 遂作出更改并整理。
CuTest 库函数解析
Section titled “CuTest 库函数解析”此库的使用重点为
Assert 公共断言相关的 API 使用,故重点介绍这些 API。
CuArray 相关
Section titled “CuArray 相关”在嵌入式调试中我们经常需要对
unsigned char*也就是unit8_t类型的输入和返回进行测试,例如串口、I2C、、SPI、蓝牙等等。因此我额外添加了
CuArray对象以及相关的 API。
CuArray 对象
Section titled “CuArray 对象”由
CuArrayNew()函数创建或使用CuArrayInit()手动初始化。在使用时需要注意一个概念,例如,
arr[num]其中num的含义为偏移量(offset)而非索引(index)。C语言每个版本的公开资料中并无索引的概念,若将其理解成索引在程序开发时可能会出现逻辑不清晰而引起的边界+1问题。
unsigned char* CuArrAlloc (size_t size)
Section titled “unsigned char* CuArrAlloc (size_t size)”创建
size大小的数组空间。
unsigned char* CuArrCopy (unsigned char* old, size_t len)
Section titled “unsigned char* CuArrCopy (unsigned char* old, size_t len)”从指定数组中复制并创建指定大小的空间。
void CuArrayInit (CuArray* arr)
Section titled “void CuArrayInit (CuArray* arr)”初始化数组对象并分配
ARRAY_MAX字节大小的的初始空间。
CuArray* CuArrayNew (void)
Section titled “CuArray* CuArrayNew (void)”创建并初始化数组对象,在初始化时分配
ARRAY_MAX字节大小的的初始空间。
void CuArrayAppend (CuArray* arr, unsigned char* array, size_t len)
Section titled “void CuArrayAppend (CuArray* arr, unsigned char* array, size_t len)”数组对象拼接其他数组。
void CuArrayAppendSingle (CuArray* arr, unsigned char single)
Section titled “void CuArrayAppendSingle (CuArray* arr, unsigned char single)”数组对象拼接单字节。
void CuArrayInsert (CuArray* arr, unsigned char* array, size_t pos, size_t len)
Section titled “void CuArrayInsert (CuArray* arr, unsigned char* array, size_t pos, size_t len)”数组对象插入其他数组。
void CuArrayResize (CuArray* arr, size_t newSize)
Section titled “void CuArrayResize (CuArray* arr, size_t newSize)”重分配数组对象的内存。
void CuArrayDelete (CuArray* arr)
Section titled “void CuArrayDelete (CuArray* arr)”删除数组对象并释放分配给它的空间。
CuString 相关
Section titled “CuString 相关”
strlen()函数计算字符串长度时,它会返回字符串中字符的数量,不包括终止符。
CuString 对象
Section titled “CuString 对象”由
CuStringNew()函数创建或使用CuStringInit()手动初始化 ,为该库基础的字符串相关的类型。凡涉及该对象的 buffer 成员所指向的数组中均已包含字符串终止符
0。
char* CuStrAlloc (size_t size)
Section titled “char* CuStrAlloc (size_t size)”创建
size大小的字符串空间, 创建字符串空间的时候涉及到/0时,szie应比最大字符串字符数大1。
char* CuStrCopy (const char* old)
Section titled “char* CuStrCopy (const char* old)”创建可以容纳制定字符串大小的空间,该空间大小为指定字符串字符数 +1。
void CuStringInit (CuString* str)
Section titled “void CuStringInit (CuString* str)”初始化
CuString对象并创建STRING_MAX大小的内存空间,默认为 256 字节。
CuString* CuStringNew (void)
Section titled “CuString* CuStringNew (void)”创建初始化后的
CuString对象并返回其地址。其对应的内存空间同样为
STRING_MAX字节。
void CuStringRead (CuString* str, const char* path)
Section titled “void CuStringRead (CuString* str, const char* path)”需使用者自行构建。
例如从自己的数据源或文件中读取相关的字符串。
void CuStringAppend (CuString* str, const char* text)
Section titled “void CuStringAppend (CuString* str, const char* text)”字符串拼接函数。
当预先分配的内存不足时重新分配更大的内存空间,新的空间大小默认为
所有字符数+1+STRING_INC。
void CuStringAppendChar (CuString* str, char ch)
Section titled “void CuStringAppendChar (CuString* str, char ch)”字符拼接函数。
当预先分配的内存不足时重新分配更大的内存空间,新的空间大小默认为
所有字符数+1+STRING_INC。拼接完成后依然是一个完整的字符串,其存在字符串终止符
/0。
void CuStringAppendFormat (CuString* str, const char* format, …)
Section titled “void CuStringAppendFormat (CuString* str, const char* format, …)”使用变参格式化字符串并追加到
str。当
CuString对象的内存空间不足时其同样会自动扩容。字符串格式化时开辟了一个大小为
HUGE_STRING_LEN的临时变量空间,可根据自己的场景需求改变该值。
void CuStringInsert (CuString* str, const char* text, size_t pos)
Section titled “void CuStringInsert (CuString* str, const char* text, size_t pos)”在指定位置插入字符串。
当
CuString对象的内存空间不足时其同样会自动扩容。
void CuStringResize (CuString* str, size_t newSize)
Section titled “void CuStringResize (CuString* str, size_t newSize)”重新分配
CuString对象内存空间。
void CuStringDelete (CuString *str)
Section titled “void CuStringDelete (CuString *str)”删除(释放)
CuString对象。
CuTest 相关
Section titled “CuTest 相关”CuTest 对象
Section titled “CuTest 对象”测试样例。
void CuTestInit (CuTest* t, const char* name, TestFunction function)
Section titled “void CuTestInit (CuTest* t, const char* name, TestFunction function)”初始化
CuTest对象。
CuTest* CuTestNew (const char* name, TestFunction function)
Section titled “CuTest* CuTestNew (const char* name, TestFunction function)”创建并初始化
CuTest对象。
void CuTestRun (CuTest* tc);
Section titled “void CuTestRun (CuTest* tc);”运行测试样例。
void CuTestDelete (CuTest *t);
Section titled “void CuTestDelete (CuTest *t);”删除(释放)
CuTest对象。
CuSuite 相关
Section titled “CuSuite 相关”CuSuite 对象
Section titled “CuSuite 对象”测试套件。
void CuSuiteInit (CuSuite* testSuite)
Section titled “void CuSuiteInit (CuSuite* testSuite)”初始化
CuSuite对象。
CuSuite* CuSuiteNew (void)
Section titled “CuSuite* CuSuiteNew (void)”创建并初始化
CuSuite对象。
void CuSuiteDelete (CuSuite *testSuite)
Section titled “void CuSuiteDelete (CuSuite *testSuite)”删除
CuSuite对象。
void CuSuiteAdd (CuSuite* testSuite, CuTest *testCase)
Section titled “void CuSuiteAdd (CuSuite* testSuite, CuTest *testCase)”给测试套件添加测试样例。
SUITE_ADD_TEST (SUITE,TEST)
Section titled “SUITE_ADD_TEST (SUITE,TEST)”快速添加测试样例。
void CuSuiteAddSuite (CuSuite* testSuite, CuSuite* testSuite2)
Section titled “void CuSuiteAddSuite (CuSuite* testSuite, CuSuite* testSuite2)”测试套件拼接。
void CuSuiteRun (CuSuite* testSuite)
Section titled “void CuSuiteRun (CuSuite* testSuite)”运行测试套件。
void CuSuiteSummary (CuSuite* testSuite, CuString* summary)
Section titled “void CuSuiteSummary (CuSuite* testSuite, CuString* summary)”获取测试套间的测试总结(返回值)。
void CuSuiteDetails (CuSuite* testSuite, CuString* details)
Section titled “void CuSuiteDetails (CuSuite* testSuite, CuString* details)”获取测试套件详细的测试结果。
Assert 公共断言相关
Section titled “Assert 公共断言相关”验证测试用例的预期结果是否与实际结果相符。
该部分由宏进行了一层封装,其返回值都为
void类型。它们的第一个参数全为
CuTest测试样例对象。使用方法:
CuFail (tc, ms)
Section titled “CuFail (tc, ms)”标记失败函数:
tc是指向CuTest结构的指针,代表当前的测试用例。
ms是一个字符串,描述了为什么测试失败。当调用
CuFail时,它会立即停止当前测试用例的执行,并将测试标记为失败。这允许开发者在测试过程中的任何地方插入断言,如果特定的条件没有满足,就可以立即报告错误。
函数使用举例:
CuAssert (tc, ms, cond)
Section titled “CuAssert (tc, ms, cond)”条件断言函数:
tc是指向CuTest结构的指针,代表当前的测试用例。
ms是一个字符串,描述了失败时的额外信息。
cond是需要检查的条件表达式。如果这个表达式的计算结果为真(非零),则测试通过;如果结果为假(零),则测试失败,并记录错误信息,包括文件名和行号以及提供的错误消息。
函数使用举例:
CuAssertTrue (tc, cond)
Section titled “CuAssertTrue (tc, cond)”条件判断断言函数:
tc是指向CuTest结构的指针,代表当前的测试用例。
cond是需要检查的条件表达式。如果这个表达式的结果为真(非零),则测试通过;如果结果为假(零),则测试失败。
测试失败时,会记录错误信息,包括文件名和行号。
函数使用举例:
CuAssertMacroEquals (tc, ex, ac)
Section titled “CuAssertMacroEquals (tc, ex, ac)”宏对比断言函数:
主要用于将预期宏的值与所测试函数的返回值进行对比,不同时打印断言。
tc是指向CuTest结构的指针,代表当前的测试用例。
ex是一个预期宏。
ac实际的值。
函数使用举例:
CuAssertStrEquals (tc,ex,ac)
Section titled “CuAssertStrEquals (tc,ex,ac)”字符串对比断言函数:
tc是指向CuTest结构的指针,代表当前的测试用例。
ex是期望的字符串。
ac是实际的字符串,即被测试函数的返回值或某个变量的值。如果
ex和ac两个字符串相等,测试通过;如果不相等,测试失败,并记录错误信息,包括文件名、行号以及预期值和实际值。
函数使用举例:
CuAssertStrEquals_Msg (tc,ms,ex,ac)
Section titled “CuAssertStrEquals_Msg (tc,ms,ex,ac)”在
CuAssertStrEquals()函数的基础上添加错误信息打印。
ms是一个字符串,描述了失败时的额外信息。
函数使用举例:
CuAssertIntEquals (tc,ex,ac)
Section titled “CuAssertIntEquals (tc,ex,ac)”整数对比断言函数:
tc是指向CuTest结构的指针,代表当前的测试用例。
ex是期望的整数。
ac是实际的整数,即被测试函数的返回值或某个变量的值。如果
ex和ac两个整数相等,测试通过;如果不相等,测试失败,并记录错误信息,包括文件名、行号以及预期值和实际值。
函数使用举例:
CuAssertIntEquals_Msg (tc,ms,ex,ac)
Section titled “CuAssertIntEquals_Msg (tc,ms,ex,ac)”在
CuAssertIntEquals_Msg函数的基础上添加错误信息打印。
ms是一个字符串,描述了失败时的额外信息。
函数使用举例:
CuAssertDblEquals (tc,ex,ac,dl)
Section titled “CuAssertDblEquals (tc,ex,ac,dl)”双精度浮点数对比断言函数:
tc是指向CuTest结构的指针,代表当前的测试用例。
ex是期望的浮点数值。
ac是实际的浮点数值,即被测试函数的返回值。
dl是允许的误差范围(delta),因为浮点数运算可能会有精度问题,所以这个参数允许在一定误差范围内认为两个浮点数是相等的。
函数使用举例:
CuAssertDblEquals_Msg (tc,ms,ex,ac,dl)
Section titled “CuAssertDblEquals_Msg (tc,ms,ex,ac,dl)”在
CuAssertDblEquals()函数的基础上添加错误信息打印。
ms是一个字符串,描述了失败时的额外信息。
函数使用举例:
CuAssertPtrEquals (tc,ex,ac)
Section titled “CuAssertPtrEquals (tc,ex,ac)”指针对比断言函数:
tc是指向CuTest结构的指针,代表当前的测试用例。
ex是期望的指针。
ac是实际的指针,即被测试函数的返回值或某个变量的值。如果
ex和ac两个指针相等,测试通过;如果不相等,测试失败,并记录错误信息,包括文件名、行号以及预期值和实际值。
函数使用举例:
CuAssertPtrEquals_Msg (tc,ms,ex,ac)
Section titled “CuAssertPtrEquals_Msg (tc,ms,ex,ac)”在
CuAssertPtrEquals()函数的基础上添加错误信息打印。
ms是一个字符串,描述了失败时的额外信息。
函数使用举例:
CuAssertPtrNotNull (tc,p)
Section titled “CuAssertPtrNotNull (tc,p)”指针对比断言函数:
tc是指向CuTest结构的指针,代表当前的测试用例。
p是需要检查的指针。如果
p不是NULL,测试通过;如果是NULL,测试失败,并记录错误信息,包括文件名、行号以及一个默认的错误消息或自定义的错误消息。
函数使用举例:
CuAssertPtrNotNull_Msg (tc,msg,p)
Section titled “CuAssertPtrNotNull_Msg (tc,msg,p)”在
CuAssertPtrNotNull()函数的基础上添加错误信息打印。
ms是一个字符串,描述了失败时的额外信息。
函数使用举例:
CuAssertArrEquals (tc, ex, ac, len)
Section titled “CuAssertArrEquals (tc, ex, ac, len)”数组对比断言函数:
tc是指向CuTest结构的指针,代表当前的测试用例。
ex是期望的数组。
ac是实际的数组,即被测试函数的返回值或某个变量的值。
len是要对比的长度。如果
ex和ac两个数组在len长度内完全相同,测试通过;如果不相等,测试失败,并记录错误信息,包括文件名、行号以及具体位置预期值和实际值的差异。
函数使用举例:
CuAssertArrEquals_Msg (tc, ms, ex, ac, len)
Section titled “CuAssertArrEquals_Msg (tc, ms, ex, ac, len)”在
CuAssertArrEquals函数的基础上添加错误信息打印。
ms是一个字符串,描述了失败时的额外信息。
函数使用举例:
CREATE_ASSERTS
Section titled “CREATE_ASSERTS”宏函数创建辅助函数。
函数说明使用
Section titled “函数说明使用”setjmp.h 相关
Section titled “setjmp.h 相关”实例
jmp_buf 类型
Section titled “jmp_buf 类型”
jmp_buf是一个数据类型,用于保存调用环境,包括栈指针、指令指针和寄存器等。在执行setjmp()时,这些环境信息会被保存到jmp_buf类型的变量中。
int setjmp (jmp_buf environment)
Section titled “int setjmp (jmp_buf environment)”这个宏把当前环境保存在变量
environment中,以便函数longjmp()后续使用。如果这个宏直接从宏调用中返回,则它会返回零,但是如果它从longjmp()函数调用中返回,则它会返回一个非零值。
void longjmp (jmp_buf environment, int value)
Section titled “void longjmp (jmp_buf environment, int value)”该函数恢复最近一次调用
setjmp()宏时保存的环境,jmp_buf参数的设置是由之前调用setjmp()生成的。
Cutest 例程解析
Section titled “Cutest 例程解析”AllTest.c
Section titled “AllTest.c”CuTestTest.c
Section titled “CuTestTest.c”- 创建新字符串
- 字符串拼接
注意此处的字符串长度并不包含字符串结束时的
/0。
- 测试框架对 NULL 的处理
- 字符拼接
- 在不同的位置插入字符串
- 缓存区大小检查
str->size为CuStringNew()创建出对象的字符串缓存区大小。当目前开辟的缓存区大小不够但是系统的空间充足时 cutest 会调用
CuStringResize额外增加缓存区。
- 创建并返回一个测试套件,用于测试 CuString 的功能
CuGetSuite函数中均为测试框架函数中的一些运用。
… …
查找源文件:脚本会检查当前目录中的所有
.c文件,或者使用命令行参数指定的文件。生成头文件:在生成的文件开头插入自动生成的代码提示、标准库包含和 CuTest 头文件的引入。
提取测试函数:脚本会查找所有以
void Test开头的函数,提取其函数名并生成相应的extern声明。这是为了使测试函数在其他文件中可见。构建测试套件:为每个测试函数生成
SUITE_ADD_TEST调用,以将这些测试添加到测试套件中。运行所有测试:在
RunAllTests函数中,创建一个输出字符串和一个测试套件,然后运行所有添加的测试,输出测试结果,并清理资源。主函数:脚本在生成的文件中包含一个
main函数,调用RunAllTests,从而执行所有测试。
Windows
Section titled “Windows”中文 README 文档 (原项目)
Section titled “中文 README 文档 (原项目)”如何使用
您可以使用 CuTest 创建单元测试,以推动您的开发,采用极限编程的风格。您还可以为现有代码添加单元测试,以确保其按预期工作。
您的单元测试是一次投资。它们让您能够自信地更改代码和添加新功能,而不必担心意外破坏早期功能。
许可
有关许可的详细信息,请参见 license.txt。
入门
要将单元测试添加到您的 C 代码中,您只需 CuTest.c 和 CuTest.h 文件。
CuTestTest.c 和 AllTests.c 已包含在内,以提供如何编写单元测试的示例,以及如何将它们聚合到套件中并合并到单个 AllTests.c 文件中。套件允许您将组测试放入逻辑集合中。AllTests.c 组合所有套件并运行它们。
您不需要查看 CuTest.c。查看 CuTestTest.c 和 AllTests.c(以获取示例用法)应该就足够了。
下载源代码后,运行编译器以创建名为 AllTests.exe 的可执行文件。例如,如果您使用的是带有 cl.exe 编译器的 Windows,您可以输入:
这将运行与 CuTest 相关的所有单元测试并在控制台上打印输出。您可以在上面的命令中将 cl.exe 替换为 gcc 或您喜欢的其他编译器。
详细示例
这是一个更详细的示例。我们将首先进行一个简单的测试练习。目标是创建一个字符串工具库。首先,让我们编写一个将以 null 结尾的字符串转换为全大写的函数。
确保 CuTest.c 和 CuTest.h 可以在您的 C 项目中访问。接下来,创建一个名为 StrUtil.c 的文件,内容如下:
创建另一个名为 AllTests.c 的文件,内容如下:
然后在命令行输入:
以进行编译。您可以用您喜欢的编译器替换 gcc。CuTest 应该足够可移植,可以处理所有 Windows 和 Unix 编译器。然后要运行测试,输入:
这将打印一个错误,因为我们尚未正确实现 StrToUpper 函数。我们只是返回字符串,而没有将其转换为大写。
将其重写如下:
重新编译并再次运行测试。这次测试应该通过。
接下来该做什么
此时,您可能想为 StrToUpper 函数编写更多测试。以下是一些想法:
- TestStrToUpper_EmptyString:传入 ""
- TestStrToUpper_UpperCase:传入 “HELLO WORLD”
- TestStrToUpper_MixedCase:传入 “HELLO world”
- TestStrToUpper_Numbers:传入 “1234 hello”
在编写每个测试时,将其添加到 StrUtilGetSuite 函数中。如果不这样做,测试将不会运行。稍后在编写其他函数和测试时,请务必也将它们包含在 StrUtilGetSuite 中。StrUtilGetSuite 函数应包含 StrUtil.c 中的所有测试。
随着时间的推移,您将创建另一个名为 FunkyStuff.c 的文件,其中包含与 StrUtil 无关的其他函数。遵循相同的模式。在 FunkyStuff.c 中创建 FunkyStuffGetSuite 函数,并将 FunkyStuffGetSuite 添加到 AllTests.c 中。
该框架的设计方式使得组织大量测试变得容易。
大局
每个单独的测试对应于一个 CuTest。这些被分组形成 CuSuite。CuSuites 可以包含 CuTests 或其他 CuSuites。AllTests.c 将程序中的所有 CuSuites 收集到一个单一的 CuSuite 中,然后作为一个 CuSuite 运行。
该项目是开源的,因此可以随意查看 CuTest.c 文件的底层实现,以了解其工作原理。CuTestTest.c 包含对 CuTest.c 的测试。因此,CuTest 自己进行测试。
由于 AllTests.c 中有一个 main(),您在构建产品时需要排除它。如果您希望避免处理多个构建,这里有一个更好的方法。删除 AllTests.c 中的 main()。请注意,它只是调用 RunAllTests()。相反,我们将直接从主程序中调用此函数。
现在在实际程序的 main() 中检查是否传入了命令行选项 “—test”。如果是,则从 AllTests.c 调用 RunAllTests()。否则,运行实际程序。
将测试与代码一起发布是有用的。如果您的客户抱怨问题,可以让他们运行单元测试并将输出发送给您。这可以帮助您快速定位客户环境中出现故障的系统部分。
CuTest 提供了一系列丰富的 CuAssert 函数。以下是列表:
该项目是开源的,因此您可以添加其他更强大的断言,以使测试更易于编写且更简洁。请随时向我发送您所做的更改,以便我可以将其纳入未来的版本中。
如果您发现本文档中的任何错误,请联系我:asimjalis@peakprogramming.com。
自动化测试套件生成
make-tests.sh 将在当前目录中的所有 .c 文件中进行 grep,并生成运行其中包含的所有测试的代码。使用此脚本,您无需担心编写 AllTests.c 或处理其他套件代码。
致谢
以下人员为 CuTest 项目贡献了有用的代码更改。感谢他们!
- [2003.02.23] Dave Glowacki dglo@hyde.ssec.wisc.edu
- [2009.04.17] Tobias Lippert herrmarder@googlemail.com
- [2009.11.13] Eli Bendersky eliben@gmail.com
- [2009.12.14] Andrew Brown abrown@datasci.com