在单独的可执行文件中调用INSTANTIATE_TEST_CASE_P时,库中的TEST_P测试无法运行

时间:2019-03-05 01:33:58

标签: c++ cmake googletest

我正在尝试按照googletest自述文件中有关Creating Value-Parameterized Abstract Tests的说明进行操作。我已经创建了一个项目目录,其中有CMakeLists.txtfixture.hhfixture.cctest.cc,并且整个Google Test存储库都在子目录{{1}中检出了}。我的目标只是创建一个包含文本夹具类和一组googletest测试的库,这些库可以由各个单元测试可执行文件链接以最大程度地减少代码重复。

CMakeLists.txt:

TEST_P
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)

enable_testing()
add_subdirectory(googletest)
include(GoogleTest)

add_library(test_utils STATIC fixture.cc)
target_link_libraries(test_utils PUBLIC gtest)

add_executable(unit_tests test.cc)
target_link_libraries(unit_tests PRIVATE test_utils gtest_main)
gtest_discover_tests(unit_tests)
// fixture.hh

#pragma once

#include <gtest/gtest.h>

namespace test_utils { /* *************************************************** */

class Fixture : public ::testing::TestWithParam<size_t> {};

} /* namespace test_utils *************************************************** */
// fixture.cc

#include "fixture.hh"

using namespace test_utils;

TEST_P( Fixture, foo )
{
  ASSERT_EQ( GetParam()%2, 0 );
}

当我运行// test.cc #include "fixture.hh" namespace { /* ************************************************************** */ using namespace test_utils; INSTANTIATE_TEST_CASE_P( BarInstantiation, Fixture, ::testing::Values( 18 ) ); } /* namespace ************************************************************** */ 时,它输出:

./unit_tests

Running main() from ../googletest/googletest/src/gtest_main.cc [==========] Running 0 tests from 0 test cases. [==========] 0 tests from 0 test cases ran. (0 ms total) [ PASSED ] 0 tests. 输出ctest。我在做什么错了?

3 个答案:

答案 0 :(得分:2)

您知道,使用googletest进行单元测试的标准过程是:

  1. 构建一个包含被测代码的库。
  2. 在一个或多个源文件中编写googletest单元测试。
  3. (可选)在the standard googletest manner中写一个定义main的源文件 运行单元测试。或者,您可以不定义任何main,而可以在链接中添加libgtest_main
  4. 将所有googletest源从 2 3 编译为目标文件。
  5. 链接googletest测试运行程序,输入所有对象文件以及库中的 1 libgtest_main(如果需要)以及libgtest

您想链接libgtest_main而不是定义main 通过在静态库中存档 2 中的所有目标文件并将其添加到链接中,以非常规的方式改变过程 而不是直接链接目标文件。

在您发布的示例中,还有另外一些琐碎的变化。你没有图书馆 测试中的代码,因为不需要用它来说明您的问题。您刚刚有一个占位符 如果运行,则必须通过单元测试。问题在于它甚至无法运行!

暂时将CMake放在一边,因为这与 解释你的难题。在这里,您的文件已发布:

$ ls -R
.:
fixture.cc  fixture.hh  test.cc

