使用 Catch2 编译多个测试源的正确方法是什么?
What is the correct way to compile multiple test sources with 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
之类的东西进行编译,函数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)
在 Catch2 文档的"向上扩展"部分中,他们提到为了拆分测试,您可能需要
使用尽可能多的其他 cpp 文件(或任何您称之为 实现文件)作为测试的需要,但是分区 对您的工作方式最有意义。每个额外的文件需要 只有 #include"catch.hpp">
但是在文档的示例部分中,我没有看到像我这样的用例。我读了这篇博文,其中描述了三种对我没有吸引力的解决方案:将函数定义为宏,或使函数static
或inline
。
有没有另一种方法来编译这些文件,这些文件会产生一个具有test_main.cc
定义的 main 函数的可执行文件?
这实际上与 Catch 或测试无关。当您#include
C++中的文件时,它会逐字复制粘贴到#include
行。如果你把自由函数定义放在头中,你会看到构建实际程序等问题,等等。
根本的问题是#include
与大多数语言中的等效指令(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 << "Hin"; }
似乎您担心每次更改测试时都会"重新编译 Catch"。我不想打破它给你,但你现在处于C++境:你将毫无意义地重新编译东西很多。当包含它们的源文件更改时,像 Catch CATCH 这样的纯头库必须在某种程度上"重新编译",因为无论好坏,如果源文件或从源文件传递包含的头文件包含catch2.hpp
,那么当读取该源文件时,编译器将解析catch2.hpp
的源代码。
经过一些实验,我找到了一个合理的解决方案,不需要你在对测试进行更改时完全重新编译 Catch。
以与以前相同的方式定义test_main.cc:
#define CATCH_CONFIG_MAIN
#include "catch2.hpp"
添加另一个 .cc 文件,test_root其中包含测试文件作为标头:
#include "test1.hpp"
#include "test2.hpp"
将测试源更改为标头:
测试1.hpp
#pragma once
#include "catch2.hpp"
#include "test_utils.hpp"
TEST_CASE("test1", "[test1]") {
REQUIRE(1 == 1);
}
测试2.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,当然必须重新链接两个目标文件。
我将暂时不接受这个答案,以防有更好的解决方案。
- 为不同配置设置MSVC_RUNTIME_LIBRARY的正确方法是什么
- 在C++中,将大的无符号浮点数四舍五入为整数的最佳方法是什么
- 实现无开销push_back的最佳方法是什么
- C++从另一个类访问公共静态向量的正确方法是什么
- 在 c++ 中拥有一组结构的正确方法是什么?
- 通过JNI传递数据数组的最快方法是什么
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 使用不同的CRT将新的C++代码与旧的(二进制)组件隔离开来的最佳方法是什么
- 当无法使用模板和宏时,生成类型变体C++代码的最简单方法是什么?
- 在另一个类视图中添加最多2个图表的正确方法是什么
- 在C++中样板"冷/never_inline"错误处理技术的最佳方法是什么?
- 在 c++ 中对类中的 c 字符串动态数组进行排序的最佳方法是什么?
- 在C++中包含原型文件的正确方法是什么?
- 在 OpenCV C++ 中估计基本矩阵之前对相应点进行归一化的正确方法是什么?
- 在PostgreSQL中根据它们的ID选择大量行的最快方法是什么?
- 在OSX上使用CMake将Adobe的XMP工具包构建为共享库的最简单方法是什么?
- 将一系列整数放入类的最佳方法是什么?
- 从长整整转换为uint64_t的推荐方法是什么?
- 将此布尔值传递给此函数的最有效方法是什么?
- 通过比较C++中的行在 txt 文件中搜索的最简单方法是什么?