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_printf
extern 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_fprintf
extern 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.c
gcc cmockery.c calculator.c calculator_test.c -g -O0 -o test
clean:
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 _WIN32
handle_exceptions = !IsDebuggerPresent();
#endif // _WIN32
#if UNIT_TESTING_DEBUG
handle_exceptions = 0;
#endif // UNIT_TESTING_DEBUG
if (handle_exceptions) {
#ifndef _WIN32
unsigned 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 // _WIN32
previous_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 _WIN32
unsigned int i;
//在這里回復默認的異常處理函數,每一個單元測試都要這么做一次。
for (i = 0; i < ARRAY_LENGTH(exception_signals); i++) {
signal(exception_signals[i], default_signal_functions[i]);
}
#else // _WIN32
if (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)
{//如果結構體中的函數指針為空,那么就continue
continue;
}
//以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;
}
后續這個測試框架可以以靜態庫的方式存在于項目中,單獨的簡歷一個測試文件夾,專門用于各個模塊的單元測試代碼。或者就編譯成動態庫安裝到系統里,都是可以的。