能否使用MSTest测试C++ DLL中的“internal”类?

10
我们目前正在尝试为我们的c++应用程序添加单元测试。该应用程序由30个项目组成,生成29个dll和1个exe。由于MSTest已经包含在Visual Studio 2010中,因此我们使用它来运行我们的单元测试。
对于被声明为“public”的类,测试非常顺利。这些类在开头有以下内容:
#ifdef RESEAU_IMPL
    #define CLASS_DECL      _declspec(dllexport)
#else
    #define CLASS_DECL      _declspec(dllimport)
#endif 

但对于其他类(代码的90%),它们没有被声明为public,因此我们无法在测试中使用它们。
我在谷歌上读到了InternalVisibleTo属性,但它似乎只适用于c# .NET程序集。我是对的吗?我也读到了要将我的类声明为“as_friend”,但我不确定应该放在哪里。
简而言之:我想测试DLL中未公开/公共的类。我该怎么做?
谢谢
*编辑*
Gishu评论说,在非托管代码中无法进行单元测试,但确实是可能的。看,这是一个测试本地C ++代码的TestMethod。 CVersion在C ++ MFC中。
[TestMethod]
void AssignationCVersion()
{
    CVersion version1234(1,2,3,4);
    CVersion version4321(4,3,2,1);
    Assert::IsTrue(version1234 != version4321);
    version1234 = version4321;
    Assert::IsTrue(version1234 == version4321);
};

但是似乎不可能使用特殊标签来测试内部函数。我第一个同意测试内部方法并不是好的实践,但这些DLL不仅仅是实用程序函数,它们也是“真正”的应用程序的一部分(也许这是糟糕的设计,但是15年前就这样做了)。有人对此有什么想法吗?


C++不支持反射。你无法测试无法调用的代码。 - Hans Passant
你是否正在使用托管的 C++ 测试 dll 调用非托管的 SUT?这是我第一次听说这种用法。在 MSDN 上也找不到任何文档。我们公司的人使用 Google 的 fwk 或 cppunit……但如果你想要 IDE 集成和协作,那就不行了。 - Gishu
嗨,这是我们测试“架构”的文档基础:http://msdn.microsoft.com/en-us/library/ms243171.aspx。正如您所看到的,它被标记为“不支持”,但他们提供了一种方法,它确实有效。这个MSdn页面非常具有误导性。 - Jean-François Côté
3个回答

12
参见问题: 在DLL中对非导出类进行单元测试 三种选择似乎是:
  • 将测试代码放入DLL中,以便可以访问非导出类和函数
  • 将包含测试代码的所有文件添加到测试项目中,以使它们编译两次(不知道MSTEst是否适用于此,但使用类似于Boost测试或CPPunit的东西会是这样做的方式)
  • 将所有非导出可测试代码构建为静态库,然后将其链接到测试代码和DLL。
这些都有不同的问题。
将测试代码放入DLL中并不理想。要么只在非生产构建中包含它,这样您就无法测试发布的内容;要么在所有构建中都包含它,这样您就会发布测试代码,这可能是不希望的。此外,还需要某种入口点来访问这些测试,从而强制编译器包含所有代码,如果否则被视为不可访问,则防止优化程序删除它(可能有一些您正在测试的代码无法从DLL中的任何公共方法访问,因此优化器可以决定将其删除为死代码--将测试放在DLL中可以避免这种情况)。
将源文件添加到两个项目中会增加构建时间和维护复杂性。每次添加新源文件或删除源文件时,都需要在两个位置中添加它。此外,根据代码的大小,这可能会显著增加构建时间,因为它必须将大量代码编译两次。
将所有非导出可测试代码放入静态库中的缺点是创建了一个额外的项目,并使组织更加复杂。您需要注意代码结构(例如,一个源文件应仅包含导出或非导出代码),并且意味着您需要针对导出部分和非导出部分分别进行测试项目。但是,它意味着代码只编译一次,测试不是最终可执行文件的一部分,优化程序可以完全工作。
根据DLL公共接口的大小,即导出类/函数的数量,我认为第三个选项是最可行的。通常,您只有一个小的公共接口,它是较大内部结构的门面。除了公共门面之外的所有内容都可以放入单独的静态库中,然后可以轻松地将其链接到测试可执行文件和DLL中。

感谢提供的信息。我们最终选择在需要进行测试的类前添加“class_decl”。由于我们永远不会测试所有这个应用程序(遗留软件),所以只需按需向公共接口展示我们想要测试的内容即可。 - Jean-François Côté
为什么你的第三个解决方案需要两个单独的测试项目?我认为只需要一个测试项目,将静态库链接到其中(包含导出类和非导出类),例如聚合所有的.obj文件,无论它们是否被导出。 - toussa
1
第二个选项的变体避免了重新编译所有源文件。在链接器选项中,将源项目的 $(IntDir) 添加到附加库目录,并将 .obj 文件添加到附加依赖项中。不要忘记从测试项目到源项目的项目依赖关系。当然,维护复杂性会增加,但只是在添加单元测试时通常的程度(即我需要考虑拉入哪个对象来满足测试)。 - Rai
在第三种情况下,我们为什么需要将目标代码(要进行测试的代码)构建为静态库而不是动态链接库? - Mohammed Noureldin

6
无论你是一个单元测试框架还是其他什么,都无法测试看不见的代码。在Windows上,只有用__declspec(dllexport)定义的符号才会被导出。任何其他符号在编译DLL时都被视为内部,对使用该DLL的代码不可见。
这很重要,因为它意味着链接器可以优化、修改或删除未导出的代码。你想要测试的代码可能根本不存在。它可能存在,但形式与你期望的不同。DLL是在一个合同下编译的,声明为dllexport的任何内容必须存在且可见,而其他任何东西只需要正常工作。它不一定要从外部世界访问。
这不是MSTest的缺点(即使它有很多其他缺点,并且对于单元测试C++代码来说是一个相当糟糕的选择)。
如果你想测试那段代码,你有两个选择:
1. 用dllexport导出它;
2. 将你的单元测试代码编写为DLL本身的一部分。

我该如何编写单元测试“作为”dll本身的一部分?如果MSTest是一个糟糕的选择,你有什么更好的建议? - Jean-François Côté

-3

别把怒火撒在信使身上。

  • Visual Studio单元测试(也称与MSTest.exe一起运行的测试)仅测试托管代码。您无法测试非托管C ++。 VS11(下一个版本)将提供新的本机单元测试框架。
  • 像您所说的InternalsVisibleTo也仅适用于托管代码。
  • 在我看来,您通常不需要测试内部类。就像私有类型或方法一样,您通过使用它们的公共/公开方法进行测试。因此,如果PublicA.Method1()是客户端练习InternalHelper.Method2()的方式;那么我依赖于PublicA.Method1()的测试来告诉我它们中的任何一个是否损坏。
  • 如果必须测试内部类,请尝试将其公开(如果方法足够复杂..请参见MethodObject重构)。或者您可以使测试类成为内部类的友元

.

class ProductionSUT
{
  // production code to be tested
  friend class TestProductSUT;
}

免责声明:我还没有尝试过这个..所以可能需要一些调整来平息编译器。


9
“你‘不需要测试内部类’。真的吗?” - Tom Quarendon
@TomQuarendon 是的。考虑到客户端唯一能够访问该代码的方式是通过其他类,因此测试应该沿着同样的线路进行。内部类是实现细节。你可以放弃 InternalClass1 并编写 InternalClass2,只要保留公开客户端类型的行为,你的测试就不会改变。 - Gishu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接