首先,我将按照标准程序构建googletest运行程序( 没有被测代码的事实。

编译源代码:

$ g++ -Wall -Wextra -c test.cc fixture.cc

链接:

$ g++ -o tester test.o fixture.o -lgtest_main -lgtest -pthread

运行:

$ ./tester
Running main() from /home/imk/Downloads/googletest-master/googletest/src/gtest_main.cc
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from BarInstantiation/Fixture
[ RUN      ] BarInstantiation/Fixture.foo/0
[       OK ] BarInstantiation/Fixture.foo/0 (0 ms)
[----------] 1 test from BarInstantiation/Fixture (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (0 ms total)
[  PASSED  ] 1 test.

没问题。

现在,我将按照您想要的方式再次使测试运行器成为可能。无需重新编译 任何来源。我将制作一个包含fixture.o的静态库:

$ ar rcs libutest.a fixture.o

并使用它而不是fixture.o重新链接:

$ g++ -o tester test.o -L. -lutest -lgtest_main -lgtest -pthread

然后再次运行:

$ ./tester
Running main() from /home/imk/Downloads/googletest-master/googletest/src/gtest_main.cc
[==========] Running 0 tests from 0 test cases.
[==========] 0 tests from 0 test cases ran. (0 ms total)
[  PASSED  ] 0 tests.

有你的难题。现在没有测试。怎么会来?

最后,我将以第三种方式链接测试跑步者:

$ g++ -o tester test.o -lgtest_main -lgtest -pthread

不链接 fixture.olibutest.a。然后运行:

$ ./tester
Running main() from /home/imk/Downloads/googletest-master/googletest/src/gtest_main.cc
[==========] Running 0 tests from 0 test cases.
[==========] 0 tests from 0 test cases ran. (0 ms total)
[  PASSED  ] 0 tests.

与第二个完全一样。在这种情况下,这并不奇怪。我们知道 可以将googletest运行器链接起来,而无需进行任何测试,这就是它的作用。

让我们对fixture.o中定义的符号进行一些研究。

$ nm -C --defined-only fixture.o | egrep -w '(W|V)' | wc -l
463

定义了463 weak symbols。而且只有2 定义的强符号为:

$ nm -C --defined-only fixture.o | egrep -w '(A|B|C|D|G|R|S|T)'
0000000000000000 B Fixture_foo_Test::gtest_registering_dummy_
0000000000000000 T Fixture_foo_Test::TestBody()

链接器在 strong 符号之间进行了重要区分。

链接器将不会链接包含任何失败的强符号引用的程序 解决,即在链接中找不到定义。会失败 带有 undefined reference 错误的链接。但这并不一定要解决 引用链接程序。程序中未解析的对弱符号的引用只是保持为null(= 0)。

链接器不会链接产生链接的程序(或共享库) 同一符号的多个强定义。它将无法与 多个定义错误。但是它可以容忍多个 weak 定义 以及同一符号的强定义(在这种情况下,它会选择 解析引用的强定义,并丢弃所有弱引用。而且它 将容忍一个符号的多个弱定义而没有任何强定义。在 在这种情况下,它将任意选择一个较弱的定义-实际上, 只是它看到的第一个-并丢弃所有其余的。

C ++编译器通常会发出弱和强符号定义的混合体 为了将C ++转换为可成功链接的目标代码。内联类方法 函数模板的实例化必须使用弱定义进行编译。

链接器对弱引用和强引用的不同义务 以其不同的义务连接到作为目标文件的输入文件 还有一个是静态库

向链接器提供目标文件foo.o时,它将无条件地将其链接到程序。

static library是目标文件的存档,您可以从其中提供目标文件给链接器 提取所需的 ,然后将它们链接到程序中。默认情况下不会 无条件地提取并链接其中任何一个。只有那些解决 链接中已经需要引用的符号,

但是符号引用是链接器不需要解析的符号引用 链接程序。因此,默认情况下,它将从不从静态文件中提取并链接目标文件 库来解决符号参考。引用是不确定的 在程序中,因此链接器将不费力地搜索库来对其进行定义。

将我们刚刚回顾过的事实放在一起,我们知道这种联系:

$ g++ -o tester test.o fixture.o -lgtest_main -lgtest -pthread

执行TEST_P中定义的fixture.cc

链接fixture.o到程序的程序,因为它是一个目标文件。

我们也知道这种联系:

$ g++ -o tester test.o -L. -lutest -lgtest_main -lgtest -pthread

执行了0次测试,将提取libutest.a(fixture.o)并将其链接到 如果test.o包含至少一个对以下内容的引用,则该程序 only fixture.o中定义的强符号,即对以下其中一项的引用:

Fixture_foo_Test::gtest_registering_dummy_
Fixture_foo_Test::TestBody()

由于fixture.o中定义的所有463个其他符号都很弱。

但是test.o不会引用任何包含Fixture_foo_Test的符号:

$ nm -C test.o | grep Fixture_foo_Test; echo Done
Done

因此我们可以得出以下结论:

$ g++ -o tester test.o -L. -lutest -lgtest_main -lgtest -pthread

提取并链接libutest.a(fixture.o)。完全一样 链接为:

$ g++ -o tester test.o -lgtest_main -lgtest -pthread

我们知道其中包含0个测试。

fixture.o放入静态库会使它不必要成为链接 的tester,并且未链接。

鉴于此,您可以通过将CMakeLists.txt简化为以下内容来轻松对其进行修复:

cmake_minimum_required(VERSION 3.10 FATAL_ERROR)

enable_testing()
add_subdirectory(googletest)
include(GoogleTest)

add_executable(unit_tests test.cc fixture.cc)
target_link_libraries(unit_tests PRIVATE gtest_main gtest)
gtest_discover_tests(unit_tests)

这将导致fixture.o无条件链接到unit_tests 程序将输出:

[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from BarInstantiation/Fixture
[ RUN      ] BarInstantiation/Fixture.foo/0
[       OK ] BarInstantiation/Fixture.foo/0 (0 ms)
[----------] 1 test from BarInstantiation/Fixture (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[  PASSED  ] 1 test.

如您所愿。

很显然,此解决方案无法满足您提出的创建libtest_utils.a的动机 首先:

  

我的目标只是创建一个包含文本夹具类和一组TEST_P测试的库,这些库可以由各个单元测试可执行文件链接起来,以最大程度地减少代码重复。

但是这个目标是错误的。如果要从相同的源重建它,那么从一组TEST_P测试源(或任何源)构建一个静态库是没有意义的。 用于与需要某些目标文件的每个可执行文件链接,您将这些文件归档在静态库中。如果您要针对需要它的每个可执行文件从源代码重建它, 您只需将所有TEST_P测试源保存在一个共享的位置或存储库中,就可以最大程度地减少代码重复,并且 still 重建静态库毫无意义。 对于每个可执行文件。这些可执行项目中的每一个也可能在自己的自己源中包括库的部分{\ {1}}源,而不必费心重建库或链接 反对。

另一方面,如果您打算 not 为其重建,那么您将有代码重用动机从TEST_P测试源构建静态库。每一个 单元测试可执行项目,但是要将其构建为一个独立项目,并在以后构建的单元测试可执行文件的链接中简单地使用它。您 不想这样做 ,出于与Googletest is best built within each unit-test project that uses it相同的正当理由, 就像你在做的一样。如果您随后链接了googletest,则无法实现将googletest构建为项目内依赖项的目的 具有目标文件库的可执行文件,这些文件是在过去某个时间使用googletest修订版(可能使用不同的编译器)在不同项目中编译的。

您希望googletest由您的单元测试项目构建,并且您的TEST_P源将在同一项目中编译并与同一googletest 链接。所以有 与将其直接链接到单元测试可执行文件中相比,将您构建到中间静态库中的TEST_P对象文件进行存档没有任何意义。和, 如果您将它们放在中间静态库中,则默认情况下,链接程序将忽略它们。

我说默认情况下链接器将忽略它们,因为可以强制链接器链接对象 静态库中的文件,即使不需要。您可以通过强制执行 使用链接器的--whole-archive option提取并链接 all 静态库中的目标文件。 如果像往常一样通过gcc / g ++(或clang / clang ++)调用链接器,则需要使用链接选项 静态库TEST_P为:

libbar.a

您需要在... -Wl,--whole-archive -lbar -Wl,--no-whole-archive ... 之前启用--whole-archive,然后在之后禁用它,因为 否则,它将不仅适用于-lbar,而且适用于所有后续库,包括 默认情况下,您甚至没有在命令行中提到的那些都已链接。

如果像这样强制-lbar中所有目标文件的链接,那么当然 它们包含的所有弱定义将用于解析弱符号引用 在程序中。

但是,实现链接选项libbar.a并不容易 在构建-Wl,--whole-archive -lbar -Wl,--no-whole-archive的CMake项目中。参见this question 和答案。再一次,它没有意义,那么为什么要与框架抗争呢? Googletest希望您在自己的产品中建立单元测试 单元测试项目,然后做明显的事情:将目标文件与程序链接。如果那样做 您的单元测试将被链接并按预期运行。

答案 1 :(得分:1)

如果直接链接fixture.cc(首先不创建静态库),是否可以检查是否有区别。

官方googletest repository中有一个测试用例,用于明确测试可以在不同的翻译单元中定义和实例化的测试用例。我不确定,但是在fixture.cc周围添加一个静态库可能会导致static initialization ordering problem

答案 2 :(得分:1)

TEST_P是一个宏,展开后会声明一个完整的c ++类,INSTANTIATE_TEST_CASE_P在其上添加了功能。

由于您的TEST_P位于单独的.cc文件中,因此您的INSTANTIATE_TEST_CASE_P调用(位于其自己的.cc文件中)看不到它,因此它实质上是在定义没有拥有类的函数。

另一方面,TEST_P定义的类没有配备INSTANTIATE_TEST_CASE_P会为其定义的方法。因此,它找不到任何要运行的测试实例。

您需要将TEST_P声明移至INSTANTIATE_TEST_CASE_P调用可以看到它的位置,以使其起作用。

需要注意的是-TEST_P不仅声明了一个类,而且定义了几个函数。因此,如果尝试将其放在头文件中,则可能会收到一些与这些类方法有关的链接器错误(例如,已定义)。因此,最好的选择可能就是在INSTANTIATE_TEST_CASE_P调用旁边。

TEST_P实际上是要成为其自己的独立测试用例,似乎它被设计成可以进入其自己的源文件,并且似乎无法像某种库组件那样进行访问。