【问题标题】:Catch.hpp unit testing: How to dynamically create test cases?Catch.hpp 单元测试:如何动态创建测试用例?
【发布时间】:2016-06-21 15:49:33
【问题描述】:

我正在使用 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 ownmain() 或许可以做到这一点,但我不知道如何做到这一点。 (我可以将我的TEST_CASE 代码直接放入main() 吗?如果我想将我的TEST_CASE 代码保存在不同的文件中怎么办?另外,它会影响我的其他更标准的测试用例吗?)

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

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

【问题讨论】:

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


    【解决方案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);
    }
    

    【讨论】:

    • 我正在使用您的第一个建议,这让我很顺利。理想情况下,我想在运行时动态检测模块并为每个模块生成一个测试用例,但我开始认为使用 Catch 是不可能的。一些背景知识:模块从我定义的抽象基类定义派生类。它们产生不同的输出,但检查输出的过程是相同的。其他开发人员添加了他们自己的模块,所以如果他们可以使用我们的测试功能来检查他们自己的模块的合规性,那就太好了。也许我做错了,但我没有看到更好的方法。
    • Google Group 上有更多关于此的讨论:groups.google.com/forum/#!searchin/catch-forum/section/…
    【解决方案2】:

    听起来 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;
    }
    

    是的,它很丑,但我已经确认它有效。

    【讨论】:

      猜你喜欢
      • 2010-11-14
      • 1970-01-01
      • 1970-01-01
      • 2012-01-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-16
      相关资源
      最近更新 更多