我有一堆单元测试,我想从 Visual Studio 的测试框架迁移到更易于访问的测试框架。我开始将一些移至 ctest,其中(据我所知)每个测试都需要编译为单独的可执行文件。
我的 cmakelists.txt 看起来像:
cmake_minimum_required(VERSION 3.10)
project(UnitTests)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_MACOSX_RPATH 1)
include(CTest)
enable_testing()
add_executable(Test1 Test1.cpp)
add_executable(Test2 Test2.cpp)
add_test(First Test1)
add_test(Second Test2)
问题是 Test1 和 Test2 分别编译为 2.7 MB 和 2.6 MB 的文件。这是因为每个人都必须拥有#include "BigClass.h"
,BigClass
我真正想要测试的庞大而庞大的项目在哪里。
我可能有几十个测试想要迁移,因此编译和运行所有这些测试可能会遇到数百兆字节的可执行文件,其中大部分空间只是重复来自BigClass
!
我试图将这个类作为一个库包含在内:
add_library(BigClass SHARED ../path/to/BigClass.cpp)
然后在文件末尾:
target_link_libraries(Test1 PUBLIC BigClass)
但这并不影响空间。
我想我想要的是一种动态链接到的方法BigClass
(无论如何都会编译),因此所有测试都可以共享代码。但是我不确定如何执行此操作,也不太确定如何搜索它(例如,搜索动态链接#include
表明这实际上是不可能的)。
无论如何都可以帮我找到:
- 一个可以避免所有这些问题的跨平台 C++ 测试框架?
忽略代码建议偏离主题的事实……
我们正在使用catch2,我对此很满意。
- 一种在 ctest 中编译单元测试以共享大量相同代码的方法?
测试也是其他应用。因此,下一个问题的答案应该涵盖这一点。
- 关于如何动态链接 C++ 类的说明?
我制作了一个MCVE,试图涵盖整个故事。做好准备……
当代码被编写为 DLL 的一部分时,它必须导出所有在其他 DLL 或可执行文件中可见的符号。其他 DLL 和可执行文件必须依次导入这些符号。微软提供了对这个主题的一般介绍:dllexport, dllimport。
有一个常用的宏技巧来确保可以从 DLL 内部(dllexport
需要的地方)和 DLL 外部(需要的地方)使用相同的标头dllimport
。
这在以下方面得到了证明BigClass.h
:
#ifndef BIG_CLASS_H
#define BIG_CLASS_H
/* A macro trickery to provide export and import prefixes
* platform dependent for Windows and Linux.
*/
#ifdef _WIN32
// for Windows, Visual C++
#define DLL_EXPORT __declspec(dllexport)
#define DLL_IMPORT __declspec(dllimport)
#else // (not) _WIN32
// for gcc
#define DLL_EXPORT __attribute__((visibility("default")))
#define DLL_IMPORT __attribute__((visibility("default")))
#endif // _WIN32
/* The macro trickery to define how exported symbols shall
* be included.
*/
#ifdef BIG_CLASS_STATIC
#define BIG_CLASS_API
#else // (not) BIG_CLASS_STATIC
#ifdef BUILD_BIG_CLASS
#define BIG_CLASS_API DLL_EXPORT
#else // (not) BUILD_BIG_CLASS
#define BIG_CLASS_API DLL_IMPORT
#endif // BUILD_BIG_CLASS
#endif // BIG_CLASS_STATIC
// the library interface
#include <iostream>
struct BigClass {
const int id = 0;
// inline constructors doesn't need an export guard
BigClass() = default;
// non-inline constructors need an export guard
BIG_CLASS_API BigClass(int id);
// non-inline functins as well
BIG_CLASS_API void print() const;
// no export guard for inline member functions
void printLn() const
{
print();
std::cout << std::endl;
}
};
// functions need to be exported
extern BIG_CLASS_API void print(const BigClass&);
// inline functions don't need to be exported
inline void printLn(const BigClass& big)
{
big.printLn();
}
#endif // BIG_CLASS_H
因此,引入了第一个宏DLL_EXPORT
和DLL_IMPORT
以隐藏 Windows (Visual Studio) 和 Linux (g++) 之间的不同声明属性。
这些定义可以在多个库之间共享。
第二组宏是特定BIG_CLASS_
于库的——因此,所有宏中都有前缀。
这BIG_CLASS_STATIC
是一个宏,当(且仅当)库被构建为静态库时,它应该被定义为编译器的参数。如果静态库必须链接到可执行文件,也必须定义它。
这BUILD_BIG_CLASS
是一个宏,当(且仅当)库被构建并链接为 DLL(或 Linux 中的共享对象)时,它应该被定义为编译器的参数。
这两个宏都被认为准备了第三个宏BIG_CLASS_API
,该宏扩展为
DLL_EXPORT
如果构建了 DLLDLL_IMPORT
如果构建了其他任何旨在使用和链接 DLL 的内容。相应的BigClass.cc
:
#include "BigClass.h"
BigClass::BigClass(int id): id(id) { }
void BigClass::print() const
{
std::cout << "BigClass::print(): *this: BigClass { id: " << id << " }";
}
void print(const BigClass& big)
{
std::cout << "print(): "; big.print();
}
没有什么特别之处。
BigClass.h
并将BigClass.cc
用于构建示例BigClass.dll
。
此外,两个测试应用程序Test1
和Test2
应与BigClass.dll
.
Test1.cc
:
#include <iostream>
#include <BigClass.h>
#define TEST(...) std::cout << #__VA_ARGS__ << ";\n"; __VA_ARGS__
int main()
{
std::cout
<< "Test1:\n"
<< "======\n";
TEST(BigClass big);
TEST(big.print());
std::cout << '\n';
TEST(big.printLn());
TEST(BigClass big1(1));
TEST(big1.print());
std::cout << '\n';
TEST(big1.printLn());
std::cout << "Done." << std::endl;
}
Test2.cc
:
#include <iostream>
#include <BigClass.h>
#define TEST(...) std::cout << #__VA_ARGS__ << ";\n"; __VA_ARGS__
int main()
{
std::cout
<< "Test2:\n"
<< "======\n";
TEST(BigClass big);
TEST(print(big));
std::cout << '\n';
TEST(printLn(big));
TEST(BigClass big2(2));
TEST(print(big2));
std::cout << '\n';
TEST(printLn(big2));
std::cout << "Done." << std::endl;
}
同样,它们也没有什么特别之处。
免责声明:宏TEST()
与单元测试无关。这只是一种演示摆弄。
最后,CMakeLists.txt
将所有内容放在一起(例如在 VS 解决方案中BigClassDLL.sln
):
project (BigClassDLL)
cmake_minimum_required(VERSION 3.10.0)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
if (MSVC)
# Unfortunately neither
# CMAKE_CXX_STANDARD_REQUIRED ON
# nor
# CMAKE_CXX_EXTENSIONS OFF
# force the /permissive- option
# which makes MSVC standard conform.
add_compile_options(/permissive-)
endif()
# enable shared libraries (aka. DLLs)
option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
include_directories("${CMAKE_SOURCE_DIR}")
# build the library
add_library(BigClass
BigClass.cc BigClass.h)
set_target_properties(BigClass
PROPERTIES
PROJECT_LABEL "Big Class DLL"
DEFINE_SYMBOL BUILD_BIG_CLASS)
if (NOT BUILD_SHARED_LIBS)
target_compile_definitions(BigClass PUBLIC BIG_CLASS_STATIC)
endif()
# build the tests
add_executable(Test1 Test1.cc)
target_link_libraries(Test1 BigClass)
add_executable(Test2 Test2.cc)
target_link_libraries(Test2 BigClass)
关于动态链接有一些具体的事情:
option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
这在CMake:第 9 步:选择静态或共享库中进行了解释。
set_target_properties(BigClass PROPERTIES DEFINE_SYMBOL BUILD_BIG_CLASS)
这可确保BUILD_BIG_CLASS
在BigClass
构建库时在编译器的命令行中专门定义宏。
if (NOT BUILD_SHARED_LIBS)
target_compile_definitions(BigClass PUBLIC BIG_CLASS_STATIC)
endif()
宏BIG_CLASS_STATIC
将在BigClass
构建或使用库的每个编译器命令行中定义。
免责声明:我知道CMakeLists.txt
用于多个项目的一种被认为是不好的风格。当然,对于一个严肃的项目,库应该与自己的CMakeLists.txt
文件一起驻留在自己的目录中。我把它放在一起是为了尽可能地减少它(也是因为我可以)。
它在 Visual Studio 2019 中的外观:
Visual Studio 2019 的构建工件:
运行Test1.exe
:
Test1:
======
BigClass big;
big.print();
BigClass::print(): *this: BigClass { id: 0 }
big.printLn();
BigClass::print(): *this: BigClass { id: 0 }
BigClass big1(1);
big1.print();
BigClass::print(): *this: BigClass { id: 1 }
big1.printLn();
BigClass::print(): *this: BigClass { id: 1 }
Done.
运行Test2.exe
:
Test2:
======
BigClass big;
print(big);
print(): BigClass::print(): *this: BigClass { id: 0 }
printLn(big);
BigClass::print(): *this: BigClass { id: 0 }
BigClass big2(2);
print(big2);
print(): BigClass::print(): *this: BigClass { id: 2 }
printLn(big2);
BigClass::print(): *this: BigClass { id: 2 }
Done.
最后(为了解决可移植性),我在 Linux 中构建了相同的 CMake 项目:
Scheff'sCat@debian:/mnt/hgfs/hostD/Entwicklung/tests/C++/dll$ mkdir build-Linux
Scheff'sCat@debian:/mnt/hgfs/hostD/Entwicklung/tests/C++/dll$ cd build-Linux
Scheff'sCat@debian:/mnt/hgfs/hostD/Entwicklung/tests/C++/dll/build-Linux$ ccmake ..
Scheff'sCat@debian:/mnt/hgfs/hostD/Entwicklung/tests/C++/dll/build-Linux$ cmake --build .
Scanning dependencies of target BigClass
[ 16%] Building CXX object CMakeFiles/BigClass.dir/BigClass.cc.o
[ 33%] Linking CXX shared library libBigClass.so
[ 33%] Built target BigClass
Scanning dependencies of target Test2
[ 50%] Building CXX object CMakeFiles/Test2.dir/Test2.cc.o
[ 66%] Linking CXX executable Test2
[ 66%] Built target Test2
Scanning dependencies of target Test1
[ 83%] Building CXX object CMakeFiles/Test1.dir/Test1.cc.o
[100%] Linking CXX executable Test1
[100%] Built target Test1
Scheff'sCat@debian:/mnt/hgfs/hostD/Entwicklung/tests/C++/dll/build-Linux$ ls
CMakeCache.txt CMakeFiles cmake_install.cmake libBigClass.so Makefile Test1 Test2
Scheff'sCat@debian:/mnt/hgfs/hostD/Entwicklung/tests/C++/dll/build-Linux$ ./Test1
Test1:
======
BigClass big;
big.print();
BigClass::print(): *this: BigClass { id: 0 }
big.printLn();
BigClass::print(): *this: BigClass { id: 0 }
BigClass big1(1);
big1.print();
BigClass::print(): *this: BigClass { id: 1 }
big1.printLn();
BigClass::print(): *this: BigClass { id: 1 }
Done.
Scheff'sCat@debian:/mnt/hgfs/hostD/Entwicklung/tests/C++/dll/build-Linux$ ./Test2
Test2:
======
BigClass big;
print(big);
print(): BigClass::print(): *this: BigClass { id: 0 }
printLn(big);
BigClass::print(): *this: BigClass { id: 0 }
BigClass big2(2);
print(big2);
print(): BigClass::print(): *this: BigClass { id: 2 }
printLn(big2);
BigClass::print(): *this: BigClass { id: 2 }
Done.
Scheff'sCat@debian:/mnt/hgfs/hostD/Entwicklung/tests/C++/dll/build-Linux$
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句