从场景设置中可以看出,gtest
缺少测试用例的库在应用程序构建中是静态链接的。此外,GNU 工具链正在使用中。
问题行为的原因很简单。测试程序不包含对库中任何包含
TEST(X, just_a_passing_test)
. 所以链接器不需要链接该库中的任何目标文件来链接程序。所以它没有。因此
gtest
运行时在可执行文件中找不到该测试,因为它不存在。
它有助于理解 GNU 格式的静态库是目标文件的存档,装饰有管理头块和全局符号表。
OP 发现,通过在程序中编写对问题库中任何公共符号的临时引用,他可以“神奇地”将其测试用例强制到程序中。
没有魔法。为了满足对该公共符号的引用,链接器现在必须从库中链接一个目标文件——包含符号定义的目标文件。并且 OP 表明该库是由.cpp
. 所以库中只有一个目标文件,它也包含测试用例的定义。有了链接中的那个目标文件,测试用例就在程序中。
OP 对编译器选项进行了徒劳的调整,从 GCC 切换到 clang,以寻找一种更体面的方式来达到同样的目的。编译器无关紧要。GCC 或 clang,它的链接由系统链接器完成,ld
(除非采取了不寻常的措施来替换它)。
即使程序没有引用该目标文件中的符号,是否有更体面的方法可以ld
从静态库链接目标文件?
有。说问题程序是app
问题静态库是
libcool.a
然后,链接的常用 GCC 命令行app
与此类似,在相关方面:
g++ -o app -L/path/to/the/libcool/archive -lcool
这将命令行委托给ld
,以及附加的链接器选项和库,这些选项和库g++
被认为是它所在的系统的默认值。
当链接器开始考虑-lcool
时,它会发现这是一个归档请求/path/to/the/libcool/archive/libcool.a
。然后它会判断此时它是否还有任何未解析的符号引用,其定义已编译在libcool.a
. 如果有,那么它会将这些目标文件链接到app
. 如果不是,那么它不会链接任何东西libcool.a
并继续传递。
但是我们知道libcool.a
我们想要链接的符号定义,即使app
没有引用它们。在这种情况下,我们可以告诉链接器链接目标文件,libcool.a
即使它们没有被引用。更准确地说,我们可以告诉g++
链接器这样做,如下所示:
g++ -o app -L/path/to/the/libcool/archive -Wl,--whole-archive -lcool -Wl,-no-whole-archive
这些-Wl,...
选项告诉g++
将选项传递...
给ld
. 该--whole-archive
选项告诉ld
链接来自后续档案的所有目标文件,无论它们是否被引用,直到另行通知。-no-whole-archive
告诉
ld
停止这样做并像往常一样恢复业务。
它看起来好像-Wl,-no-whole-archive
是多余的,因为它是命令行上的最后一件事
g++
。但事实并非如此。请记住,在g++
将系统默认库传递给ld
. --whole-archive
当这些默认库被链接时,您绝对不想生效。(链接将因多个定义错误而失败)。
将此解决方案应用于问题案例TEST(X, just_a_passing_test)
并将被执行,而无需强制程序对定义该测试的目标文件进行一些无操作引用。
在一般情况下,这种解决方案有一个明显的缺点。如果碰巧我们想要强制链接某个未引用的目标文件的库包含一堆我们真的不需要的其他未引用的目标文件。
--whole-archive
也将它们全部链接起来,它们只是程序中的臃肿。
该--whole-archive
解决方案可能比 no-op reference hack 更可敬,但它并不可敬。它甚至看起来都不值得尊敬。
这里真正的解决方案就是做合理的事情。如果您希望链接器链接程序中某些内容的定义,则不要对链接器保密。至少在您希望使用其定义的每个编译单元中声明该事物。
gtest
对测试用例做合理的事情涉及理解gtest
宏之类TEST(X, just_a_passing_test)
的扩展为类定义,在这种情况下:
class X_just_a_passing_test_Test : public ::testing::Test {
public:
X_just_a_passing_test_Test() {}
private:
virtual void TestBody();
static ::testing::TestInfo* const test_info_ __attribute__ ((unused));
X_just_a_passing_test_Test(X_just_a_passing_test_Test const &);
void operator=(X_just_a_passing_test_Test const &);
};
(加上一个静态初始化器test_info_
和一个定义TestBody()
)。
对于TEST_F
,TEST_P
变体也是如此。因此,您可以在代码中部署这些宏,并使用与类定义相同的约束和期望。
有鉴于此,
如果您
有一个libcool
在cool.h
cool.cpp
gtest
tests
tests.cpp
- 写一个头文件,
cool_test.h
#include "cool.h"
在里面
#include <gtest/gtest.h>
在里面。
- 然后在其中定义您的
libcool
测试用例
#include "cool_test.h"
在tests.cpp
,
- 编译
tests.cpp
和libcool
链接libgtest
很明显,为什么您不会做 OP 所做的事情。您不会定义、 within
和 not in需要和tests.cpp
不需要的类。cool.cpp
cool.cpp
tests.cpp
OP 反对在库中定义测试用例的建议,因为:
如果没有每个库的可执行文件,您将如何测试库,从而使快速运行它们变得很痛苦。
作为经验法则,我建议为gtest
每个库维护一个可执行文件以进行单元测试:使用诸如make
.对一堆图书馆的判决。但是,如果您不想这样做,则仍然没有异议:
// tests.cpp
#include "cool_test.h"
#include "cooler_test.h"
#include "coolest_test.h"
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
libcool
用、libcooler
和编译和libcoolest
链接libgtest