(五)cmockery中的assert_module分析

    所分析的assert_module.c和assert_module_test.c文件位于 工程中的 cmockery/src/example/ 目录下,是关于assert方法的一个应用,可以进行判断assert函数中的参数为True还是False。
关于assert(百度百科):
    编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设,可以将断言看作是异常处理的一种高级形式。断言表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真。可以在任何时候启用和禁用断言验证,因此可以在测试时启用断言,而在部署时禁用断言。同样,程序投入运行后,最终用户在遇到问题时可以重新起用断言。
assert使用情况:
    1.可以在预计正常情况下程序不会到达的地方放置断言 :assert false
    2.断言可以用于检查传递给私有方法的参数。(对于公有方法,因为是提供给外部的接口,所以必须在方法中有相应的参数检验才能保证代码的健壮性)
    3.使用断言测试方法执行的前置条件和后置条件
    4.使用断言检查类的不变状态,确保任何情况下,某个变量的状态必须满足。(如age属性应大于0小于某个合适值)
不用断言:
    断言语句不是永远会执行,可以屏蔽也可以启用
因此:
    1.不要使用断言作为公共方法的参数检查,公共方法的参数永远都要执行
     2.断言语句不可以有任何边界效应,不要使用断言语句去修改变量和改变方法的返回值.
C里的宏:assert
    功 能: 测试一个条件并可能使程序终止
    用 法: void assert(int test);
  1. #include<assert.h>
  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. struct ITEM
  5. {
  6. int key;
  7. int value;
  8. };
  9. /*add item to list,make sure list is not null*/
  10. void additem(struct ITEM* itemptr)
  11. {
  12. assert(itemptr!=NULL);
  13. /*additemtolist*/
  14. }
  15. int main(void)
  16. {
  17. additem(NULL);
  18. return 0;
  19. }
注意:assert是宏,而不是函数。在C的assert.h头文件中。
  1. #define assert(expr)
  2. ((expr)
  3. ?__ASSERT_VOID_CAST(0)
  4. :__assert_fail(__STRING(expr),__FILE__,__LINE__,__ASSERT_FUNCTION))
  5. /*DefinedInGlibc2.15*/
    assert的作用是先计算表达式expr,如果其值为假(即为0),那么它会打印出来assert的内容和__FILE__, __LINE__, __ASSERT_FUNCTION,然后执行abort()函数使kernel杀掉自己并coredump(是否生成coredump文件,取决于系统配置);否则,assert()无任何作用。宏assert()一般用于确认程序的正常操作,其中表达式构造无错时才为真值。完成调试后,不必从源代码中删除assert()语句,因为宏NDEBUG有定义时,宏assert()的定义为空。
在调试结束后,可以通过在包含#include <assert.h>的语句之前插入 #define NDEBUG 来禁用assert调用。


