• (五)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信息。






















  • 相关阅读:
    winform,WPF 释放内存垃圾,减少资源占用方法
    Winform中使用WPF控件并动态读取Xaml
    Winform程序双向绑定
    STM32L15XXX 入门笔记
    STM32固件库下载地址
    C#实现虚拟控件列表显示100w个控件方法
    DotNetBar滚动条的疑似BUG
    VS Sln图标空白修复办法
    Swift下使用Xib设计界面
    关于Mac OS虚拟机下共享文件夹的方法
  • 原文地址:https://www.cnblogs.com/cfzhang/p/7528e6af4efc387b2fd1c135f05a2c28.html
Copyright © 2020-2023  润新知