Catch.hpp 单元测试:如何动态创建测试用例?
Catch.hpp unit testing: How to dynamically create test cases?
我正在使用 CATCH v1.1 build 14 对我的 C++ 代码进行单元测试。
作为测试的一部分,我想检查我的代码中几个模块的输出。没有固定数量的模块;可以随时添加更多模块。但是,测试每个模块的代码是相同的。因此,我认为将测试代码放在 for
循环中是最理想的。事实上,使用 catch.hpp
,我已经验证我可以在测试用例中动态创建部分,其中每个部分对应一个模块。我可以通过在 for 循环中包含 SECTION
宏来做到这一点,例如:
#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 文档并进行了在线搜索,但找不到有关如何执行此操作的任何说明。
有一种方法可以实现您正在寻找的东西,但我发现您的做法是错误的:-
单元测试旨在测试每个单元,即您编写一个组件和一个测试来验证该组件的正确行为。如果您后来决定以某种方式更改一个组件,则更新相应的测试。
如果将所有组件的所有测试汇总到同一个文件中,则隔离行为不同的单元会变得更加困难。
如果你想排除一个组件的测试,因为它在你的所有组件中基本相同,你可以执行以下操作之一:
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"
这很简单,但有一个缺点 - 当您 运行 测试 运行 时,您将有重复的测试(按名称),因此您只能 运行 所有测试,或按标签。
您可以通过将组件名称字符串化并将其 pre/appending 到测试用例名称来解决此问题。
** 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 可能正在转向基于 属性 的测试,我希望这将允许一种动态创建测试用例的方法。与此同时,这就是我最终做的事情。
我创建了一个 .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;
}
是的,它很丑,但我已经确认它有效。
我正在使用 CATCH v1.1 build 14 对我的 C++ 代码进行单元测试。
作为测试的一部分,我想检查我的代码中几个模块的输出。没有固定数量的模块;可以随时添加更多模块。但是,测试每个模块的代码是相同的。因此,我认为将测试代码放在 for
循环中是最理想的。事实上,使用 catch.hpp
,我已经验证我可以在测试用例中动态创建部分,其中每个部分对应一个模块。我可以通过在 for 循环中包含 SECTION
宏来做到这一点,例如:
#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 文档并进行了在线搜索,但找不到有关如何执行此操作的任何说明。
有一种方法可以实现您正在寻找的东西,但我发现您的做法是错误的:-
单元测试旨在测试每个单元,即您编写一个组件和一个测试来验证该组件的正确行为。如果您后来决定以某种方式更改一个组件,则更新相应的测试。
如果将所有组件的所有测试汇总到同一个文件中,则隔离行为不同的单元会变得更加困难。
如果你想排除一个组件的测试,因为它在你的所有组件中基本相同,你可以执行以下操作之一:
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"
这很简单,但有一个缺点 - 当您 运行 测试 运行 时,您将有重复的测试(按名称),因此您只能 运行 所有测试,或按标签。
您可以通过将组件名称字符串化并将其 pre/appending 到测试用例名称来解决此问题。
** 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 可能正在转向基于 属性 的测试,我希望这将允许一种动态创建测试用例的方法。与此同时,这就是我最终做的事情。
我创建了一个 .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;
}
是的,它很丑,但我已经确认它有效。