【问题标题】:How to test an EXE with Google Test?如何使用 Google Test 测试 EXE?
【发布时间】:2014-04-15 15:33:05
【问题描述】:

我在 Visual Studio 中有一个 C++ 项目,并添加了另一个专门用于测试的项目。这两个项目都是 EXE(控制台应用程序)。那么如何在第二个项目中使用第一个项目呢?

澄清一下,如果第一个项目是一个可以简单地包含在第二个项目中的库,但作为一个 EXE,这就是问题所在。

【问题讨论】:

  • 测试输出是另一个exe,取决于您要测试的内容包括测试项目中的文件,或者只是将业务分离到一个独立的dll中
  • 如果您想为应用程序提供一个 exe 并为单元测试提供一个不同的 exe,您必须获取要测试的代码并将其构建到库中。完成后,您可以将库链接到两个 exe。
  • 您能否进一步澄清:a) 您已经为您的控制台应用程序开发了一些特定于应用程序的类/API,您希望使用 googletest 进行单元测试。 b) 您想使用 googletest 测试 .exe 的行为。哪个?
  • @MikeKinghan 这是选项 a) 我想要

标签: c++ googletest


【解决方案1】:

根据您的 cmets,您有一个 C++ 控制台应用程序(MyApp),您已经为其开发了一些特定于应用程序的类,您希望使用 googletest 在 视觉工作室。怎么样?

正如你所说,如果你想对 library 进行单元测试,这样做的方法是 明显的。你会:

  • 1) 创建一个项目以创建一个单元测试应用程序 (UnitTest)。
  • 2) 配置包含搜索目录,以便编译器可以找到库的头文件。
  • 3) 配置库搜索目录,以便链接器可以找到库本身。
  • 4) 将库本身添加到链接器输入中。
  • 5) 使UnitTest 项目依赖于库项目,以便构建UnitTest 确保MyApp 是最新的。
  • 6) 根据 googletest 文档对 UnitTest 应用进行编码。

但由于您要进行单元测试的类特定于 MyApp,因此您没有任何 图书馆。

对此的回答是:您没有包含要进行单元测试的类的库?所以做一个!

这样你使用 3 个项目:-

  • MyAppLib,生成包含您要进行单元测试的所有功能的库。
  • MyApp,生成与目前相同的可执行文件,但链接MyAppLib
  • UnitTest,生成对MyAppLib 进行单元测试的可执行文件,同时链接MyAppLib

但是,如果您不喜欢训练中士的回答,您可以解决它。

从通常的构建系统的角度来看(在 Visual Studio 中设计的那个), MyApp 项目的重要输出是构建目标 - .exe。 生成的.obj 文件只是中间副产品。 VS 不为您提供支持 将这些副产品视为依赖项目的自动链接器输入,并且如果依赖项目也是同一类型的.exe - 就像你的情况一样 - 那么这种自动链接无论如何都是不可能的,因为主要入口点将被多重定义。

但从单元测试的角度来看,情况正好相反。 .exe 不感兴趣,而(部分).obj 文件全部或部分包含您要进行单元测试的类的实现。在foo 类在foo.h 中定义并在foo.cpp 中实现的教科书案例中,UnitTest 的链接中需要目标文件foo.obj

为简单起见,假设MyApp 仅使用一个特定于应用程序的类foo, 在foo.h 中定义并在foo.cpp 中实现。那么你有两个选项来构建UnitTest

  • a) 您可以将foo.cpp 添加到UnitTest 的源文件中。当然不要复制它。只需从MyApp 的源文件夹添加现有项目。然后你就完成了,但是这个 当然有一个缺点,foo.cppUnitTest 项目。

  • b) 您可以将foo.obj 视为链接UnitTest 所需的静态库 并按照上述步骤1) - 6) 操作。这尤其意味着在第 3 步)UnitTest 的 {Debug|Release} 构建配置了包含\path\to\MyApp\{Debug|Release}(相对或绝对形式)的库搜索目录。

实际上,对于选项 b),MyApp 中的 .obj 文件很可能不止一个,您必须在 UnitTest 中进行链接,而且它们的数量很可能会随着时间的推移而增长。维护UnitTest 的正确链接可能会成为一件苦差事,您可能会得出这样的结论:训练中士毕竟是对的。

【讨论】:

  • 谢谢,我想我会硬着头皮重构为LIB+EXE。
  • 很好的答案@Mike,确实我认为foo.obj 有点像静态库,因为库是对象的集合(.o.obj 等),所以直接添加就可以了。
  • 自从我上次需要 gtest 以来已经有几年了,我最终得到了这个确切的答案,我看到了我上次来这里时的橙色投票。似曾相识,很好的答案
【解决方案2】:

视情况而定。 Google Test(主要)是一个单元测试框架(过度简化,测试类)。您绝对可以将 is 用于其他类型的测试,但它没有“内置”功能用于其他类型的测试,您必须自己编写。

如果您尝试对可执行文件进行系统测试,则可以运行该进程。如果您使用的是多平台系统或已经有 boost 依赖项,我建议使用 Boost.Process。否则,请看这里:launch an exe/process with stdin stdout and stderr?

您编写的“测试”将调用可执行文件,并可以相应地输入标准输入或标准输出。

例如:

