关于assert(百度百科):
编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设,可以将断言看作是异常处理的一种高级形式。断言表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真。可以在任何时候启用和禁用断言验证,因此可以在测试时启用断言,而在部署时禁用断言。同样,程序投入运行后,最终用户在遇到问题时可以重新起用断言。
assert使用情况:
1.可以在预计正常情况下程序不会到达的地方放置断言 :assert false
2.断言可以用于检查传递给私有方法的参数。(对于公有方法,因为是提供给外部的接口,所以必须在方法中有相应的参数检验才能保证代码的健壮性)
3.使用断言测试方法执行的前置条件和后置条件
4.使用断言检查类的不变状态,确保任何情况下,某个变量的状态必须满足。(如age属性应大于0小于某个合适值)
不用断言:
断言语句不是永远会执行,可以屏蔽也可以启用
因此:
1.不要使用断言作为公共方法的参数检查,公共方法的参数永远都要执行
2.断言语句不可以有任何边界效应,不要使用断言语句去修改变量和改变方法的返回值.
C里的宏:assert
功 能: 测试一个条件并可能使程序终止
用 法: void assert(int test);
#include<assert.h>
#include<stdio.h>
#include<stdlib.h>
struct ITEM
{
int key;
int value;
};
/*add item to list,make sure list is not null*/
void additem(struct ITEM* itemptr)
{
assert(itemptr!=NULL);
/*additemtolist*/
}
int main(void)
{
additem(NULL);
return 0;
}
#define assert(expr)
((expr)
?__ASSERT_VOID_CAST(0)
:__assert_fail(__STRING(expr),__FILE__,__LINE__,__ASSERT_FUNCTION))
/*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
#include <assert.h>
#define UNIT_TESTING 1
// If unit testing is enabled override assert with mock_assert().
#if UNIT_TESTING
extern void mock_assert(const int result, const char* const expression,
const char * const file, const int line);
#undef assert
#define assert(expression)
mock_assert((int)(expression), #expression, __FILE__, __LINE__);
#endif // UNIT_TESTING
void increment_value(int * const value) {
assert(value);
(*value) ++;
}
void decrement_value(int * const value) {
if (value) {
(*value) --;
}
}
其中的第2行是我添加的,这样就可以将系统的assert函数进行了一个重映射为项目实现函数。当这一行注释掉的时候,使用的为系统的assert函数,集libc里面的宏,运行的结果如下:
在使用这个宏的时候,将assert函数映射为项目里实现的断言功能,那么运行结果如下:
assert_module_test.c
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include "cmockery.h"
extern void increment_value(int * const value);
/* This test case will fail but the assert is caught by run_tests() and the
* next test is executed. */
void increment_value_fail(void **state) {
increment_value(NULL);
}
// This test case succeeds since increment_value() asserts on the NULL pointer.
void increment_value_assert(void **state) {
expect_assert_failure(increment_value(NULL));
}
/* This test case fails since decrement_value() doesn't assert on a NULL
* pointer. */
void decrement_value_fail(void **state) {
expect_assert_failure(decrement_value(NULL));
}
int main(int argc, char *argv[]) {
const UnitTest tests[] = {
unit_test(increment_value_fail),
unit_test(increment_value_assert),
unit_test(decrement_value_fail),
};
return run_tests(tests);
}
对于UnitTest功能之前已经有过了解,所以这里也就是相当于注册了三个测试,其中:
A: increment_value_fail函数调用increment_value(NULL);也就是相当于执行了assert(NULL)也就是执行了mock_assert(0,“value”,__FILE__, __LINE__);
mock_assert
// Replacement for assert.
void mock_assert(const int result, const char* const expression,
const char* const file, const int line) {
if (!result) {
if (global_expecting_assert) {
longjmp(global_expect_assert_env, (int)expression);
} else {
print_error("ASSERT: %s ", expression);
_fail(file, line);
}
}
}
在这里走的是else分支,即打印了“ASSERT:value”之后,就调用_fail函数:
void _fail(const char * const file, const int line) {
print_error("ERROR: " SOURCE_LOCATION_FORMAT " Failure! ", file, line);
exit_test(1);
}
// Printf formatting for source code locations. #define SOURCE_LOCATION_FORMAT "%s:%d"
打印出了“ERROR:filename:line....”,之后调用了exit_test,在exit_test函数中调用longjmp函数进行跳转,即跳到在_run_test函数中以setjmp设置的检测点继续执行;
// Exit the currently executing test.
static void exit_test(const int quit_application) {
if (global_running_test) {
longjmp(global_run_test_env, 1);
} else if (quit_application) {
exit(-1);
}
}
B: increment_value_assert函数调用了expect_assert_failure(increment_value(NULL));
decrement_value_fail函数调用了expect_assert_failure(decrement_value(NULL));
对于expect_assert_failure来说,这个函数其实是一个宏定义如下:
#define expect_assert_failure(function_call)
{
const int expression = setjmp(global_expect_assert_env);
global_expecting_assert = 1;
if (expression) {
print_message("Expected assertion %s occurred ",
*((const char**)&expression));
global_expecting_assert = 0;
} else {
function_call ;
global_expecting_assert = 0;
print_error("Expected assert in %s ", #function_call);
_fail(__FILE__, __LINE__);
}
}
这里将expect_assert_failure(increment_value(NULL));函数进行展开来看,预编译之后,代码如下:
{
const int expression = setjmp(global_expect_assert_env);
global_expecting_assert = 1;
if (expression) {
print_message("Expected assertion %s occurred ",
*((const char**)&expression));
global_expecting_assert = 0;
} else {
increment_value(NULL) ;
global_expecting_assert = 0;
print_error("Expected assert in %s ", "increment_value(NULL)");
_fail(__FILE__, __LINE__);
}
}
可以看到这里使用setjmp函数设置了一个全局变量global_expect_assert_env,然后进入else分支里面进行函数调用。按照正常的流程,在这里是执行不到print_error("Expected assert in %s
", "increment_value(NULL)"); 这个语句的,因为我们预期的就是错误,然后会直接在进入到increment_value函数的assert函数中
void mock_assert(const int result, const char* const expression,
const char* const file, const int line) {
if (!result) {
if (global_expecting_assert) {
longjmp(global_expect_assert_env, (int)expression);
} else {
print_error("ASSERT: %s ", expression);
_fail(file, line);
}
}
}
走到了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));函数进行展开来看,预编译之后,代码如下:
{
const int expression = setjmp(global_expect_assert_env);
global_expecting_assert = 1;
if (expression) {
print_message("Expected assertion %s occurred ",
*((const char**)&expression));
global_expecting_assert = 0;
} else {
decrement_value(NULL);
global_expecting_assert = 0;
print_error("Expected assert in %s ", "decrement_value(NULL)");
_fail(__FILE__, __LINE__);
}
}
可以看到这里使用setjmp函数设置了一个全局变量global_expect_assert_env,然后进入else分支里面进行函数调用。按照正常的流程,在这里是执行不到print_error("Expected assert in %s
", "decrement_value(NULL)"); 这个语句的,因为我们预期的就是错误,然后会直接在decrement_value函数if判断语句为if(NULL)然后什么也不会执行,那么这个函数会正常结束,但是与我们的预期效果 expect_assert_failure 相悖,即没有出现错误,所以会出现该条测试失败,未通过的log信息。