我已经实现了一个在下面使用 C 函数的外观模式,我想对其进行正确测试。
我真的无法控制这些 C 函数。它们在标头中实现。现在我#ifdef 在生产中使用真实的标头,在测试中使用我的模拟标头。C中有没有办法通过覆盖C函数地址或其他方式在运行时交换C函数?我想摆脱代码中的#ifdef。
我已经实现了一个在下面使用 C 函数的外观模式,我想对其进行正确测试。
我真的无法控制这些 C 函数。它们在标头中实现。现在我#ifdef 在生产中使用真实的标头,在测试中使用我的模拟标头。C中有没有办法通过覆盖C函数地址或其他方式在运行时交换C函数?我想摆脱代码中的#ifdef。
要扩展 Bart 的答案,请考虑以下简单示例。
#include <stdio.h>
#include <stdlib.h>
int (*functionPtr)(const char *format, ...);
int myPrintf(const char *fmt, ...)
{
char *tmpFmt = strdup(fmt);
int i;
for (i=0; i<strlen(tmpFmt); i++)
tmpFmt[i] = toupper(tmpFmt[i]);
// notice - we only print an upper case version of the format
// we totally disregard all but the first parameter to the function
printf(tmpFmt);
free(tmpFmt);
}
int main()
{
functionPtr = printf;
functionPtr("Hello world! - %d\n", 2013);
functionPtr = myPrintf;
functionPtr("Hello world! - %d\n", 2013);
return 0;
}
输出
Hello World! - 2013
HELLO WORLD! - %D
奇怪的是,您甚至需要一个 ifdef-selected 标头。要测试的代码和您的模拟应该具有完全相同的函数签名,以便成为要测试的模块的正确模拟。然后在生产编译和测试编译之间唯一改变的是您将哪些.o文件提供给链接器。
可以使用 Typemock Isolator++,而不会创建不必要的新间接级别。它可以在测试内部完成,而无需更改您的生产代码。考虑以下示例:
您的代码中有 Sum 函数:
int Sum(int a, int b)
{
return a+b;
}
并且您想将其替换为 Sigma 进行测试:
int Sigma(int a, int b)
{
int sum = 0;
for( ; 0<a ; a--)
{
sum += b;
}
return sum;
}
在您的测试中,在使用 Sum 之前模拟 Sum:
WHEN_CALLED:调用你想伪造的方法。
ANY_VAL:指定模拟将应用的 args 值。在这种情况下,任何 2 个整数。
*DoStaticOrGlobalInstead:求和的替代行为。在此示例中,我们改为调用 Sigma。
TEST_CLASS(C_Function_Tests)
{
public:
TEST_METHOD(Exchange_a_C_function_implementation_at_run_time_is_Possible)
{
void* context = NULL; //since Sum global it has no context
WHEN_CALLED(Sum (ANY_VAL(int), ANY_VAL(int))).DoStaticOrGlobalInstead(Sigma, context);
Assert::AreEqual(2, Sum(1,2));
}
};
* DoStaticOrGlobalInstead 可以设置其他类型的行为而不是调用替代方法。您可以抛出异常、返回值、忽略方法等...
例如:
TEST_METHOD(Alter_C_Function_Return_Value)
{
WHEN_CALLED(Sum (ANY_VAL(int), ANY_VAL(int))).Return(10);
Assert::AreEqual(10, Sum(1,2));
}
我认为在运行时覆盖函数不是一个好主意。一方面,可执行段可能被设置为只读,即使不是,如果你的程序集太大,你最终可能会踩到另一个函数的代码。
我认为您应该为要使用的一组实现和另一组实现创建类似函数指针集合的东西。每次要调用函数时,都会从选定的函数指针集合中调用。完成此操作后,您可能还拥有代理函数(仅从所选集合中调用)来隐藏函数指针语法。