【问题标题】:Unit Testing with Catch C++ is interfering with my main()使用 Catch C++ 进行单元测试正在干扰我的 main()
【发布时间】:2018-02-09 21:51:01
【问题描述】:

我是单元测试的新手,并决定使用 Catch 框架用于 c++,因为它似乎很容易与其一个头文件集成。但是,我有一个多文件二叉搜索树程序(文件有:main.cpp、Tree.h、Tree.hxx、TreeUnitTests.cpp、catch.hpp)。如果我在 main.cpp 中注释掉我的 int main() 函数,我只能让我的单元测试运行。我知道它与我的 TreeUnitTests.cpp 中的“#define CATCH_CONFIG_MAIN”声明冲突,但如果我不包含该声明,我将无法运行单元测试。每次我想运行单元测试时,如何在不必注释 main() 的情况下让两者都运行?

这是我正在使用的头文件: https://raw.githubusercontent.com/philsquared/Catch/master/single_include/catch.hpp

我在 Catch 教程中找到并用作指南: https://github.com/philsquared/Catch/blob/master/docs/tutorial.md

一些相关文件供参考: ma​​in.cpp:

//******************* ASSN 01 QUESTION 02 **********************

#include "Tree.h"
#include <iostream>

using namespace std;

/*
int main()
{

    //creating tree with "5" as root
    Tree<int> tree(5);
    tree.insert(2);
    tree.insert(88);
    tree.inorder();
    cout << "does tree contain 2?: ";
    cout << tree.find(2) << endl;
    cout << "does tree contain 3?: ";
    cout << tree.find(3) << endl;

    Tree<int> copytree(tree);
    cout << "copied original tree..." << endl;
    copytree.preorder();
    cout << "after deletion of 2:\n";
    copytree.Delete(2);
    copytree.postorder();


    return 0;
}
*/

TreeUnitTests.cpp:

#include <iostream>
#include "Tree.h"
#define CATCH_CONFIG_MAIN
#include "catch.hpp"

TEST_CASE("Pass Tests")
{
    REQUIRE(1 == 1);
}

TEST_CASE("Fail test")
{
    REQUIRE(1 == 0);
}

(我的测试不是真正的测试,只是为了验证 Catch 框架是否正常工作。我猜你可以说这是一个元测试)

