calculator.c
#ifdef HAVE_CONFIG_H#include "config.h"#endif#include <assert.h>#ifdef HAVE_MALLOC_H#include <malloc.h>#endif#include <stdio.h>#include <stdlib.h>#include <string.h>#define UNIT_TESTING 1// If this is being built for a unit test.#if UNIT_TESTING/* Redirect printf to a function in the test application so it's possible to* test the standard output. */#ifdef printf#undef printf#endif // printf#define printf example_test_printfextern void print_message(const char *format, ...);extern int example_test_printf(const char *format, ...);/* Redirect fprintf to a function in the test application so it's possible to* test error messages. */#ifdef fprintf#undef fprintf#endif // fprintf#define fprintf example_test_fprintfextern int example_test_fprintf(FILE * const file, const char *format, ...);// Redirect assert to mock_assert() so assertions can be caught by cmockery.#ifdef assert#undef assert#endif // assert#define assert(expression) \mock_assert((int)(expression), #expression, __FILE__, __LINE__)void mock_assert(const int result, const char* expression, const char *file,const int line);/* Redirect calloc and free to test_calloc() and test_free() so cmockery can* check for memory leaks. */#ifdef calloc#undef calloc#endif // calloc#define calloc(num, size) _test_calloc(num, size, __FILE__, __LINE__)#ifdef free#undef free#endif // free#define free(ptr) _test_free(ptr, __FILE__, __LINE__)void* _test_calloc(const size_t number_of_elements, const size_t size,const char* file, const int line);void _test_free(void* const ptr, const char* file, const int line);/* main is defined in the unit test so redefine name of the the main function* here. */#define main example_main/* All functions in this object need to be exposed to the test application,* so redefine static to nothing. */#define static#endif // UNIT_TESTING// A binary arithmetic integer operation (add, subtract etc.)typedef int (*BinaryOperator)(int a, int b);// Structure which maps operator strings to functions.typedef struct OperatorFunction {const char* operator;BinaryOperator function;} OperatorFunction;int add(int a, int b);int subtract(int a, int b);int multiply(int a, int b);int divide(int a, int b);// Associate operator strings to functions.static OperatorFunction operator_function_map[] = {{"+", add},{"-", subtract},{"*", multiply},{"/", divide},};int add(int a, int b) {return a + b;}int subtract(int a, int b) {return a - b;}int multiply(int a, int b) {return a * b;}int divide(int a, int b) {assert(b); // Check for divide by zero.return a / b;}/* Searches the specified array of operator_functions for the function* associated with the specified operator_string. This function returns the* function associated with operator_string if successful, NULL otherwise.*/BinaryOperator find_operator_function_by_string(const size_t number_of_operator_functions,const OperatorFunction * const operator_functions,const char* const operator_string) {size_t i;assert(!number_of_operator_functions || operator_functions);assert(operator_string);for (i = 0; i < number_of_operator_functions; i++) {const OperatorFunction *const operator_function =&operator_functions[i];if (strcmp(operator_function->operator, operator_string) == 0) {return operator_function->function;}}return NULL;}/* Perform a series of binary arithmetic integer operations with no operator* precedence.** The input expression is specified by arguments which is an array of* containing number_of_arguments strings. Operators invoked by the expression* are specified by the array operator_functions containing* number_of_operator_functions, OperatorFunction structures. The value of* each binary operation is stored in a pointer returned to intermediate_values* which is allocated by malloc().** If successful, this function returns the integer result of the operations.* If an error occurs while performing the operation error_occurred is set to* 1, the operation is aborted and 0 is returned.*/int perform_operation(int number_of_arguments, char *arguments[],const size_t number_of_operator_functions,const OperatorFunction * const operator_functions,int * const number_of_intermediate_values,int ** const intermediate_values, int * const error_occurred) {char *end_of_integer;int value;unsigned int i;assert(!number_of_arguments || arguments);assert(!number_of_operator_functions || operator_functions);assert(error_occurred);assert(number_of_intermediate_values);assert(intermediate_values);*error_occurred = 0;*number_of_intermediate_values = 0;*intermediate_values = NULL;if (!number_of_arguments)return 0;// Parse the first value.value = (int)strtol(arguments[0], &end_of_integer, 10);if (end_of_integer == arguments[0]) {// If an error occurred while parsing the integer.fprintf(stderr, "Unable to parse integer from argument %s\n",arguments[0]);*error_occurred = 1;return 0;}// Allocate an array for the output values.*intermediate_values = calloc(((number_of_arguments - 1) / 2),sizeof(**intermediate_values));i = 1;while (i < number_of_arguments) {int other_value;const char* const operator_string = arguments[i];const BinaryOperator function = find_operator_function_by_string(number_of_operator_functions, operator_functions, operator_string);int * const intermediate_value =&((*intermediate_values)[*number_of_intermediate_values]);(*number_of_intermediate_values) ++;if (!function) {fprintf(stderr, "Unknown operator %s, argument %d\n",operator_string, i);*error_occurred = 1;break;}i ++;if (i == number_of_arguments) {fprintf(stderr, "Binary operator %s missing argument\n",operator_string);*error_occurred = 1;break;}other_value = (int)strtol(arguments[i], &end_of_integer, 10);if (end_of_integer == arguments[i]) {// If an error occurred while parsing the integer.fprintf(stderr, "Unable to parse integer %s of argument %d\n",arguments[i], i);*error_occurred = 1;break;}i ++;// Perform the operation and store the intermediate value.*intermediate_value = function(value, other_value);value = *intermediate_value;}if (*error_occurred) {free(*intermediate_values);*intermediate_values = NULL;*number_of_intermediate_values = 0;return 0;}return value;}int main(int argc, char *argv[]) {int return_value;int number_of_intermediate_values;int *intermediate_values;// Peform the operation.const int result = perform_operation(argc - 1, &argv[1],sizeof(operator_function_map) / sizeof(operator_function_map[0]),operator_function_map, &number_of_intermediate_values,&intermediate_values, &return_value);// If no errors occurred display the result.if (!return_value && argc > 1) {unsigned int i;unsigned int intermediate_value_index = 0;printf("%s\n", argv[1]);for (i = 2; i < argc; i += 2) {assert(intermediate_value_index < number_of_intermediate_values);printf(" %s %s = %d\n", argv[i], argv[i + 1],intermediate_values[intermediate_value_index++]);}printf("= %d\n", result);}if (intermediate_values) {free(intermediate_values);}return return_value;}
这个是calculator.c的源码,里面自带了一个main.c程序。这个文件可以直接编译:gcc calculator.c 然后生成 a.out之后 运行就是: ./a.out 1 + 2 - 3 + 4。我在做测试的时候发现不能很好的支持*号。
// A binary arithmetic integer operation (add, subtract etc.)typedef int (*BinaryOperator)(int a, int b);// Structure which maps operator strings to functions.typedef struct OperatorFunction {const char* operator;BinaryOperator function;} OperatorFunction;int add(int a, int b);int subtract(int a, int b);int multiply(int a, int b);int divide(int a, int b);// Associate operator strings to functions.static OperatorFunction operator_function_map[] = {{"+", add},{"-", subtract},{"*", multiply},{"/", divide},};int add(int a, int b) {return a + b;}int subtract(int a, int b) {return a - b;}int multiply(int a, int b) {return a * b;}int divide(int a, int b) {assert(b); // Check for divide by zero.return a / b;}
这样就构造了一个结构体数组,用来保存运算符号和运算方法的一个映射关系,后续只要在operator_function_map数组里面去根据符号找到执行方法就可以了。
find_operator_function_by_string函数就是执行了上面说的这个功能,根据传入的操作符号找到对应的函数,并进行返回函数指针。
perform_operation函数中对于传入的参数进行解析,先解析第一个放到临时变量value中,然后再循环体重进行解析操作符获取计算方法的函数指针,然后再获取另一个操作数,进行执行计算函数,然后返回值会转储到value中方便下次继续使用。肯定有一些错误告警日志信息以及错误判断。最终如果参数无误,将计算的结果进行返回。还有就是在这个函数中会分配一块内存用来存储每一次计算得到的中间值信息。
最后的main函数就是调用测试的过程,后续应该把这个main.c该为example_main.c,否则会在calculator_test.c函数编译的过程中出错。
关于不能执行乘法的解释:
在以./a.out 2 * 3 这个命令行进行执行程序的时候,会将其中*号解析为一个通配符,即当前目录下的所有内容名字的集合。 如果想正常调用乘法的话,可以将 * 号用单引号进行包起来就可以正常的调用了。还有就是这个方法只会从左向右进行计算,不会进行优先级的区分(有兴趣可以进一步完善哟)。
运行结果如下:

