【问题标题】:Unit testing non-exported classes in a DLL对 DLL 中的非导出类进行单元测试
【发布时间】:2011-07-26 16:44:19
【问题描述】:

我们使用 Visual Studio 2008 开发 C++ 应用程序,并使用 Boost.Test 进行单元测试。目前,我们有一个单独的解决方案,其中包含我们的单元测试。

我们在核心解决方案中的许多项目都会生成 DLL。我们的测试覆盖率有限,因为我们无法测试非导出的类。

我对如何测试这些有两个想法:

  1. 导出所有内容
  2. 将测试放在 DLL 中(相同的项目和解决方案)并使用 Boost.Test 的外部运行器

我不完全确定缺点是什么。上面的数字 1 破坏了模块级别的封装,而数字 2 可能会导致更大的 DLL,除非可以仅在某些配置中包含测试代码。

那么,上述方法有什么严重的缺陷,或者你能想到其他的解决方案吗?

【问题讨论】:

  • 我想暗示CMake 提供了一个名为“对象库”的功能。 (add_library( foo_obj OBJECT ... )) 在我的项目中,我将源代码构建到对象库中,然后将其链接到 both DLL (add_library( foo SHARED ... $<TARGET_OBJECTS:foo_obj> )) 它的测试驱动程序 (@987654324 @)。这是下面使用不同构建系统的答案的变体(这就是为什么我将其添加为评论,而不是答案),但它解决了同样的问题。

标签: c++ visual-studio unit-testing boost-test


【解决方案1】:

扩展 Tom Quarendon's answer to this question,我使用了 Simon Steele 回复的轻微变体:

  • 创建一个测试项目(使用你喜欢的任何测试框架,我使用CppUnit)。
  • 在您的 test_case.cpp 中,#include <header/in/source/project.h>
  • 在测试项目属性中:
    • 在 Linker->General 中,将源项目的 $(IntDir) 添加到 Additional Library Directories。
    • 在 Linker->Input 中,将 .obj 文件添加到 Additional Dependencies。
  • 在Project->Project Dependencies中将测试项目的依赖添加到源项目中。

同样,唯一的维护开销是单元测试的标准开销 - 创建对您要测试的单元的依赖关系。

【讨论】:

  • 关于此方法的另一个细节:如果您的 DLL 使用预编译的头文件,则所有链接以生成被测 DLL 的 .obj 文件都依赖于预编译的头文件。正在测试的 DLL。在构建测试项目时,这会导致链接器错误 LNK2011: precompiled object not linked in;图像可能无法运行。除了您正在测试的特定目标文件之外,您还必须添加 stdafx.obj(如果您的 PCH 文件是通过编译 stdafx.cpp 生成的)。
  • 此方法的另一个注意事项:如果您的测试项目使用多个包含需要测试的同名目标文件的 dll,您可以在 Linker->Input 设置中指定完整路径,例如$(SolutionDir)\<project_name>t\$(Platform)\$(Configuration)\<object_name>.obj 来区分它们。
  • 你可能还需要/FORCE:MULTIPLE,正如我刚刚发现的那样。
  • 为了避免输入所有文件名,可以在命令行的附加选项中使用通配符代替,如stackoverflow.com/a/25776187/640195
  • 为什么我应该拥有 obj 而不是 lib?这里有什么区别?因为它只适用于 .obj,不适用于 .lib
【解决方案2】:

我为此使用的解决方案是将相同的非导出代码也构建到我的测试 DLL 中。这确实会增加构建时间并意味着将所有内容添加到两个项目中,但可以节省导出所有内容或将测试放入主要产品代码中的操作。

另一种可能性是将未导出的代码编译成一个库,供带导出的 DLL 和单元测试项目使用。

【讨论】:

  • 这可能适用于小型项目,但我们有很多代码,因此必须在两个地方进行更改将是一场维护噩梦。
  • 唯一需要做的更改是添加或删除文件时。因此,如果添加了包含需要进行单元测试的代码的新 CPP 文件,则需要将其添加到两个项目中。源代码没有两个副本,每个包含可测试代码的源文件都包含在两个项目中。
  • 这是我的第一个方法,它对我有用。我想到了一个潜在的问题——有时一个 dll 项目使用与测试项目不同的编译标志。因此,dll 和测试项目可以为同一个源文件创建不同的目标文件。虽然在我的情况下我很确定它们是相同的,但通常测试 dll 项目创建的目标文件比测试测试项目创建的目标文件更安全。我最终将我的方法转换为@Rai 描述的方法。
【解决方案3】:

也在寻找解决方案,也许以下会更容易维护。

添加新的构建配置,例如“Unit testing Debug”为DLL项目,将Configuration Type改为“Static Library.lib”(“General”->“Configuration Type”)。

然后只需在此项目上添加单元测试的依赖项,现在当您使用新的构建配置“单元测试调试”时,所有内容都应该链接在一起。 如果您使用发布版本进行单元测试,那么您需要添加另一个具有发布优化的配​​置。

所以这个解决方案的好处是:

  • 维护成本低
  • 单个 DLL/静态库项目
  • 不必手动链接到 .obj 文件

缺点:

  • 额外的配置文件将需要对您的构建环境 (CI) 进行一些更改
  • 编译时间更长

更新: 我们实际上最终使用了不同的方法。

我们为每个现有项目添加了新的“测试调试”/“测试发布”配置。

对于 .exe/.dll 项目,我们禁用原始 main.cpp 编译并将其替换为实例化测试框架(例如 gtest)并运行所有测试的文件,测试位于单独的 .cpp 文件中也从常规配置(发布/调试)中的编译中排除,并且仅在测试配置中启用。

对于 .lib 项目,我们还有新的“测试调试”/“测试发布”配置,我们将静态库转换为 .exe 文件,并提供一个 main.cpp 实例化测试框架并运行测试和测试自己。测试相关文件从发布/调试配置的编译中排除。

【讨论】:

    【解决方案4】:

    尝试在所有文件都包含的地方进行如下定义:

    #define EXPORTTESTING __declspec(dllexport)
    

    并使用它代替 dllexport,如下所示:

    class EXPORTTESTING Foo 
    {
     ...
    };
    

    然后您将能够关闭用于构建发布 DLL 的标志,但为可单元测试的 DLL 保持开启。

    【讨论】:

    • 不确定这样做是不是一个好方法... 可测试的代码不应被修改以进行测试。即使它是一个简单的宏。
    猜你喜欢
    • 1970-01-01
    • 2019-06-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多