【问题讨论】:

  • 您是否将其全部链接到一个可执行文件中?为什么?
  • 你不应该用测试程序编译你的main.cpp。测试程序有自己的main()
  • 您应该有一个单独的构建用于单元测试,不包括main.cpp 文件。或者,这个单独的构建可以创建一个定义(例如#define UNIT_TESTING),它可以用于有条件地从main.cpp 中仅删除main() 函数(以防它包含其他可测试的函数)。
  • 我没有意识到我不应该用测试程序编译 main.cpp。我如何不一起编译它,因为它都在 Visual Studios 的同一个项目文件夹中?我怎样才能创建这个单独的构建? (澄清一下,main.cpp 和 TreeUnitTests.cpp 都在 Source 文件夹下,Tree.h 和 Tree.hxx 在 Header 文件夹下。这两个文件夹都在同一个项目文件夹中)你还可以提供更多对所提到的#define UNIT_TESTING 定义的见解?
  • P.S. catch.hpp 也在 Header 文件夹下。

标签: c++ unit-testing catch-unit-test


【解决方案1】:

由于您使用的是 Visual Studio,因此正确的方法是使用 Configuration Manager(可通过右键单击解决方案资源管理器工具窗口中的解决方案来访问)并创建单独的解决方案配置。

在“新解决方案配置”表单中,为配置指定一个有意义的名称(例如 UnitTesting)。还有一个名为“复制自:”的下拉列表,您可以在其中选择将设置复制到新配置中的配置。不要将其留在&lt;Empty&gt;,而是选择一些您迄今为止用于构建源的源配置(因为它将正确设置包含文件夹和其他设置)。通过选中“创建新项目配置”复选框,确保您还为解决方案中的所有项目创建了匹配的 项目 配置。

创建配置后,您可以像在调试和发布配置之间切换一样,使用工具栏下拉列表来选择它:

点击选择你的新UnitTesting配置

现在,当您打开某个项目或该项目中的文件的属性页时(右键单击文件或项目,选择Properties),您可以选择特定配置(在您的情况下为UnitTesting),并指定仅对这个单一配置有效的某些选项。

您也可以在属性页中选择All Configurations 以将设置应用于所有配置。这在添加其他包含目录和常用预处理器设置时很重要。如果不小心只在Debug配置中添加了一些include目录,切换到UnitTesting后编译器将找不到头文件。

因此,要使此配置的行为有所不同,您可以执行以下操作:

1。从UnitTesting 构建配置中排除main.cpp

例如,您可以右键单击main.cpp,打开Properties,然后仅在此配置中从构建中排除该文件:

从构建中排除特定配置的文件

2。使用预处理器宏有条件地排除main() 函数

或者,您可以打开项目属性并设置仅在此配置中定义的特定预处理器宏:

UnitTesting 配置创建一个UNIT_TESTING 宏,再次使用MS Paint 为图形添加风格

因此,对于后一种方法,您的实际 main.cpp 将更改为:

// remove if UnitTesting configuration is active
#ifndef UNIT_TESTING
int main(void)
{
    ...
}
#endif

第一种方法很简洁,因为它根本不需要更改生产代码,但是当其他毫无戒心的人在 Visual Studio 中查看您的代码时,当他们开始想知道为什么某些代码时,后一种方法可能更明显根本没有被编译。我有时会忘记构建配置中缺少某个文件,然后盯着奇怪的编译错误看了一会儿。

而且,使用第二种方法,您还可以轻松地在同一位置提供自定义 Catch 入口点:

#ifdef UNIT_TESTING

    // this is the main() used for unit testing

#   define CATCH_CONFIG_RUNNER
#   include <catch.hpp>

    int main(void)
    {
        // custom unit testing code
    }    

#else

    int main(void)
    {
        // actual application entry point
    }

#endif

【讨论】:

  • 这正是我所需要的,谢谢!选项 1 完美运行,实施起来非常简单。另外作为对未来自己或其他想知道的人的说明,当我打开配置管理器并创建一个名为 UnitTesting 的新解决方案配置时,我必须“从以下位置复制设置:”调试以使其按我想要的方式工作。示例:drive.google.com/file/d/0B-CcSAH4A8jFUWhERHhKdGYzazg/…
【解决方案2】:

当您省略 CATCH_CONFIG_RUNNER 时,Catch 会为您实现 main。对于大多数测试来说,它已经足够好了,但是如果您需要更多控制,那么您需要告诉它使用您的 main 并在其中使用 bootstrap Catch。

使用像这样简单的东西:

ma​​in.cpp

#define CATCH_CONFIG_RUNNER // Configure catch to use your main, and not its own.
#include <catch.hpp>
#include <iostream>
#include <exception>

int main(int argCount, char** ppArgs)
{
    try {
        // bootstrap Catch, running all TEST_CASE sequences.
        auto result = Catch::Session().run(argCount, ppArgs);
        std::cin.get(); // Immediate feedback.
        return (result < 0xFF ? result : 0xFF);
    } catch (const std::exception& ex) {
        auto pMessage = ex.what();
        if (pMessage) {
            std::cout << "An unhandled exception was thrown:\n" << pMessage;
        }
        std::cin.get(); // Immediate feedback.
        return -1;
    }
}

tests.cpp

#include <catch.hpp>

TEST_CASE("Pass Tests")
{
    REQUIRE(1 == 1);
}

TEST_CASE("Fail test")
{
    REQUIRE(1 == 0);
}

这或多或少是我在生产代码中使用的,效果很好。

【讨论】:

  • 你是说我需要将“#define CATCH_CONFIG_RUNNER”放在我的 main.cpp 中,然后在我的 main 函数中创建我的单元测试吗?这会在我的其他代码已经在 main() 之前完成吗?
  • 假设您不需要在 main 中做任何棘手的事情,请或多或少地保留它在我的示例中。将您的测试用例分成您认为合适的 cpp 文件。 Catch 使用元编程魔法来完成这项工作。例如,我通常每个 hpp 使用一个测试 cpp 文件,但在某些情况下,当标头很大时,会将其拆分为更小的测试序列。
  • 几个小时前我刚刚了解了单元测试,这是我第一次尝试在 c++ 程序中实现它,所以对于我理解起来有点慢,我深表歉意。你能详细说明你之前的反馈吗?在我之前展示的主要功能中(仅被注释掉以表明我需要这样做才能让我的单元测试运行),我是否需要取出我目前拥有的所有东西,而不是简单地添加您提供的代码?如果是这样,我应该把我的代码放在哪里?
  • 复制我的示例并用它替换 main.cpp 的内容。以您认为合适的任何方式将所有 TEST_CASE 序列移动到其他 cpp 文件。例如,您可以创建一个名为“tests.cpp”的文件并将所有 TEST_CASE 序列放在那里。
  • @KobyDuck:您能解释一下如何将编译后的 main.o 文件与 test.cpp 链接以及如何运行测试吗?我尝试使用您的方法,但无法编译 test.cpp,因为它一直抛出未定义的 main()。
猜你喜欢
  • 1970-01-01
  • 2020-06-08
  • 2022-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-05
  • 2016-09-24
相关资源
最近更新 更多