std::string path_to_exectuable = "thepath";
TEST(FooTester,CheckHelpScriptReturns0)
{
 using bp =::boost::process; 
 std::vector<std::string> args; args.push_back("--help");
 bp::context ctx; 
 ctx.stdout_behavior = bp::capture_stream(); 

 bp::child c = bp::launch(exec, args, ctx); 
 bp::status s = c.wait(); 
 ASSERT_TRUE(s.exited())<<"process didn't exit!";
 ASSERT_EQ(s.exit_status(),0)<<"Help didn't return 0";
}

【讨论】:

  • 您有什么想法吗,为什么以下内容不起作用/如何制作以下内容:EXPECT_EXIT(c.terminate(), ::testing::ExitedWithCode(0), "");。就我而言,我得到Result: failed to die ?!?
【解决方案3】:

我遇到了类似的情况,就编译器而言,我的设置方式有效地实现了 Mike Kinghan 的回答的相同目标,但从用户的角度来看却以不同的方式进行。

我所做的是创建一个我称之为“测试”的自定义配置。您可以通过打开项目设置、选择“配置管理器...”并在配置选择框中选择“新建...”来创建新配置。

当出现提示时,我选择从默认的“调试”配置中复制设置,这样我就可以像在“调试”配置中一样使用调试器进行测试。

在新的测试配置下,我将编译器和链接器的选项设置为像往常一样使用 google test。

属性的重要变化是我定义了一个预处理器变量,我称之为“TESTING”。

我重写了我的“main.cpp”,看起来像这样:

...
// includes
// functions
// whatever
...

#ifdef TESTING
#include <gtest/gtest.h>
#endif

int main(int argc, char **argv) {
   #ifdef TESTING
   ::testing::InitGoogleTest(&argc, argv);
   int val = RUN_ALL_TESTS();
   getchar();  // not necessary, but keeps the console open
   return val;
   #endif    

   // rest of main() as normal...
}

我想表明的是,我只在定义 main 的地方更改了几行,我不必在整个文件中进行大的更改。

现在这一切都设置好了,我只是为我的测试创建了一个新的源文件夹,并在其中创建“.cpp”文件。为了避免膨胀正常的可执行文件,我用检查 TESTING 变量来包装这些文件,所以我有这样的东西:

tests/Test.cpp:

#ifdef TESTING

#include <gtest/gtest.h>

#include "my_class_header.h"

TEST(TestMyClass, test_something) {
    // perform some test on class
} 

#endif

我认为这些文件仍然会在 Debug 和 Release 配置下被编译器“命中”,因此拥有大量这些文件可能会减慢构建速度,但 Debug 和 Release 对象不会因测试代码而变得臃肿。

两个要点是:

  • 使用此方法,测试代码仍然与应用程序代码分开组织,但它仍然驻留在同一个 Visual Studio 项目中,这可能有益,也可能无益。就我个人而言,我喜欢不必管理/担心第二个项目。
  • 就像 Mike Kinghan 所说,自己管理和链接 .obj 文件可能会成为一件苦差事,但通过使用这种方法,默认的 Visual Studio 设置会为您管理。

一个缺点是所有对象文件的有效冗余副本将在“测试”输出目录中创建。有了更多的配置,肯定有办法“共享”调试对象文件,但我没有理由走那么远。

这是一种非常简单的方法,它可能比将应用程序重构为单独的库和主库要容易得多。我不喜欢使用预处理器,但在这种情况下,它相当简单,没有太多的代码膨胀,并且完全完成了它需要做的事情。您总是可以通过其他方式触发测试,而无需使用预处理器。

【讨论】:

    【解决方案4】:

    如果您对在不同的项目中进行测试不是很严格,您可以在您的应用程序项目中编写测试。然后让应用程序在接收到某些命令行参数时执行测试,否则执行正常的应用程序逻辑,即

    int main(int argc, char* argv[])
    {
        if (argc >= 2 && std::string(argv[1]) == "--tests")
        {
            ::testing::InitGoogleTest(&argc, argv);
            return RUN_ALL_TESTS();
        }
        else
        {
            // Application logic goes here
        }
    }
    
    TEST(ExampleTests, TestSQRTCalculation) // assuming all the right headers are included
    {
        EXPECT_NEAR(2.0, std::sqrt(4.0), 0.000001);
    }
    

    这避免了仅出于测试目的而创建不必要的库,尽管如果结构正确,您仍然可以这样做。缺点是测试代码进入了你要发布的可执行文件。如果你不想这样,我猜你需要一个额外的配置来指定一个预处理器指令来禁用测试。

    调试测试或在构建后自动运行它们很容易,只需分别将“--tests”指定为调试参数或在构建后命令行中。

    【讨论】:

      【解决方案5】:

      如果您想测试控制台应用程序,您可以运行打开控制台窗口的测试并运行第一个应用程序的 exe 文件。 然后在您的 googletest 中捕获您刚刚运行的 exe 的标准输出。

      [为了更好地控制第一个应用程序,您可能需要将第一个应用程序解析参数发送给它,例如一些标志,如 -x 或任何你需要的。]

      【讨论】:

        【解决方案6】:

        我已经准备了一个 github 存储库,其中包括与 Mike 的“drill-sergeant”建议并行的 Visual Studio 2015 解决方案。您可以直接使用它,无需任何额外的要求或依赖。

        https://github.com/fuatcoskun/GoogleTestVS2015

        希望对你有帮助……

        【讨论】:

          猜你喜欢
          • 2013-08-22
          • 2018-03-02
          • 2014-10-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-06-21
          • 2016-10-11
          • 2018-11-09
          相关资源
          最近更新 更多