17

我有一个用 C++ 编写的程序,其中包含一些包含链接库的子文件夹。有一个顶级 SConscript,它调用子文件夹/库中的 SConscript 文件。

在库 cpp 中,有一个 GTest 测试函数:

TEST(X, just_a_passing_test) {
EXPECT_EQ(true, true);
}

main()顶层程序源中,它只调用 GTests main,并在其中包含另一个 GTest 测试:

int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

TEST(Dummy, should_pass){
EXPECT_EQ(true, true);
}

现在的问题是,当我运行程序时,GTest 只在main.cpp源代码中运行测试。忽略库中的测试。现在,当我在同一个库 cpp 中引用一个不相关的类时,它变得很奇怪main.cpp,以一种没有副作用的方式(例如SomeClass foo;),测试神奇地出现了。我尝试使用 -O0 和其他技巧来强制 gcc 不优化未调用的代码。我什至尝试过 Clang。我怀疑这与 GTest 在编译期间如何进行测试发现有关,但我找不到有关此问题的任何信息。我相信它使用静态初始化,所以也许那里有一些奇怪的顺序。非常感谢任何帮助/信息!

更新:在常见问题解答中找到一个听起来像这个问题的部分,尽管它专门指的是 Visual C++。其中包括一个技巧/hack,以强制编译器在未引用时不丢弃该库。它建议不要将测试放在库中,但这让我想知道如果没有每个库的可执行文件,您将如何测试库,这使得快速运行它们变得很痛苦并且输出臃肿。 https://code.google.com/p/googletest/wiki/Primer#Important_note_for_Visual_C++_users

4

1 回答 1

27

从场景设置中可以看出,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变体也是如此。因此,您可以在代码中部署这些宏,并使用与类定义相同的约束和期望。

有鉴于此, 如果您 有一个libcoolcool.hcool.cppgtestteststests.cpp

  • 写一个头文件,cool_test.h
  • #include "cool.h"在里面
  • #include <gtest/gtest.h>在里面。
  • 然后在其中定义您的libcool测试用例
  • #include "cool_test.h"tests.cpp,
  • 编译tests.cpplibcool链接libgtest

很明显,为什么您不会做 OP 所做的事情。您不会定义、 within 和 not in需要tests.cpp不需要的类。cool.cppcool.cpptests.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

于 2015-06-27T13:27:49.663 回答