calculator_test.c函数中对于calculator.c函数中的方法进行了单元测试,需要在calculator.c的最开始添加一行 #define UNIT_TESTING 1 这样使能单元测试功能。然后执行make:
test : cmockery.c calculator.c calculator_test.cgcc cmockery.c calculator.c calculator_test.c -g -O0 -o testclean:rm -f test
calculator_test.c中的main函数如下:
int main(int argc, char* argv[]) {UnitTest tests[] = {unit_test(test_add),unit_test(test_subtract),unit_test(test_multiply),unit_test(test_divide),unit_test(test_divide_by_zero),unit_test(test_find_operator_function_by_string_null_functions),unit_test(test_find_operator_function_by_string_null_string),unit_test(test_find_operator_function_by_string_valid_null_functions),unit_test(test_find_operator_function_by_string_not_found),unit_test(test_find_operator_function_by_string_found),unit_test(test_perform_operation_null_args),unit_test(test_perform_operation_null_operator_functions),unit_test(test_perform_operation_null_number_of_intermediate_values),unit_test(test_perform_operation_null_intermediate_values),unit_test(test_perform_operation_no_arguments),unit_test(test_perform_operation_first_arg_not_integer),unit_test(test_perform_operation_unknown_operator),unit_test(test_perform_operation_missing_argument),unit_test(test_perform_operation_no_integer_after_operator),unit_test(test_perform_operation),unit_test(test_example_main_no_args),unit_test(test_example_main),};return run_tests(tests);}
其中的前四个单元测试就是简单的一个断言测试,取test_add为例,如下:
// Ensure add() adds two integers correctly.void test_add(void **state) {assert_int_equal(add(3, 3), 6);assert_int_equal(add(3, -3), 0);}
test_divide_by_zero,test_find_operator_function_by_string_null_functions,test_find_operator_function_by_string_null_string测试,使用expect_assert_failure,确认100/0是非法的。否则会无法通过测试。
最核心的两个函数如下,重要的位置都加了注释。
int _run_test(const char * const function_name, const UnitTestFunction Function,void ** const state, const UnitTestFunctionType function_type,const void* const heap_check_point) {const ListNode * const check_point = heap_check_point ?heap_check_point : check_point_allocated_blocks();void *current_state = NULL;int rc = 1;int handle_exceptions = 1;#ifdef _WIN32handle_exceptions = !IsDebuggerPresent();#endif // _WIN32#if UNIT_TESTING_DEBUGhandle_exceptions = 0;#endif // UNIT_TESTING_DEBUGif (handle_exceptions) {#ifndef _WIN32unsigned int i;//注册新号处理函数,出现异常的时候,会跳到exception_handler这里去处理,//然后再exception_handler调用longjmp跳回到setjmp位置继续下一次单元测试。//会返回并保存默认的信号处理方发指针for (i = 0; i < ARRAY_LENGTH(exception_signals); i++) {default_signal_functions[i] = signal(exception_signals[i], exception_handler);}#else // _WIN32previous_exception_filter = SetUnhandledExceptionFilter(exception_filter);#endif // !_WIN32}if (function_type == UNIT_TEST_FUNCTION_TYPE_TEST) {print_message("%s: Starting test\n", function_name);}initialize_testing(function_name);global_running_test = 1;//在这里进行的setjmp,后续如果出现新号或者错误处理,后会返回这里继续else部分的执行。if (setjmp(global_run_test_env) == 0) {Function(state ? state : ¤t_state);fail_if_leftover_values(function_name);/* If this is a setup function then ignore any allocated blocks* only ensure they're deallocated on tear down. */if (function_type != UNIT_TEST_FUNCTION_TYPE_SETUP) {fail_if_blocks_allocated(check_point, function_name);}global_running_test = 0;if (function_type == UNIT_TEST_FUNCTION_TYPE_TEST) {print_message("%s: Test completed successfully.\n", function_name);}rc = 0;} else {global_running_test = 0;print_message("%s: Test failed.\n", function_name);}teardown_testing(function_name);if (handle_exceptions) {#ifndef _WIN32unsigned int i;//在这里回复默认的异常处理函数,每一个单元测试都要这么做一次。for (i = 0; i < ARRAY_LENGTH(exception_signals); i++) {signal(exception_signals[i], default_signal_functions[i]);}#else // _WIN32if (previous_exception_filter) {SetUnhandledExceptionFilter(previous_exception_filter);previous_exception_filter = NULL;}#endif // !_WIN32}return rc;}int _run_tests(const UnitTest * const tests, const size_t number_of_tests) {print_message("%d\n",sizeof(int));// 是否执行下一个测试.int run_next_test = 1;// 是否前一个测试执行失败了.int previous_test_failed = 0;//检查堆指针的状态const ListNode * const check_point = check_point_allocated_blocks();//当前的测试索引size_t current_test = 0;//已经执行过测试的个数size_t tests_executed = 0;//测试失败的数目size_t total_failed = 0;// setup 函数的个数size_t setups = 0;//(setup主要实现测试前的初始化工作,teardown主要实现测试完成后的垃圾回收工作)// teardown 函数的个数size_t teardowns = 0;/* 一个测试状态的栈. 一个状态是当测试的setup发生时入栈* 另个是当测试的teardown发生时出栈 */TestState* test_states = malloc(number_of_tests * sizeof(*test_states));size_t number_of_test_states = 0;//失败测试的名字指针数组,最多为所有的都失败const char** failed_names = malloc(number_of_tests *sizeof(*failed_names));void **current_state = NULL;//确保最大的整数类型要不小于一个指针类型assert_true(sizeof(LargestIntegralType) >= sizeof(void*));while (current_test < number_of_tests){const ListNode *test_check_point = NULL;TestState *current_TestState;const UnitTest * const test = &tests[current_test++];if (!test->function){//如果结构体中的函数指针为空,那么就continuecontinue;}//以run_next_test控制是否为test,区分setup必须首先被执行。switch (test->function_type){case UNIT_TEST_FUNCTION_TYPE_TEST:run_next_test = 1;break;case UNIT_TEST_FUNCTION_TYPE_SETUP:{// Checkpoint the heap before the setup.current_TestState = &test_states[number_of_test_states++];current_TestState->check_point = check_point_allocated_blocks();test_check_point = current_TestState->check_point;current_state = ¤t_TestState->state;*current_state = NULL;run_next_test = 1;setups ++;break;}case UNIT_TEST_FUNCTION_TYPE_TEARDOWN:// Check the heap based on the last setup checkpoint.assert_true(number_of_test_states);current_TestState = &test_states[--number_of_test_states];test_check_point = current_TestState->check_point;current_state = ¤t_TestState->state;teardowns ++;break;default:print_error("Invalid unit test function type %d\n",test->function_type);exit_test(1);break;}if (run_next_test){//进入测试单元进行运行函数int failed = _run_test(test->name, test->function, current_state,test->function_type, test_check_point);if (failed){//记录错误函数的名字,后续会用来记录输出failed_names[total_failed] = test->name;}switch (test->function_type){//统计信息case UNIT_TEST_FUNCTION_TYPE_TEST:previous_test_failed = failed;total_failed += failed;tests_executed ++;break;case UNIT_TEST_FUNCTION_TYPE_SETUP:if (failed){total_failed ++;tests_executed ++;// Skip forward until the next test or setup function.run_next_test = 0;}previous_test_failed = 0;break;case UNIT_TEST_FUNCTION_TYPE_TEARDOWN:// If this test failed.if (failed && !previous_test_failed){total_failed ++;}break;default:assert_false("BUG: shouldn't be here!");break;}}}//输出打印信息,并且释放申请的资源if (total_failed){size_t i;print_error("%d out of %d tests failed!\n", total_failed,tests_executed);for (i = 0; i < total_failed; i++){print_error(" %s\n", failed_names[i]);}}else{print_message("All %d tests passed\n", tests_executed);}if (number_of_test_states){print_error("Mismatched number of setup %d and teardown %d ""functions\n", setups, teardowns);total_failed = -1;}free(test_states);free((void*)failed_names);//如果在这里还没有回复堆区域,那么就说明内存存在泄漏fail_if_blocks_allocated(check_point, "run_tests");return (int)total_failed;}
后续这个测试框架可以以静态库的方式存在于项目中,单独的简历一个测试文件夹,专门用于各个模块的单元测试代码。或者就编译成动态库安装到系统里,都是可以的。