翻译的匆忙,没有校验,纰漏之处感谢请指正。转载请注明来自:see-see ,版权归原文 所有。
Cmockery 是一个轻量级的库,作者用它来做C单元测试。
如今已经有很多的C单元测试框架可用,但大多数框架相当复杂,并且对最新的编译器技术有所依赖。有些开发会使用老版本的编译器, 这样就很难使用那些测试框架。另外,许多测试框架会假设待测代码(程序或模块)执行的平台是同一个。正因为这种假设,很多测试框架需要在待测模块中引入C标准库的头文件,这不符合惯例,或者在待测模块中实现一个不完全的C库。
Cmockery 只需要测试程序与标准C库链接,这能最大限度地减少与标准库头文件的冲突。此外, Cmockery尽量避免使用编译器中比较新的一些特性。
因此,Cmockery就成了一个可用于测试各种不同的代码轻量级的库。如果开发者只是想简单地测试一个程序,并且可以使用最新的编译器,其他单元测试框架也是可取的。
Cmockery 的测试与Cmockery库,标准C库,待测模块链接在一起,最总被编译成一个可以独立运行的程序。在测试过程中,待测模块的任何外部信息都应被模拟,即用测试用例中定义的函数返回值来替换。即使会出现待测代码在实际运行环境和测试环境运行时有差异,仍可视为有效。因为它的目的在于代码模块在功能面上的逻辑测试,不必要求所有的行为都和目标环境一致。
如果不做一些修改,无法将一个模块编译成可执行程序。因此 UNIT_TESTING 预定义应当定义在执行Cmockery单元测试的应用程序中, 编译待测代码时,可用条件编译决定是否参与单元测试。
Cmockery单元测试用例就是函数,签名为void function(void **state) . Cmockery 测试程序将(多个)测试用例的函数指针初始化到一个表中,使用unit_test*() 宏. 这个表会传给 run_tests() 宏来执行测试用例。 run_tests() 将适当的异常/信号句柄,以及其他数据结构的指针装入到测试函数。当单元测试结束时, run_tests() 会显示出各种定义的测试是否成功。
run_tests.c -------------------------------------------------------------------------------
在测试函数被run_tests() 执行之前,断言/信号句柄会被一个句柄重载,它可以在异常发生时,显示错误并退出测试函数。 如果一个异常发生在测试函数的外部,例如Cmockery本身,程序将终止执行并返回错误码。
当测试函数执行run_tests() 时,如果错误出现了,当前的测试函数将中断,测试程序继续执行下一测试函数。测试失败的通过CMockery函数fail() 给出最终的标志。导致Cmockery库测试失败的事件如下:
运行时的断言宏和C标准库的assert() 相似,需要在待测模块中使用Cmockery的mock_assert() 函数重定义。 通常mock_assert() 表示 测试的失败 . 如果一个函数被expect_assert_failure() 宏调用, 那么在这个函数中就要调用mock_assert() ,测试将进行。若没有调用mock_assert() ,表示次函数测试失败。
assert_module.c -------------------------------------------------------------------------------
assert_module_test.c -------------------------------------------------------------------------------
Cmockery提供了一系列的断言宏,在测试程序的使用方法和C标准中的用法一致。 当断言错误发生时,Cmockery的断言宏会将这个这个错误输出到标准错误流,并把这个测试标记为失败。 由于标准C库中assert()的是限制,Cmockery的assert_true() 和 assert_false() 宏只能显示导致断言失败的表达式。Cmockery中和具体类型相关的断言宏, assert_{类型}_equal() and assert_{类型}_not_equal(), 显示那些导致断言失败的数据, 这样可以增加数据的可视化,辅助调试那些出错的测试用例。
assert_macro.c -------------------------------------------------------------------------------
assert_macro_test.c ----------------------------------------------------------------------------
为了能用Cmockery测试待测模块中的内存泄漏,缓存溢出和underflows问题,应该将对malloc() 、 calloc() 和free() 的调用分别替换成test_malloc() 、test_calloc() 和 test_free() 。每次释放块内存是使用test_free() ,如果一个内存块被 标记为测试失败 ,它将检测内存崩溃。所有块的内存分配使用 test_*() 函数,Cmockery库将跟踪它们。当测试完成时,如果有任何分配的块没有被释放(内存泄漏), 这些信息会被记录,此测试标记为失败。
为了简单起见,Cmockery会在同一进程中执行所有的测试。因此,一个测试程序的所有测试用例,共用一个单独的地址空间, 也就是说,一个测试用例的内存崩溃,会导致整个测试程序的提前结束。
allocate_module.c -------------------------------------------------------------------------------
allocate_module_test.c -------------------------------------------------------------------------------
一个单元测试最好能将待测函数或模块从外部依赖中隔离。 这就会用到模拟函数,它通过动态或静态方式链接到待测模块中去。 当被测代码直接引用外部函数是,模拟函数必须静态链接。 动态链接是一个简单的过程,将一个函数指针放到一个表中,给待测模块中一个测试用例定义的模拟函数引用。
为了简化模拟函数的实现,Cmockery 提供了给模拟函数的每个测试用例存放返回值的功能,使用的是 will_return() 函数。然后,这些值将通过每个模拟函数调用mock() 返回。 传给will_return() 的值,将分别添加到每个函数所特有的队列中去。连续调用 mock() ,将从函数的队列中移除一个返回值。 这使一个模拟函数通过多次调用mock() ,来返回(多个)输出参数和(一个)返回值成为可能。 此外,一个模拟函数多次调用(多个)返回值的做法也是可以的。
database.h -------------------------------------------------------------------------------
customer_database.c -------------------------------------------------------------------------------
customer_database_test.c -------------------------------------------------------------------------------
除了存储模拟函数的返回值之外,Cmockery还提供了对模拟函数参数期望值的存储功能,使用的是 expect_*()函数,一个模拟函数的参数可以通过check_expected()宏来做有效的验证.
连续调用expect_*()宏,是用队列中的一个参数值来检测给定的参数。 check_expected()检测一个的函数参数,它与expect_*()相对应,即将出队的值。 如果参数检验失败,这个测试将标记为失败。 此外,如果调用check_expected()时,队列中没有参数值出队,测试也会失败。
product_database.c -------------------------------------------------------------------------------
product_database_test.c -------------------------------------------------------------------------------
Cmockery允许每个测试用例多次装载和卸载函数的做法, 装载函数,通过unit_test_setup() 或unit_test_setup_teardown() 宏给出, 支持多个测试用例公共的通用初始化; 此外,卸载函数,通过unit_test_teardown() 或 unit_test_setup_teardown() 宏,给出一个测试用例在执行失败情况下的代码路径。
key_value.c -------------------------------------------------------------------------------
key_value_test.c -------------------------------------------------------------------------------
一个很小基于命令行的计算器程序calculator.c
与计算器程序的测试程序calculator_test.c ,它包含了本文所涉及的所有特性的一个实例。