I use C language for embedded development. Embedded development is inevitably a development that has one habit and two habits. In such development, it will be important how to deal with the following unit tests.
--How to test hardware-dependent code (such as WiFi module initialization function) --How to test functions that are mixed with non-C standard functions such as Free RTOS
These are solved by injecting the function from the outside. In this article, we'll call it function injection. This is a dependency injection or inspired by the DI pattern.
To be honest, the content uses only the standard functions of C language, so it seems that there is no problem even if you do not match the version.
Create a structure that uses a function pointer, insert it appropriately, and describe up to the point where the function is actually used.
main.c
typedef struct
{
void (*Func1)(void);
int (*Func2)(int a);
} Test_t;
void func1(void) {}
int func2(int a) { return a; }
int FUNC(Test_t test)
{
test.Func1();
printf("%d\r\n", test.Func2(2));
}
int main()
{
Test_t test;
test.Func1 = &func1;
test.Func2 = &func2;
FUNC(test);
return 0;
}
Just in case, I will explain it.
By doing this, Func1 used in FUNC can be replaced in the main function. This should help you understand how you want to pass a function pointer.
I would like to introduce you immediately. Due to the implementation, macros are separated according to the presence or absence of the return value of the function.
I will introduce the common structure first.
mock_macro.h
/**
* @Enumeration type for determining whether the brief function was called
*/
typedef enum
{
/**
* @brief not called
*/
mock_DEFINE_RESULT_NOT_CALL = 0,
/**
* @brief called
*/
mock_DEFINE_RESULT_CALL = 1,
} MOCK_DEFINE_RESULT_t;
mock_macro_v_func_name.h
/**
* @brief Generate the function name you want to dummy
* @param[in]name The name of the mock function
*/
#define V_FUNC_NAME(name) v##name##MacroMOCK
/**
* @brief FUNC_Generate a variable name for the flag that stores the presence or absence of a NAME call
* @param[in]name The name of the mock function
*/
#define V_VARIABLE_NAME(name) gx##name##CallFlag
/**
* @brief V_FUNC_Generate a variable name for the flag that stores the number of NAME calls
* @param[in]name The name of the mock function
*/
#define V_VARIABLE_TIMES_NAME(name) gx##name##MacroCallTimesFlag
/**
* @brief V_FUNC_Generate a function name to get a flag to save whether NAME is called or not
* @param[in]name The name of the mock function
*/
#define V_GET_FUNC_NAME(name) xGet##name##MacroFlag
/**
* @brief V_FUNC_Generate a function name to get a flag to save the number of NAME calls
* @param[in]name The name of the mock function
*/
#define V_GET_FUNC_TIMES_NAME(name) xGet##name##MacroTimesFlag
mock_macro_v_func.h
#include "mock_macro.h"
#include "mock_macro_v_func_name.h"
/**
* @brief V_MOCK_Initialize variables defined in FUNC
* @details
*Of the Test file[Initialization of mock functions using function macros]To use in blocks
* @param[in]name The name of the mock function
*/
#define V_INIT_VARIABLE_FUNC(name) \
V_VARIABLE_NAME(name) = MOCK_DEFINE_RESULT_NOT_CALL; \
V_VARIABLE_TIMES_NAME(name) = 0
/**
* @brief Define mock function[Function with no type to return]
* @details
*Of the Test file[Mock function definition using function macro]To use in blocks
*Description of the function group defined in this macro
* V_MOCK_FUNC_CALL(name, ...)
*This macro is not used directly. The actual function is executed with an injection return.
*
* V_GET_FUNC_POINTER(name)
* V_MOCK_FUNC_Macro to get a function pointer of CALL
*
* V_GET_MOCK_FUNC_CALL(name)
* V_MOCK_FUNC_Macro to check if the CALL function is called
*
* V_GET_MOCK_FUNC_TIMES_CALL(name)
* V_MOCK_FUNC_Macro to check the number of CALL function calls
*
* @param[in]name The name of the mock function
* @param[in] ...Required types and formal parameters of mock functions
*/
#define V_MOCK_FUNC_DEFINITION(name, ...) \
static MOCK_DEFINE_RESULT_t V_VARIABLE_NAME(name); \
static uint32_t V_VARIABLE_TIMES_NAME(name); \
static void V_FUNC_NAME(name)(__VA_ARGS__); \
static void V_FUNC_NAME(name)(__VA_ARGS__) \
{ \
V_VARIABLE_NAME(name) = MOCK_DEFINE_RESULT_CALL; \
V_VARIABLE_TIMES_NAME(name) \
++; \
} \
static MOCK_DEFINE_RESULT_t V_GET_FUNC_NAME(name)(void); \
static MOCK_DEFINE_RESULT_t V_GET_FUNC_NAME(name)(void) \
{ \
return V_VARIABLE_NAME(name); \
} \
static uint32_t V_GET_FUNC_TIMES_NAME(name)(void); \
static uint32_t V_GET_FUNC_TIMES_NAME(name)(void) \
{ \
return V_VARIABLE_TIMES_NAME(name); \
}
/**
* @brief V_MOCK_Call the FUNC mock function
* @param[in]name The name of the mock function
* @param[in] ...Required arguments for mock function
*/
#define V_MOCK_FUNC_CALL(name, ...) \
V_FUNC_NAME(name) \
(__VA_ARGS__)
/**
* @brief Get the flag of whether the mock function was called
* @param[in]name The name of the mock function
*/
#define V_GET_MOCK_FUNC_CALL(name) \
V_GET_FUNC_NAME(name) \
()
/**
* @brief Get the flag of how many times the mock function is called
* @param[in]name The name of the mock function
*/
#define V_GET_MOCK_FUNC_TIMES_CALL(name) \
V_GET_FUNC_TIMES_NAME(name) \
()
/**
* @brief Get the function pointer of a mock function
* @param[in]name The name of the mock function
*/
#define V_GET_FUNC_POINTER(name) \
&V_FUNC_NAME(name)
mock_macro_x_func_name.h
/**
* @brief FUNC_Generate a variable name for the flag that stores the presence or absence of a NAME call
* @param[in]name The name of the mock function
*/
#define X_VARIABLE_NAME(name) gx##name##CallFlag
/**
* @brief FUNC_Generate a variable name for the flag that stores the number of NAME calls
* @param[in]name The name of the mock function
*/
#define X_VARIABLE_TIMES_NAME(name) gx##name##CallTimesFlag
/**
* @brief FUNC_Generate a flag name to make NAME fail
* @param[in]name The name of the mock function
*/
#define X_VARIABLE_NAME_FAIL_FLAG(name) gx##name##FailFlag
/**
* @brief X_FUNC_Array that stores the return data of the NAME function
* @param[in]name The name of the mock function
*/
#define X_FUNC_RESULT_DATA_NAME(name) gx##name##ResultData
/**
* @brief X_FUNC_RESULT_DATA_Function name that sets data in the array of NAME
* @param[in]name The name of the mock function
*/
#define X_SET_RESULT_DATA_FUNC_NAME(name) vSet##name##ResultData
/**
* @brief X_SET_RESULT_DATA_FUNC_Flag name of whether NAME was called
* @param[in]name The name of the mock function
*/
#define X_SET_RESULT_DATA_FUNC_CALL_FLAG_NAME(name) gx##name##ResultDataCallFlag
/**
* @brief X_FUNC_Generate a function name to get the flag of whether to call NAME
* @param[in]name The name of the mock function
*/
#define X_GET_FUNC_NAME(name) xGet##name##MacroFlag
/**
* @brief X_FUNC_Generate a function name to get the flag for the number of NAME calls
* @param[in]name The name of the mock function
*/
#define X_GET_FUNC_TIMES_NAME(name) xGet##name##MacroTimesFlag
/**
* @brief Generate the function name you want to dummy
* @param[in]name The name of the mock function
*/
#define X_FUNC_NAME(name) x##name##MacroMOCK
/**
* @brief X_GET_FUNC_Generate a function name that causes NAME to fail
* @param[in]name The name of the mock function
*/
#define X_FUNC_FAIL_NAME(name) v##name##FailMacroMOCK
/**
* @brief X_FUNC_RESULT_Maximum size of DATA array
*/
#define X_FUNC_RESULT_DATA_LENGTH (5)
mock_macro_x_func.h
#include "mock_macro.h"
#include "mock_macro_x_func_name.h"
/**
* @brief V_MOCK_Initialize variables defined in FUNC
* @details
*Of the Test file[Initialization of mock functions using function macros]To use in blocks
* @param[in]name The name of the mock function
*/
#define X_INIT_VARIABLE_FUNC(name) \
X_VARIABLE_NAME(name) = MOCK_DEFINE_RESULT_NOT_CALL; \
X_VARIABLE_NAME_FAIL_FLAG(name) = MOCK_DEFINE_RESULT_NOT_CALL; \
X_SET_RESULT_DATA_FUNC_CALL_FLAG_NAME(name) = MOCK_DEFINE_RESULT_NOT_CALL; \
X_VARIABLE_TIMES_NAME(name) = 0; \
memset(X_FUNC_RESULT_DATA_NAME(name), 0, sizeof(X_FUNC_RESULT_DATA_NAME(name)))
/**
* @brief Define mock function[Functions with types returned]
* @details
*Of the Test file[Mock function definition using function macro]To use in blocks
*Description of the function group defined in this macro
* X_MOCK_FUNC_CALL(name, ...)
*This macro is not used directly. The actual function is executed with an injection return.
*The return value of this function is subject to the following conditions
*High priority
* 1. X_SET_FUNC_RESULT_When data is defined in CALL, data is returned according to the number of function calls.
* 2. X_MOCK_FUNC_If FAIL is called in advance, the False data specified at the time of definition is returned.
* 3.If none of the above is true, the True data specified at the time of definition will be returned.
*Low priority
*
* X_GET_FUNC_POINTER(name)
* X_MOCK_FUNC_Macro to get a function pointer of CALL
*
* X_GET_MOCK_FUNC_CALL(name)
* X_MOCK_FUNC_Macro to check if you called a CALL function
*
* X_GET_MOCK_FUNC_TIMES_CALL(name)
* X_MOCK_FUNC_Macro to check the number of CALL function calls
*
* X_MOCK_FUNC_FAIL(name)
* X_MOCK_FUNC_Macro to fail CALL function
*
* X_SET_FUNC_RESULT_CALL(name, index, result)
* X_MOCK_FUNC_Macro to set the return value of the CALL function
*index can be specified up to 4 with 0 origin
*
* @param[in]name The name of the mock function
* @param[in]type Return type of mock function
* @param[in]trueValue Return value on success
* @param[in]falseValue Return value on failure
* @param[in] ...Required types and formal parameters of mock functions
*/
#define X_MOCK_FUNC_DEFINITION(name, type, trueValue, falseValue, ...) \
static MOCK_DEFINE_RESULT_t X_VARIABLE_NAME(name); \
static MOCK_DEFINE_RESULT_t X_VARIABLE_NAME_FAIL_FLAG(name); \
static MOCK_DEFINE_RESULT_t X_SET_RESULT_DATA_FUNC_CALL_FLAG_NAME(name); \
static uint32_t X_VARIABLE_TIMES_NAME(name); \
static type X_FUNC_RESULT_DATA_NAME(name)[X_FUNC_RESULT_DATA_LENGTH]; \
static type X_FUNC_NAME(name)(__VA_ARGS__); \
static type X_FUNC_NAME(name)(__VA_ARGS__) \
{ \
X_VARIABLE_NAME(name) = MOCK_DEFINE_RESULT_CALL; \
type xResult = (X_VARIABLE_NAME_FAIL_FLAG(name) == MOCK_DEFINE_RESULT_NOT_CALL) ? trueValue : falseValue; \
xResult = (X_SET_RESULT_DATA_FUNC_CALL_FLAG_NAME(name) == MOCK_DEFINE_RESULT_CALL) ? X_FUNC_RESULT_DATA_NAME(name)[X_VARIABLE_TIMES_NAME(name)] : xResult; \
X_VARIABLE_TIMES_NAME(name) \
++; \
return xResult; \
} \
static MOCK_DEFINE_RESULT_t X_GET_FUNC_NAME(name)(void); \
static MOCK_DEFINE_RESULT_t X_GET_FUNC_NAME(name)(void) \
{ \
return X_VARIABLE_NAME(name); \
} \
static void X_FUNC_FAIL_NAME(name)(void); \
static void X_FUNC_FAIL_NAME(name)(void) \
{ \
X_VARIABLE_NAME_FAIL_FLAG(name) = MOCK_DEFINE_RESULT_CALL; \
} \
static uint32_t X_GET_FUNC_TIMES_NAME(name)(void); \
static uint32_t X_GET_FUNC_TIMES_NAME(name)(void) \
{ \
return X_VARIABLE_TIMES_NAME(name); \
} \
static void X_SET_RESULT_DATA_FUNC_NAME(name)(uint32_t uxIndex, type xData); \
static void X_SET_RESULT_DATA_FUNC_NAME(name)(uint32_t uxIndex, type xData) \
{ \
X_SET_RESULT_DATA_FUNC_CALL_FLAG_NAME(name) = MOCK_DEFINE_RESULT_CALL; \
X_FUNC_RESULT_DATA_NAME(name) \
[uxIndex] = xData; \
}
/**
* @brief X_MOCK_Call the FUNC mock function
* @param[in]name The name of the mock function
* @param[in] ...Required arguments for mock function
*/
#define X_MOCK_FUNC_CALL(name, ...) \
X_FUNC_NAME(name) \
(__VA_ARGS__)
/**
* @brief Get the flag of whether the mock function was called
* @param[in]name The name of the mock function
*/
#define X_GET_MOCK_FUNC_CALL(name) \
X_GET_FUNC_NAME(name) \
()
/**
* @brief Get the mock function call count flag
* @param[in]name The name of the mock function
*/
#define X_GET_MOCK_FUNC_TIMES_CALL(name) \
X_GET_FUNC_TIMES_NAME(name) \
()
/**
* @brief Call a function that causes the mock function to fail
* @param[in]name The name of the mock function
*/
#define X_MOCK_FUNC_FAIL(name) \
X_FUNC_FAIL_NAME(name) \
()
/**
* @brief Call the function that sets the return value of the mock function
* @param[in]name The name of the mock function
* @param[in]index How many times do you want to return(0 origin)
* @param[in]result The data you want to return
*/
#define X_SET_FUNC_RESULT_CALL(name, index, result) \
X_SET_RESULT_DATA_FUNC_NAME(name) \
(index, result)
/**
* @brief Get the function pointer of a mock function
* @param[in]name The name of the mock function
*/
#define X_GET_FUNC_POINTER(name) \
&X_FUNC_NAME(name)
Basically, there is not much difference in usage with or without the return value of the function, so we will explain from the common point. In addition, detailed usage of the function is described in each macro.
--Create a dummy function
V_MOCK_FUNC_DEFINITION
、X_MOCK_FUNC_DEFINITION
--Get the function pointer of the dummy function
V_GET_FUNC_POINTER
、X_GET_FUNC_POINTER
--Initialization of variables used in dummy function
V_INIT_VARIABLE_FUNC
、X_INIT_VARIABLE_FUNC
--Get the flag of whether to call the dummy function
V_GET_MOCK_FUNC_CALL
、X_GET_MOCK_FUNC_CALL
--Get the number of times the dummy function is called
V_GET_MOCK_FUNC_TIMES_CALL
、X_GET_MOCK_FUNC_TIMES_CALL
main.c
#include "stdio.h"
#include "mock_macro.h"
#include "mock_macro_v_func.h"
#include "mock_macro_x_func.h"
typedef struct
{
void (*Func1)(void);
int (*Func2)(int a);
} Test_t;
void func1(void) { printf("func1 \r\n"); }
int func2(int a) { return a; }
// MOCK_FUNC_Since the function is defined by DEFINITION, it is necessary to define it on the main function.
V_MOCK_FUNC_DEFINITION(FUNC1, void);
X_MOCK_FUNC_DEFINITION(FUNC2, int, 1, 0, int a);
int func(Test_t test)
{
test.Func1();
printf("%d\r\n", test.Func2(2));
}
int main()
{
//INIT before using_VARIABLE_Initialize with FUNC
//Originally it is used in unit test, so it will be used in before each etc.
V_INIT_VARIABLE_FUNC(V_TEST);
X_INIT_VARIABLE_FUNC(X_TEST);
// GET_FUNC_MOCK at POINTER_FUNC_You can get a pointer to the function defined in DEFINITION
Test_t mock;
mock.Func1 = V_GET_FUNC_POINTER(FUNC1);
mock.Func2 = X_GET_FUNC_POINTER(FUNC2);
func(mock);
printf("FUNC1 Call: %s \r\n", MOCK_DEFINE_RESULT_CALL == V_GET_MOCK_FUNC_CALL(FUNC1) ? "TRUE" : "FALSE");
printf("FUNC1 Call Times: %d \r\n", V_GET_MOCK_FUNC_TIMES_CALL(FUNC1));
printf("FUNC2 Call: %s \r\n", MOCK_DEFINE_RESULT_CALL == X_GET_MOCK_FUNC_CALL(FUNC2) ? "TRUE" : "FALSE");
printf("FUNC2 Call Times: %d \r\n", X_GET_MOCK_FUNC_TIMES_CALL(FUNC2));
return 0;
}
In the above example, the mock of the func1
function is created with the name FUNC1
.
When you want to use the original function, pass the function pointer of the correct function like Test_t test
.
When passing a mock function for testing, you need to do something like Test_t mock
.
You can get a function pointer by using a macro of GET_FUNC_POINTER'series. By using
GET_MOCK_FUNC_CALL and
GET_MOCK_FUNC_TIMES_CALL`, you can get the function call presence / absence and the number of calls.
With this, it is possible to evaluate whether the injected function has been called a specified number of times.
--When failing a dummy function
X_MOCK_FUNC_FAIL
--When you want to change the return value of the dummy function according to the number of calls
X_SET_FUNC_RESULT_CALL
main.c
#include "stdio.h"
#include "mock_macro.h"
#include "mock_macro_v_func.h"
#include "mock_macro_x_func.h"
typedef struct
{
int (*Func2)(int a);
} Test_t;
int func2(int a) { return a; }
// MOCK_FUNC_Since the function is defined by DEFINITION, it is necessary to define it on the main function.
X_MOCK_FUNC_DEFINITION(FUNC2, int, 1, 0, int a);
int func(Test_t test)
{
printf("%d\r\n", test.Func2(2));
}
int main()
{
//INIT before using_VARIABLE_Initialize with FUNC
//Originally it is used in unit test, so it will be used in before each etc.
X_INIT_VARIABLE_FUNC(X_TEST);
// GET_FUNC_MOCK at POINTER_FUNC_You can get a pointer to the function defined in DEFINITION
Test_t mock;
mock.Func2 = X_GET_FUNC_POINTER(FUNC2);
#if 0
//X before the FUNC2 mock function is called_MOCK_FUNC_You can make the function fail by calling FAIL
X_MOCK_FUNC_FAIL(FUNC2);
#else
//Instead of failing, you can change the value returned depending on the number of calls
X_SET_FUNC_RESULT_CALL(FUNC2, 0, 1);
X_SET_FUNC_RESULT_CALL(FUNC2, 1, 10);
X_SET_FUNC_RESULT_CALL(FUNC2, 2, 100);
X_SET_FUNC_RESULT_CALL(FUNC2, 3, 1000);
X_SET_FUNC_RESULT_CALL(FUNC2, 4, 10000);
#endif
func(mock);
printf("FUNC2 Call: %s \r\n", MOCK_DEFINE_RESULT_CALL == X_GET_MOCK_FUNC_CALL(FUNC2) ? "TRUE" : "FALSE");
printf("FUNC2 Call Times: %d \r\n", X_GET_MOCK_FUNC_TIMES_CALL(FUNC2));
return 0;
}
By calling X_MOCK_FUNC_FAIL
in advance as described above, the variable specified when defining the function can be returned.
Also, by using X_SET_FUNC_RESULT_CALL
, the return value can be set according to the number of calls.
One thing to keep in mind with this macro is that X_MOCK_FUNC_FAIL
and X_SET_FUNC_RESULT_CALL
have priority and data is returned in the following order.
X_SET_FUNC_RESULT_CALL
, the data is returned according to the number of function calls.X_MOCK_FUNC_FAIL
is called in advance, the False data specified at the time of definition will be returned.Recommended Posts