我想在C中为C库编写测试。我想为测试模拟一些函数。
假设我的库是从以下来源编译的:
/* foo.h */
int myfunction(int x, int y);
/* foo.c */
#include "foo.h"
static int square(int x) { return x * x; }
int myfunction(int x, int y) {
return square(x) + square(y);
}
我想写一个这样的测试:
/* foo_test.c */
#include "foo.h"
static int square(int x) { return x + 1; }
int main(void) {
assert(myfunction(0, 0) == 2);
return 0;
}
有没有什么方法可以编译,使myfunction
只在链接可执行文件foo_test
时使用foo_test.c
中square
的定义,而不是foo.c
中的定义?也就是说,我想把foo.c
编译成一个库(让我们称之为libfoo.so
),然后用libfoo.so
和一些魔术编译foo_test.c
,这样我就会得到一个使用square
不同实现的可执行foo_test
。
听到square
未声明为static
时的解决方案会很有帮助,但解决上述情况会更好。
编辑:这似乎没有希望,但这里有一个想法:假设我使用-O0 -g
编译,所以square
不太可能内联,我应该有符号来显示调用的解析位置。是否有方法潜入对象文件并交换已解析的引用?
我编写了Mimick,这是一个用于处理此问题的C函数的嘲讽/存根库。
假设square既不是静态的,也不是内联的(因为否则它会绑定到编译单元和使用它的函数),并且您的函数是在名为"libfoo.so"的共享库中编译的(或者无论您的平台的命名约定是什么),这就是您要做的:
#include <stdlib.h>
#include <assert.h>
#include <mimick.h>
/* Define the blueprint of a mock identified by `square_mock`
that returns an `int` and takes a `int` parameter. */
mmk_mock_define (square_mock, int, int);
static int add_one(int x) { return x + 1; }
int main(void) {
/* Mock the square function in the foo library using
the `square_mock` blueprint. */
mmk_mock("square@lib:foo", square_mock);
/* Tell the mock to return x + 1 whatever the given parameter is. */
mmk_when(square(mmk_any(int)), .then_call = (mmk_fn) add_one);
/* Alternatively, tell the mock to return 1 if called with 0. */
mmk_when(square(0), .then_return = &(int) { 1 });
assert(myfunction(0, 0) == 2);
mmk_reset(square);
}
不过,这是一个全面的模拟解决方案,如果你只想存根square
(而不关心测试交互),你可以做类似的事情:
#include <stdlib.h>
#include <assert.h>
#include <mimick.h>
static int my_square(int x) { return x + 1; }
int main(void) {
mmk_stub("square@lib:foo", my_square);
assert(myfunction(0, 0) == 2);
mmk_reset(square);
}
Mimick的工作原理是对正在运行的可执行文件进行一些内省,并在运行时破坏全局偏移量表,将函数重定向到我们选择的存根。
看起来你在使用GCC,所以你可以使用弱属性:
弱属性导致声明作为弱符号而不是全局这主要用于定义可以在用户代码中重写的库函数,尽管它可以也可以与非函数声明一起使用。弱符号是支持ELF目标,在使用时也支持a.out目标GNU汇编程序和链接器。
http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html
不,没有解决方案。如果作用域中有一个函数的名称与源文件中的函数调用相匹配,则会使用该函数。没有任何声明技巧会让编译器放弃它。当链接器处于活动状态时,名称引用已经被解析。
假设square()
可以全局声明,则可以使用Nala测试框架来替换square()
的实现。
所有源代码,包括生成文件,都可以在这里获得。
头文件foo.h
。
int square(int x);
int myfunction(int x, int y);
源文件foo.c
。
#include "foo.h"
int square(int x) { return x * x; }
int myfunction(int x, int y) {
return square(x) + square(y);
}
测试文件test_foo.c
,以两种可选方式实现测试;替换实现(按要求)并期待回报。
#include "foo.h"
#include "nala.h"
#include "nala_mocks.h"
static int my_square(int x) { return x + 1; }
/* Replace the implemenation of square(). */
TEST(implementation)
{
square_mock_implementation(my_square);
ASSERT_EQ(myfunction(0, 0), 2);
}
/* Expect square(x=0) and return 1. */
TEST(mock)
{
square_mock(0, 1);
ASSERT_EQ(myfunction(0, 0), 2);
}
在类似您的情况下,我使用Typemock Isolator++API。
它允许您用自己的实现替换方法的原始行为。但是,由于语言的特殊性,您应该避免对测试下的函数和测试中的函数使用相同的名称。
#include "foo.h"
static int square_test(int x) { return x + 1; }
TEST_CLASS(ArgumentTests)
{
public:
TEST_METHOD_CLEANUP(TearDown)
{
ISOLATOR_CLEANUP();
}
TEST_METHOD(TestStaticReplacedStatic)
{
PRIVATE_WHEN_CALLED(NULL, square).DoStaticOrGlobalInstead(square_test, NULL);
Assert::IsTrue(myfunction(0, 0) == 2);
}
};
希望它对你有用,祝你好运!