我只是IT圈的搬运工,项目原址https://github.com/meekrosoft/fff
FFF是一个用宏实现的小框架,只需要一个头文件fff.h,没有任何其他依赖,非常简洁。
FFF的核心就是三个宏:
FAKE_VOID_FUNC(fn [,arg_types*]);
定义一个名为fn的桩函数,返回值为空,有n个参数。
FAKE_VALUE_FUNC(return_type, fn [,arg_types*]);
定义一个名为fn的桩函数,返回值类型为return_type,有n个参数。
RESET_FAKE(fn);
重置桩函数fn的调用信息。
实现上,FFF为桩生成了一系列变量。以带有返回值并且有一个参数的桩函数为例:
#define DECLARE_FAKE_VALUE_FUNC1(RETURN_TYPE, FUNCNAME, ARG0_TYPE) \ EXTERN_C \ typedef struct FUNCNAME##_Fake { \ DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ DECLARE_ALL_FUNC_COMMON \ DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0); \ } FUNCNAME##_Fake;\ extern FUNCNAME##_Fake FUNCNAME##_fake;\ void FUNCNAME##_reset(); \ END_EXTERN_C \
DECLARE_ARG生成了存储当前参数值的变量和存储历史参数值的数组。
#define DECLARE_ARG(type, n, FUNCNAME) \ type arg##n##_val; \ type arg##n##_history[FFF_ARG_HISTORY_LEN];
DECLARE_ALL_FUNC_COMMON定义了桩被调用的次数,参数历史数组的长度以及调用HISTORY_DROPPED的次数。
#define DECLARE_ALL_FUNC_COMMON \ unsigned int call_count; \ unsigned int arg_history_len;\ unsigned int arg_histories_dropped; \
DECLARE_VALUE_FUNCTION_VARIABLES定义了桩函数返回值相关的数据。
custom_fake提供了一个回调接口,可以用自己的函数来构造返回值。
实例:
#include "UI.h" #include "../../fff.h" #include "SYSTEM.h" #include "DISPLAY.h" #include <assert.h> #include <stdio.h> #include <string.h> /* Test Framework :-) */ void setup(); #define TEST_F(SUITE, NAME) void NAME() #define RUN_TEST(SUITE, TESTNAME) printf(" Running %s.%s: \n", #SUITE, #TESTNAME); setup(); TESTNAME(); printf(" SUCCESS\n"); DEFINE_FFF_GLOBALS; /* SYSTEM.h */ FAKE_VOID_FUNC2(SYSTEM_register_irq, irq_func_t, unsigned int); /* DISPLAY.h */ FAKE_VOID_FUNC(DISPLAY_init); FAKE_VOID_FUNC(DISPLAY_clear); FAKE_VOID_FUNC(DISPLAY_output, char *); FAKE_VALUE_FUNC(unsigned int, DISPLAY_get_line_capacity); FAKE_VALUE_FUNC(unsigned int, DISPLAY_get_line_insert_index); FAKE_VOID_FUNC0(button_press_cbk); /* Initialializers called for every test */ void setup() { RESET_FAKE(SYSTEM_register_irq); RESET_FAKE(DISPLAY_init) RESET_FAKE(DISPLAY_clear) RESET_FAKE(DISPLAY_output) RESET_FAKE(DISPLAY_get_line_capacity) RESET_FAKE(DISPLAY_get_line_insert_index); RESET_FAKE(button_press_cbk); FFF_RESET_HISTORY(); DISPLAY_get_line_capacity_fake.return_val = 2; } /* Tests go here */ TEST_F(UITests, init_will_initialise_display) { UI_init(); assert(DISPLAY_init_fake.call_count == 1); } TEST_F(UITests, init_will_register_interrupt_gpio2) { UI_init(); assert(SYSTEM_register_irq_fake.call_count == 1); assert(SYSTEM_register_irq_fake.arg0_val == UI_button_irq_handler); assert(SYSTEM_register_irq_fake.arg1_val == IRQ_GPIO_2); } TEST_F(UITests, when_no_irq_then_missed_irq_counter_zero) { assert(UI_get_missed_irqs() == 0); } TEST_F(UITests, when_one_irq_and_no_handler_then_missed_irq_counter_one) { UI_button_irq_handler(); assert(UI_get_missed_irqs() == 1); } TEST_F(UITests, when_one_irq_and_valid_callback_then_missed_irq_counter_zero) { UI_init(); UI_register_button_cbk(button_press_cbk); UI_button_irq_handler(); assert(UI_get_missed_irqs() == 0); } TEST_F(UITests, when_one_irq_and_valid_callback_then_callback_called) { UI_register_button_cbk(button_press_cbk); UI_button_irq_handler(); assert(button_press_cbk_fake.call_count == 1); } TEST_F(UITests, write_line_outputs_lines_to_display) { char msg[] = "helloworld"; UI_write_line(msg); assert(DISPLAY_output_fake.call_count == 1); assert(strncmp(DISPLAY_output_fake.arg0_val, msg, 26) == 0); } TEST_F(UITests, when_no_empty_lines_write_line_clears_screen_and_outputs_lines_to_display) { DISPLAY_get_line_insert_index_fake.return_val = 2; char msg[] = "helloworld"; UI_write_line(msg); assert(DISPLAY_clear_fake.call_count == 1); assert(DISPLAY_output_fake.call_count == 1); // Check the order of the calls: Don't care about the first two: // DISPLAY_get_line_capacity and DISPLAY_get_line_insert_index assert(fff.call_history_idx == 4); assert(fff.call_history[2] == (void *) DISPLAY_clear); assert(fff.call_history[3] == (void *) DISPLAY_output); } TEST_F(UITests, when_empty_lines_write_line_doesnt_clear_screen) { // given DISPLAY_get_line_insert_index_fake.return_val = 1; char msg[] = "helloworld"; // when UI_write_line(msg); // then assert(DISPLAY_clear_fake.call_count == 0); } TEST_F(UITests, when_string_longer_than_26_then_truncated_string_output) { // given char input[] = "abcdefghijklmnopqrstuvwxyz0123456789"; char expected[] = "abcdefghijklmnopqrstuvwxyz"; // when UI_write_line(input); // then assert(strncmp(expected, DISPLAY_output_fake.arg0_val, 37) == 0); } TEST_F(UITests, when_outputting_to_full_display_then_previous_inserted) { // given DISPLAY_get_line_insert_index_fake.return_val = 1; char oldest[] = "oldest"; char newest[] = "newest"; // when UI_write_line(oldest); UI_write_line(newest); // then assert(DISPLAY_output_fake.call_count == 2); // fills last line assert(strncmp(oldest, DISPLAY_output_fake.arg0_history[0], 37) == 0); //clears assert(DISPLAY_clear_fake.call_count == 1); // inserts old line at first assert(strncmp(oldest, DISPLAY_output_fake.arg0_history[1], 37) == 0); // then inserts new line assert(strncmp(newest, DISPLAY_output_fake.arg0_history[2], 37) == 0); } int main() { setbuf(stdout, NULL); fprintf(stdout, "-------------\n"); fprintf(stdout, "Running Tests\n"); fprintf(stdout, "-------------\n\n"); fflush(0); /* Run tests */ RUN_TEST(UITests, init_will_initialise_display); RUN_TEST(UITests, init_will_register_interrupt_gpio2); RUN_TEST(UITests, when_no_irq_then_missed_irq_counter_zero); RUN_TEST(UITests, when_one_irq_and_no_handler_then_missed_irq_counter_one); RUN_TEST(UITests, when_one_irq_and_valid_callback_then_missed_irq_counter_zero); RUN_TEST(UITests, when_one_irq_and_valid_callback_then_callback_called); RUN_TEST(UITests, write_line_outputs_lines_to_display); RUN_TEST(UITests, when_no_empty_lines_write_line_clears_screen_and_outputs_lines_to_display); RUN_TEST(UITests, when_empty_lines_write_line_doesnt_clear_screen); RUN_TEST(UITests, when_string_longer_than_26_then_truncated_string_output); printf("\n-------------\n"); printf("Complete\n"); printf("-------------\n\n"); return 0; }