用Catch2编译多个测试源的正确方法是什么?

时间:2019-03-14 22:14:39

标签: c++ catch2

我具有以下项目结构:

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的东西进行编译,则在test1.o和test2.o中都定义了函数something_great。这会导致类似

的错误
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)

在Catch2文档的Scaling Up部分中,他们提到为了分散测试,您可能需要

  

使用尽可能多的其他cpp文件(或任何您称为“   实施文件),但需要进行分区   最适合您的工作方式。每个额外的文件需求   仅#include“ catch.hpp”

但是在examples section of the documentation中,我没有看到像我这样的用例。我读过this blog post,其中描述了三种不适合我的解决方案:将函数定义为宏,或者使函数staticinline

是否存在另一种编译这些文件的方法,这些文件生成的单个可执行文件具有test_main.cc定义的主要功能?

2 个答案:

答案 0 :(得分:1)

这实际上与Catch或测试无关。当您#include使用C ++编写的文件时,该文件会逐字复制到#include行。如果将免费的函数定义放在标头中,则会在构建实际程序等时看到此问题。

潜在的问题是,#include与大多数语言中的等效指令(importrequire等)不是同一种import-a-module指令,可以在这种情况下执行明智的操作(确认标题与我们已经看到的标题相同,并且忽略重复的方法定义)。

建议您编写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

我还建议您阅读以下内容:What is the difference between a definition and a declaration?

明确地:

// 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的源代码。

答案 1 :(得分:-1)

经过一些试验,我找到了一个合理的解决方案,它不需要您在对测试进行更改时就完全重新编译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,当然,您必须重新链接两个目标文件。

如果有更好的解决方案,我暂时不会回答这个问题。