assert_module.c
  1. #include <assert.h>
  2. #define UNIT_TESTING 1
  3. // If unit testing is enabled override assert with mock_assert().
  4. #if UNIT_TESTING
  5. extern void mock_assert(const int result, const char* const expression,
  6. const char * const file, const int line);
  7. #undef assert
  8. #define assert(expression)
  9. mock_assert((int)(expression), #expression, __FILE__, __LINE__);
  10. #endif // UNIT_TESTING
  11. void increment_value(int * const value) {
  12. assert(value);
  13. (*value) ++;
  14. }
  15. void decrement_value(int * const value) {
  16. if (value) {
  17. (*value) --;
  18. }
  19. }

    其中的第2行是我添加的,这样就可以将系统的assert函数进行了一个重映射为项目实现函数。当这一行注释掉的时候,使用的为系统的assert函数,集libc里面的宏,运行的结果如下:

在使用这个宏的时候,将assert函数映射为项目里实现的断言功能,那么运行结果如下:


assert_module_test.c
  1. #include <stdarg.h>
  2. #include <stddef.h>
  3. #include <setjmp.h>
  4. #include "cmockery.h"
  5. extern void increment_value(int * const value);
  6. /* This test case will fail but the assert is caught by run_tests() and the
  7. * next test is executed. */
  8. void increment_value_fail(void **state) {
  9. increment_value(NULL);
  10. }
  11. // This test case succeeds since increment_value() asserts on the NULL pointer.
  12. void increment_value_assert(void **state) {
  13. expect_assert_failure(increment_value(NULL));
  14. }
  15. /* This test case fails since decrement_value() doesn't assert on a NULL
  16. * pointer. */
  17. void decrement_value_fail(void **state) {
  18. expect_assert_failure(decrement_value(NULL));
  19. }
  20. int main(int argc, char *argv[]) {
  21. const UnitTest tests[] = {
  22. unit_test(increment_value_fail),
  23. unit_test(increment_value_assert),
  24. unit_test(decrement_value_fail),
  25. };
  26. return run_tests(tests);
  27. }
    对于UnitTest功能之前已经有过了解,所以这里也就是相当于注册了三个测试,其中:
A:    increment_value_fail函数调用increment_value(NULL);也就是相当于执行了assert(NULL)也就是执行了mock_assert(0,“value”,__FILE__, __LINE__
mock_assert
  1. // Replacement for assert.
  2. void mock_assert(const int result, const char* const expression,
  3. const char* const file, const int line) {
  4. if (!result) {
  5. if (global_expecting_assert) {
  6. longjmp(global_expect_assert_env, (int)expression);
  7. } else {
  8. print_error("ASSERT: %s ", expression);
  9. _fail(file, line);
  10. }
  11. }
  12. }
在这里走的是else分支,即打印了“ASSERT:value”之后,就调用_fail函数:
  1. void _fail(const char * const file, const int line) {
  2. print_error("ERROR: " SOURCE_LOCATION_FORMAT " Failure! ", file, line);
  3. exit_test(1);
  4. }
  5. // Printf formatting for source code locations. #define SOURCE_LOCATION_FORMAT "%s:%d"
打印出了“ERROR:filename:line....”,之后调用了exit_test,在exit_test函数中调用longjmp函数进行跳转,即跳到在_run_test函数中以setjmp设置的检测点继续执行;
  1. // Exit the currently executing test.
  2. static void exit_test(const int quit_application) {
  3. if (global_running_test) {
  4. longjmp(global_run_test_env, 1);
  5. } else if (quit_application) {
  6. exit(-1);
  7. }
  8. }

B:    increment_value_assert函数调用了expect_assert_failure(increment_value(NULL));
       decrement_value_fail函数调用了expect_assert_failure(decrement_value(NULL));
    对于expect_assert_failure来说,这个函数其实是一个宏定义如下:
  1. #define expect_assert_failure(function_call)
  2. {
  3. const int expression = setjmp(global_expect_assert_env);
  4. global_expecting_assert = 1;
  5. if (expression) {
  6. print_message("Expected assertion %s occurred ",
  7. *((const char**)&expression));
  8. global_expecting_assert = 0;
  9. } else {
  10. function_call ;
  11. global_expecting_assert = 0;
  12. print_error("Expected assert in %s ", #function_call);
  13. _fail(__FILE__, __LINE__);
  14. }
  15. }
这里将expect_assert_failure(increment_value(NULL));函数进行展开来看,预编译之后,代码如下:
  1. {
  2. const int expression = setjmp(global_expect_assert_env);
  3. global_expecting_assert = 1;
  4. if (expression) {
  5. print_message("Expected assertion %s occurred ",
  6. *((const char**)&expression));
  7. global_expecting_assert = 0;
  8. } else {
  9. increment_value(NULL ;
  10. global_expecting_assert = 0;
  11. print_error("Expected assert in %s ", "increment_value(NULL)");
  12. _fail(__FILE__, __LINE__);
  13. }
  14. }
可以看到这里使用setjmp函数设置了一个全局变量global_expect_assert_env,然后进入else分支里面进行函数调用。按照正常的流程,在这里是执行不到print_error("Expected assert in %s ", "increment_value(NULL)"); 这个语句的,因为我们预期的就是错误,然后会直接在进入到increment_value函数的assert函数中
  1. void mock_assert(const int result, const char* const expression,
  2. const char* const file, const int line) {
  3. if (!result) {
  4. if (global_expecting_assert) {
  5. longjmp(global_expect_assert_env, (int)expression);
  6. } else {
  7. print_error("ASSERT: %s ", expression);
  8. _fail(file, line);
  9. }
  10. }
  11. }
走到了if分支里,即longjmp(global_expect_assert_env, (int)expression);由这个函数将程序计数器设置到expect_assert_failure宏体展开的if分支中,即会打印出print_message("Expected assertion %s occurred ",  *((const char**)&expression)); 这里需要注意,这句话的第二个参数传入的非法,正常做测试的时候会因为这个错误导致不能出现逾期的结果,如果将这句话修改为如下:print_message("Expected assertion %s occurred ",  "aaaaaaaaaaaaa"); 那么程序的运行结果则会变为:

然后退出了当前这次测试。继续进行下一个测试:

同样将expect_assert_failure(decrement_value(NULL));函数进行展开来看,预编译之后,代码如下:
  1. {
  2. const int expression = setjmp(global_expect_assert_env);
  3. global_expecting_assert = 1;
  4. if (expression) {
  5. print_message("Expected assertion %s occurred ",
  6. *((const char**)&expression));
  7. global_expecting_assert = 0;
  8. } else {
  9. decrement_value(NULL);
  10. global_expecting_assert = 0;
  11. print_error("Expected assert in %s ", "decrement_value(NULL)");
  12. _fail(__FILE__, __LINE__);
  13. }
  14. }
可以看到这里使用setjmp函数设置了一个全局变量global_expect_assert_env,然后进入else分支里面进行函数调用。按照正常的流程,在这里是执行不到print_error("Expected assert in %s ", "decrement_value(NULL)"); 这个语句的,因为我们预期的就是错误,然后会直接在decrement_value函数if判断语句为if(NULL)然后什么也不会执行,那么这个函数会正常结束,但是与我们的预期效果  expect_assert_failure 相悖,即没有出现错误,所以会出现该条测试失败,未通过的log信息。






















原文地址:https://www.cnblogs.com/cfzhang/p/7528e6af4efc387b2fd1c135f05a2c28.html