编写单元测试并需要控制 c++ 链接器



我正在为一个依赖于第三方 C 和 C++ 库以及第一方 C 库的 C++ 类编写单元测试。我遇到了麻烦,因为我希望被测类使用模拟的第三方 C 库,而测试运行器使用真正的第 3 方 C 库。

  1. 我正在编写单元测试,被测试的类依赖于libx
  2. 我创建了一个libmockx,它允许我测试参数并注入返回值。
  3. 被测试的类需要链接到libmockx以便我可以检查和控制它的行为。
  4. 单元测试应用程序需要链接到libx才能制定/解析libx数据类型。

使用哪种模式或方法将测试运行程序链接到libx,将被测类链接到libmockx,然后将测试运行程序链接到被测试的类?许多解决方案都讨论让链接器做"肮脏的工作",但ld有 100 多个参数,我不知道如何让它工作。

目前,我对所有模拟实现都有重新定义错误,我需要一种方法来解决它(无论是否涉及链接器)。

编辑如下: (回应评论)

想象一下 7 个文件:

  • test.cpp- 测试运行程序
  • object.cpp- 受测object
  • object.hpp-object标头
  • mock-parameters.h- 提供对测试的模拟参数访问
  • mock-x.c-x的模拟实现
  • x.c-x实施
  • x.h-x标头

在我需要实例化和操作x对象之前,我能够通过对g++的单个调用来编译我的测试:

g++ test.cpp object.cpp mock_x.c

我正在尝试向test.cpp添加一个测试,它将提供和测试一个x对象结果值。现在,我需要将test.cppx.c链接,同时仍然将object.cppmock_x.c链接。

当我将x.c添加到编译列表时,出现以下(预期)错误:

/usr/bin/ld: /tmp/ccDVQxtN.o: in function `set_foo(X*, int)':
mock-x.c:(.text+0x0): multiple definition of `set_foo(X*, int)'; /tmp/cc3t8CjP.o:x.c:(.text+0x14): first defined here
collect2: error: ld returned 1 exit status

x.h

#ifndef X_H
#define X_H
typedef struct X {
int foo;
char bar;
} X;
X * create_x (void);
int set_foo (X *, int);
char set_bar (X *, char);
void delete_x (X *);
#endif // X_H

x.c

#include "x.h"
#include "stdlib.h"
X * create_x (void) {
return (X *)malloc(sizeof(X));
}
int set_foo (X * x, int i) {
x->foo = i;
return i;
}
char set_bar (X * x, char c) {
x->bar = c;
return c;
}
void delete_x (X * x) {
free(x);
}

mock-x.c

#include "x.h"
#include "mock-parameters.h"
Set_foo_params set_foo_params;
int set_foo (X * x, int i) {
// Stash parameter(s)
set_foo_params.x = x;
set_foo_params.i = i;
return set_foo_params.result;
}

object.hpp

#ifndef OBJECT_HPP
#define OBJECT_HPP
#include "x.h"
class Object {
public:
void embed_x(X *);
int increment_foo(int);
private:
X * _x;
};
#endif // OBJECT_HPP

object.cpp

#include "object.hpp"
void Object::embed_x (X * x) {
_x = x;
}
int Object::increment_foo (int i) {
++i;
return set_foo(_x, i);
}

mock-parameters.h

#include "x.h"
typedef struct Set_foo_params {
X * x;
int i;
int result;
} Set_foo_params;
extern Set_foo_params set_foo_params;

test.cpp

#include "object.hpp"
#include "mock-parameters.h"
int test_object_update_foo_correctly_invokes_x_set_foo (void) {
int result;
Object object;
// Setup
X * x = create_x();
object.embed_x(x);
// Execute
object.increment_foo(7);
// Test
if (8 == set_foo_params.i) {
result = 0;
} else {
result = 1;
}
delete_x(x);
return result;
}
int test_object_update_foo_correctly_returns_x_set_foo_result (void) {
int result;
Object object;
// Setup
X * x = create_x();
object.embed_x(x);
set_foo_params.result = 5;
// Execute
int output = object.increment_foo(0);
// Test
if (5 == output) {
result = 0;
} else {
result = 2;
}
delete_x(x);
return result;
}
int main (void) {
int result;
result |= test_object_update_foo_correctly_invokes_x_set_foo();
result |= test_object_update_foo_correctly_returns_x_set_foo_result();
return result;
}

既然你澄清了你的object.ctest.c使用相同的函数 - 但应该使用不同的实现 - 唯一的方法是以某种方式分离这些函数。根据 ODR(C++编程语言的一个定义规则),您不能对同一函数/对象有两个不同的实现。

因此,唯一的方法是创建两个单独的函数/对象(具有单独的名称)。您可以使用不同的名称,也可以将相同的名称放在不同的命名空间中。

例:

x.h

#ifndef X_H
#define X_H
#ifdef X_DEBUG
#define X mock_x
#else
#define X x
#endif
namespace X {
int foo();
}
#endif // X_H

x.cpp

namespace x {
int foo()
{
int res = -1;
// rightful implementation
return res;
}
}

模拟-x.cpp

namespace mock_x {
int foo()
{
int res = -1;
// mock implementation
return res;
}
}

对象.cpp

#define X_DEBUG
#include <x.h>
int obj_bar()
{
int res = X::foo(); // uses mock implementation
return res;
}
}

测试.cpp

#include <x.h>
int test_bar()
{
int res = X::foo(); // uses real implementation
return res;
}
}

显然,这种方法是可扩展的 - 你可以在这个命名空间X中放置任意数量的类/函数/对象。通用功能可以在标头中内联定义,并在相应的模块x.c 和mock-x.c中定义单独的功能。

通常:

我肯定会朝着创建 mock-x 实现的方向看,作为一组不同的链接器符号。这绝对是更可控和灵活的方法。您甚至可以使用来自 mock-x 功能的原始 x API 实现 - 例如,在适当的时候作为回退。

编辑:

对于您新添加的特定示例代码。

模拟-x.c:

#include "x.h"
#include "mock-parameters.h"
Set_foo_params set_foo_params;
int mock_set_foo (X * x, int i) {
// Stash parameter(s)
set_foo_params.x = x;
set_foo_params.i = i;
return set_foo_params.result;
}

其余文件保持不变。 GCC 应该使用命令单独编译对象.cpp

g++ -Dset_foo=mock_set_foo -c object.cpp -o object.o
g++ test.cpp object.o x.c mock_x.c

GCC 选项-Dset_foo=mock_set_foo的工作方式与

#define set_foo mock_set_foo

在此选项之后的命令行中每个源文件的开头。 此选项广泛用于编译时配置目的。

最新更新