Catch.hpp单元测试:如何动态创建测试用例?

时间:2016-03-07 21:55:38

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

我正在使用CATCH v1.1 build 14对我的C ++代码进行单元测试。

作为测试的一部分,我想在我的代码中检查几个模块的输出。没有一定数量的模块;可以随时添加更多模块。但是,测试每个模块的代码是相同的。因此,我认为将测试代码放在for循环中是理想的。实际上,使用catch.hpp,我已经验证我可以在测试用例中动态创建节,其中每个节对应一个模块。我可以通过将SECTION宏括在for循环中来完成此操作,例如:

#include "catch.hpp"
#include <vector>
#include <string>
#include "myHeader.h"

TEST_CASE("Module testing", "[module]") {
    myNamespace::myManagerClass manager;
    std::vector<std::string> modList;
    size_t n;

    modList = manager.getModules();
    for (n = 0; n < modList.size(); n++) {
        SECTION(modList[n].c_str()) {
            REQUIRE(/*insert testing code here*/);
        }
    }
}

(这不是一个完整的工作示例,但你明白了。)

这是我的困境。我想独立测试模块,这样如果一个模块出现故障,它将继续测试其他模块而不是中止测试。但是,CATCH的工作方式,如果单个REQUIRE失败,它将中止整个测试用例。出于这个原因,我想为每个模块创建一个单独的测试用例,而不仅仅是一个单独的部分。我尝试将for循环放在TEST_CASE宏之外,但此代码无法编译(如我所料):

#include "catch.hpp"
#include <vector>
#include <string>
#include "myHeader.h"

myNamespace::myManagerClass manager;
std::vector<std::string> modList;
size_t n;

modList = manager.getModules();
for (n = 0; n < modList.size(); n++) {
    TEST_CASE("Module testing", "[module]") {
        SECTION(modList[n].c_str()) {
            REQUIRE(/*insert testing code here*/);
        }
    }
}

writing my own main()可能会这样做,但我看不清楚如何做到这一点。 (我会将TEST_CASE代码直接放入main()吗?如果我想将TEST_CASE代码保存在其他文件中该怎么办?此外,它会影响我的其他更标准的测试用例?)

我还可以使用CHECK宏而不是REQUIRE宏来避免在模块出现故障时中止测试用例,但后来我遇到了相反的问题:它试图继续对模块进行测试应该早点失败。如果我可以将每个模块放在自己的测试用例中,那么这应该给我理想的行为。

是否有一种在CATCH中动态创建测试用例的简单方法?如果是这样,你能给我一个如何做的例子吗?我通读了CATCH文档并在线搜索,但我找不到任何关于如何执行此操作的说明。

2 个答案:

答案 0 :(得分:1)

有一种方法可以实现您所寻找的目标,但我会发现您的方法是错误的: -

单元测试旨在测试每个单元,即您编写组件和测试以验证该组件的正确行为。如果您以后决定以某种方式更改一个组件,则更新相应的测试。

如果将所有组件的所有测试聚合到同一个文件中,则隔离具有不同行为的单元变得更加困难。

如果您想要分析组件的测试,因为它在所有组件中基本相同,您可以执行以下操作之一:

<强> 1。将常用测试解压缩到单独的头文件

您可以#define您要测试的组件的类型名称,然后包含一个包含所有测试的头文件:

// CommonTests.t.h
#include "catch.hpp"
TEST_CASE("Object Can be instantiated", "[ctor]")
{
   REQUIRE_NOTHROW(COMPONENT component);
}

// SimpleComponent.t.cpp
#define COMPONENT SimpleComponent
#include "CommonTests.t.h"

这很简单,但有一个缺点 - 当你运行测试运行器时,你将有重复的测试(按名称),所以你只能运行所有的测试,或者通过标记。

您可以通过对组件名称进行字符串化并将其附加/附加到测试用例名称来解决此问题。

** 2.通过参数化组件调用常用测试**

将常用测试放入单独的文件中并直接调用常用测试方法:

// CommonTests.t.h
void RunCommonTests(ComponentInterface& itf);

// CommonTests.t.cpp
void RunCommonTests(ComponentInterface& itf)
{
  REQUIRE(itf.answerToLifeUniverseAndEverything() == 42);
}

// SimpleComponent.t.cpp
#include "SimpleComponent.h"
#include "CommonTest.t.h"
#include "catch.hpp"

TEST_CASE("SimpleComponent is default-constructible", "[ctor]")
{
   REQUIRE_NOTHROW(SimpleComponent sc);
}

TEST_CASE("SimpleComponent behaves like common components", "[common]")
{
  SimpleComponent sc;
  RunCommonTests(sc);
}

答案 1 :(得分:0)

听起来Catch可能正在迁移到基于属性的测试,我希望这将允许一种动态创建测试用例的方法。与此同时,这就是我最终做的事情。

我为单个模块创建了一个.cpp文件,其中包含TEST_CASE个文件,并为模块名称创建了一个全局变量。 (是的,我知道全局变量是邪恶的,这就是我小心并将其作为最后手段使用的原因):

module_unit_test.cpp

#include "catch.hpp"
#include <string>
#include "myHeader.h"

extern const std::string g_ModuleName;  // global variable: module name

TEST_CASE("Module testing", "[module]") {
    myNamespace::myManagerClass manager;
    myNamespace::myModuleClass *pModule;
    SECTION(g_ModuleName.c_str()) {
        pModule = manager.createModule(g_ModuleName.c_str());
        REQUIRE(pModule != 0);
        /*insert more testing code here*/
    }
}

然后,我创建一个可执行文件,它将在命令行上指定的单个模块上运行此测试。 (我尝试循环遍历下面的Catch::Session().run(),但Catch不允许它多次运行。)module_test.cpp下面的代码和module_unit_test.cpp上方的单元测试代码中的目标文件在创建可执行文件时链接。

module_test.cpp

#define CATCH_CONFIG_RUNNER
#include "catch.hpp"
#include <string>
#include <cstdio>

std::string g_ModuleName;  // global variable: module name

int main(int argc, char* argv[]) {
    // Make sure the user specified a module name.
    if (argc < 2) {
        std::cout << argv[0] << " <module name> <Catch options>" << std::endl;
        return 1;
    }

    size_t n;
    char* catch_argv[argc-1];
    int result;

    // Modify the input arguments for the Catch Session.
    // (Remove the module name, which is only used by this program.)
    catch_argv[0] = argv[0];
    for (n = 2; n < argc; n++) {
        catch_argv[n-1] = argv[n];
    }

    // Set the value of the global variable.
    g_ModuleName = argv[1];

    // Run the test with the modified command line arguments.
    result = Catch::Session().run(argc-1, catch_argv);

    return result;
}

然后,我在一个单独的可执行文件中循环(没有链接到上面代码中的目标文件):

module_test_all.cpp

#include <cstdlib>
#include <vector>
#include <string>
#include "myHeader.h"

int main(int argc, char* argv[]) {
    std::string commandStr;
    int result, status = 0;
    myNamespace::myManagerClass manager;
    std::vector<std::string> modList;
    size_t m, n;

    // Scan for modules.
    modList = manager.getModules();

    // Loop through the module list.
    for (n = 0; n < modList.size(); n++) {
        // Build the command line.
        commandStr = "module_test " + modList[n];
        for (m = 1; m < argc; m++) {
            commandStr += " ";
            commandStr += argv[m];
        }

        // Do a system call to the first executable.
        result = system(commandStr.c_str());

        // If a test fails, I keep track of the status but continue
        // looping so all the modules get tested.
        status = status ? status : result;
    }

    return status;
}

是的,这很难看,但我确认它有效。