每个 CTest 可执行文件都有几 MB - 我如何使它们更小?

山姆·雅克

我有一堆单元测试,我想从 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表明这实际上是不可能的)。

无论如何都可以帮我找到:

  1. 一个可以避免所有这些问题的跨平台 C++ 测试框架?
  2. 一种在 ctest 中编译单元测试以共享大量相同代码的方法?
  3. 关于如何动态链接 C++ 类的说明?
舍夫的猫
  1. 一个可以避免所有这些问题的跨平台 C++ 测试框架?

忽略代码建议偏离主题的事实……
我们正在使用catch2,我对此很满意。

  1. 一种在 ctest 中编译单元测试以共享大量相同代码的方法?

测试也是其他应用。因此,下一个问题的答案应该涵盖这一点。

  1. 关于如何动态链接 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_EXPORTDLL_IMPORT以隐藏 Windows (Visual Studio) 和 Linux (g++) 之间的不同声明属性。

这些定义可以在多个库之间共享。

第二组宏是特定BIG_CLASS_库的——因此,所有中都有前缀

BIG_CLASS_STATIC是一个宏,当(且仅当)库被构建为静态库时,它应该被定义为编译器的参数。如果静态库必须链接到可执行文件,也必须定义它。

BUILD_BIG_CLASS是一个宏,当(且仅当)库被构建并链接为 DLL(或 Linux 中的共享对象)时,它应该被定义为编译器的参数。

这两个宏都被认为准备了第三个宏BIG_CLASS_API,该扩展为

  • 静态库什么都没有
  • DLL_EXPORT 如果构建了 DLL
  • DLL_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

此外,两个测试应用程序Test1Test2应与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)

关于动态链接有一些具体的事情:

  1. option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
    这在CMake:第 9 步:选择静态或共享库中进行了解释

  2. set_target_properties(BigClass PROPERTIES DEFINE_SYMBOL BUILD_BIG_CLASS)
    这可确保BUILD_BIG_CLASSBigClass构建库时在编译器的命令行中专门定义

  3. 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 中的外观:

VS 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] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

如何使 ctest 在临时/临时目录中运行测试可执行文件

CMake:如何指定ctest应该在其中查找可执行文件的目录?

如何使我的可执行文件更小?

如何获得文件夹中每个可执行文件的简短描述?

在Makefile中,如何为每个C文件构建单独的可执行文件

我读取的每个可执行文件都以MZ标头开头

cmake:如何迭代目录中的所有源文件并将每个源文件构建为可执行文件?

列出CTest测试套件中可执行文件的位置

每个可执行文件的不同链接器选项

如何安装可执行文件

如何查找可执行文件

如何在我的ctest cmake文件中添加-j选项?

没有设置可执行位时,如何从CD运行可执行文件?

如何验证多个文件的最大文件大小为每个文件2 mb?(验证)

当隐式规则没有提及可执行文件时,make 如何从 C 源文件构建可执行文件?

我可以从 webview 执行可执行文件吗?

有什么方法可以阻止用户创建可执行文件并运行它们?

Unix记帐:没有磁盘可执行文件的lastcomm命令-它们是什么?

如何在没有可执行文件弹出控制台的情况下在Python中运行可执行文件?

如何使用gdb单步执行可执行文件?

如何将文件作为可执行文件发送?(没有git)

如何获取python可执行文件的文件路径

为什么SmartScreen筛选器会阻止我的所有可执行文件?

为什么我有多个python可执行文件?

CMake没有创建可访问我的共享库的可执行文件

我想将具有属性的 rust 可执行文件分组为 cargo build --release

为什么每个sudo执行的可执行文件都需要sudo?

如何抑制pyinstaller生成的可执行文件窗口中的所有警告

如何列出Linux中的所有可执行文件(目录,子目录,$ PATH)?