![](/img/trans.png)
[英]Testing a static function by mocking a non-static function it calls in C
[英]Function mocking (for testing) in C?
我想用 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
将使用的定义square
在foo_test.c
,而不是一个在foo.c
,连接可执行文件,只有当foo_test
? 也就是说,我想将foo.c
编译成一个库(我们称之为libfoo.so
),然后用libfoo.so
和一些魔法编译foo_test.c
,这样我就会得到一个可执行文件foo_test
,它使用了不同的实现square
。
听到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,因此您可以使用 weak 属性:
弱属性导致声明作为弱符号而不是全局符号发出。 这主要用于定义可以在用户代码中覆盖的库函数,尽管它也可以与非函数声明一起使用。 弱符号支持 ELF 目标,也支持使用 GNU 汇编器和链接器时的 a.out 目标。
不,没有解决方案。 如果作用域中存在名称与源文件中的函数调用匹配的函数,则将使用该函数。 没有任何声明技巧会让编译器摆脱它。 当链接器处于活动状态时,名称引用将已被解析。
鉴于square()
可以全局声明,您可以使用Nala测试框架来替换square()
的实现。
所有源代码,包括一个 makefile,都可以在这里找到。
头文件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);
}
};
希望对你有用,祝你好运!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.