Как правильно скомпилировать несколько тестовых источников с Catch2? - PullRequest
2 голосов
/ 15 марта 2019

У меня есть следующая структура проекта:

test_main.cc

#define CATCH_CONFIG_MAIN

#include "catch2.hpp"

test1.cc

#include "catch2.hpp"
#include "test_utils.hpp"

TEST_CASE("test1", "[test1]") {
  REQUIRE(1 == 1);
}

test2.cc

#include "catch2.hpp"
#include "test_utils.hpp"

TEST_CASE("test2", "[test2]") {
  REQUIRE(2 == 2);
}

test_utils.hpp

#pragma once
#include <iostream>

void something_great() {
  std::cout << ":)\n";
}

Если я скомпилирую, используя что-то вроде clang++ -std=c++17 test_main.cc test1.cc test2.cc, функцияsomething_great определяется как в test1.o, так и в test2.o.Это приводит к ошибке, подобной

duplicate symbol __Z15something_greatv in:
    test1.cc.o
    test2.cc.o
ld: 1 duplicate symbol for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

В разделе Scaling Up документации Catch2 они упоминают, что для разделения ваших тестов вы можете захотеть

Используйте столько дополнительных файлов cpp (или как вы называете ваши файлы реализации), сколько вам нужно для ваших тестов, разбитых на разделы, однако наиболее подходящих для вашего способа работы.Для каждого дополнительного файла требуется только #include "catch.hpp"

, но в разделе примеров документации я не вижу варианта использования, подобного моему.Я прочитал этот пост , в котором описаны три решения, которые мне не нравятся: определение функций как макросов или создание функций static или inline.

Есть лидругой способ скомпилировать эти файлы, которые дают один исполняемый файл с основной функцией, определенной как test_main.cc?

Ответы [ 2 ]

1 голос
/ 15 марта 2019

Это на самом деле не имеет ничего общего с Catch или тестированием.Когда вы #include файл в C ++, он вставляется в копию в дословной строке #include.Если вы поместите определения свободных функций в заголовки, вы увидите, что эта проблема строит вашу реальную программу и т. Д.

Основная проблема заключается в том, что #include - это не тот тип директивы import-a-module, какэквивалентная директива (import, require и т. д.) в большинстве языков, которые делают нормальную вещь в такой ситуации (подтвердите, что заголовок тот же, что мы уже видели, и игнорируйте повторное определение метода).

Комментатор, предложивший вам написать inline, технически корректен в том смысле, что это «решит вашу проблему», поскольку ваш компилятор не будет генерировать объектный код для метода несколько раз.Однако в действительности это не объясняет, что происходит, или не решает основную проблему.


Чистое решение:

  • В test_utils.hpp замените определение метода наметод объявление : void something_great();.
  • Создать test_utils.cc с определением метода (которое вы в настоящее время используете в .hpp).
  • clang++ -std=c++17 test1.cc -c
  • clang++ -std=c++17 test2.cc -c
  • clang++ -std=c++17 test_main.cc -c
  • clang++ -std=c++17 test_utils.cc -c
  • clang++ -std=c++17 test1.o test2.o test_utils.o test_main.o

Я также рекомендую вамПрочитайте это: В чем разница между определением и объявлением?

Явно:

// test_utils.hpp
#pragma once

// This tells the compiler that when the final executable is linked,
// there will be a method named something_great which takes no arguments
// and returns void defined; the definition lives in test_utils.o in our
// case, although in practice the definition could live in any .o file
// in the final linking clang++ call.
void something_great();

И:

// test_utils.cpp
#include "test_utils.hpp"
#include <iostream>

// Generates a DEFINITION for something_great, which
// will get put in test_utils.o.
void something_great() { std::cout << "Hi\n"; }

Кажется, вы беспокоитесь о «перекомпиляции Catch» каждый раз, когда вносите изменения в тест.Я не хочу рассказывать вам об этом, но вы сейчас находитесь на земле C ++: вы будете бессмысленно много перекомпилировать материал.Библиотеки только для заголовков, такие как Catch, ДОЛЖНЫ быть до некоторой степени «перекомпилированы» при изменении исходного файла, включающего их, потому что, к лучшему или худшему, если исходный файл или файл заголовка, включенный транзитивно из исходного файла, включает catch2.hpp, то исходный файлкод catch2.hpp будет проанализирован компилятором при чтении этого исходного файла.

0 голосов
/ 15 марта 2019

После некоторых экспериментов я нашел разумное решение, которое не требует от вас полной перекомпиляции Catch каждый раз, когда вы вносите изменения в тест.

Определите test_main.cc так же, как и раньше:

#define CATCH_CONFIG_MAIN

#include "catch2.hpp"

Добавьте еще один файл .cc, test_root , который включает ваши тестовые файлы в качестве заголовков:

#include "test1.hpp"
#include "test2.hpp"

Измените ваши тестовые источники на заголовки:

test1.hpp

#pragma once
#include "catch2.hpp"
#include "test_utils.hpp"

TEST_CASE("test1", "[test1]") {
  REQUIRE(1 == 1);
}

test2.hpp

#pragma once
#include "catch2.hpp"
#include "test_utils.hpp"

TEST_CASE("test2", "[test2]") {
  REQUIRE(2 == 2);
}

Компиляция отдельно

clang++ -std=c++17 test_main.cc -c
clang++ -std=c++17 test_root.cc -c
clang++ test_main.o test_root.o

Где test_main.cc должен быть скомпилирован только один раз. test_root.cc необходимо будет перекомпилировать всякий раз, когда вы меняете свои тесты, и, конечно, вы должны связать два объектных файла.

Я пока оставлю этот ответ неприемлемым, если найдутся лучшие решения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...