Я использую 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*/);
}
}
}
Это может быть возможно сделать пишу свой main()
, но я не вижу, как именно это сделать. (Буду ли я поставить свой TEST_CASE
код непосредственно в main()
? Что делать, если я хочу сохранить свой TEST_CASE
код в другом файле? Кроме того, это повлияет на мои другие, более стандартные тестовые случаи?)
Я также могу использовать CHECK
макросы вместо REQUIRE
макросы, чтобы избежать прерывания тестового примера, когда модуль выходит из строя, но затем я получаю противоположную проблему: он пытается продолжить тест на модуле, который должен был произойти сбой в начале. Если бы я мог просто поместить каждый модуль в свой собственный тестовый пример, это дало бы мне идеальное поведение.
Есть ли простой способ динамически создавать тестовые случаи в CATCH? Если да, можете ли вы дать мне пример того, как это сделать? Я прочитал документацию CATCH и искал в Интернете, но не смог найти никаких указаний, как это сделать.
Есть способ достичь того, что вы ищете, но я бы заметил, что вы идете по этому пути неправильно: —
Модульные тесты предназначены для тестирования каждого модуля, то есть вы пишете компонент и тест для проверки правильности поведения этого компонента. Если позже вы решите каким-либо образом изменить один компонент, вы обновите соответствующий тест.
Если вы объединяете все свои тесты для всех ваших компонентов в один файл, становится намного сложнее изолировать модуль, который ведет себя по-разному.
Если вы хотите исключить тестирование компонента, поскольку оно практически одинаково для всех ваших компонентов, вы можете выполнить одно из следующих действий:
1. Распакуйте общие тесты в отдельный заголовочный файл.
Вы можете # определить имя типа компонента, который вы хотите протестировать, а затем включить заголовочный файл со всеми тестами в нем:
// 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 может перейти к тестированию на основе свойств, которое, я надеюсь, позволит динамически создавать тестовые случаи. А пока вот что я в итоге сделал.
Я создал .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;
}
Да, это некрасиво, но я подтвердил, что это работает.