C#单元测试中使用的"internal"访问修饰符

655

我在思考是否应该开始更多地使用 internal 访问修饰符。

我知道,如果我们使用 internal 并设置程序集变量 InternalsVisibleTo,那么我们就可以从测试项目中测试不想声明为公共的函数。

这让我觉得我应该总是使用 internal,因为至少每个项目(应该?)都有自己的测试项目。

为什么不能这样做呢?何时应该使用 private


1
值得一提的是,您可以经常通过在方法本身中使用System.Diagnostics.Debug.Assert()来避免对内部方法进行单元测试的需要。 - Mike Marynowski
10个回答

1489

需要测试内部类,并且有一个程序集属性:

using System.Runtime.CompilerServices;

[assembly:InternalsVisibleTo("MyTests")]

将以下内容加入项目信息文件中,例如 Properties\AssemblyInfo.cs,适用于被测试的项目。在这种情况下,“MyTests”是测试项目。


93
这真的应该是被接受的答案。我不知道你们怎么想,但当测试与它们所测试的代码“太远”时,我就会感到紧张。我完全赞成避免测试任何标记为“private”的内容,但太多的“private”可能很好地指出一个正在努力提取的“internal”类。无论是TDD还是非TDD,我都更喜欢有更多的测试来测试大量的代码,而不是有少数的测试来测试相同数量的代码。而且,避免测试“internal”内容并不能确保获得良好的比率。 - s.m.
8
@DerickBailey和Dan Tao之间正展开一场激烈讨论,讨论的主题是internalprivate之间的语义区别以及测试内部组件的必要性。这篇文章值得一读。 - Kris McGinnes
47
将代码用 #if DEBUG#endif 包围起来,这样只有在调试版本中才能启用该选项。 - The Real Edward Cullen
26
这是正确答案。任何声称只有公共方法应该进行单元测试的回答都错了单元测试的重点,并且只是在找借口。功能测试是黑盒导向的,而单元测试是白盒导向的。它们应该测试“功能单元”,而不仅仅是公共API。 - Gunnar
15
针对.NET Core - 将其添加到应用程序中的任何.cs文件中。此处详细信息 - https://dev59.com/YlgQ5IYBdhLWcg3wym0Y#42235577。 - Alex Klaus
显示剩余10条评论

188

补充Eric的回答,您也可以在csproj文件中进行配置:

<ItemGroup>
    <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
      <_Parameter1>MyTests</_Parameter1>
    </AssemblyAttribute>
</ItemGroup>

或者如果你有一个测试项目来测试每个项目,那么你可以在Directory.Build.props文件中做类似于这样的事情:

<ItemGroup>
    <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
      <_Parameter1>$(MSBuildProjectName).Test</_Parameter1>
    </AssemblyAttribute>
</ItemGroup>

参见:https://dev59.com/GVgQ5IYBdhLWcg3wazMi#49978185
示例:https://github.com/gldraphael/evlog/blob/master/Directory.Build.props#L5-L12


16
在我看来,这应该是最佳答案。其他所有答案都非常过时,因为.NET正在远离程序集信息并将功能移入csproj定义中。 - mBrice1024
这个方法在Unity项目中不起作用,因为csproj文件会被Unity重建。在这种情况下,采用接受的答案是正确的方法。 - piersb
2
非常好的答案!这比添加AssemblyInfo文件要干净得多。 - Rickless
3
不错,但已经过时了。请使用 <InternalsVisibleTo>,如 ihebiheb 的回答 中所述。 - l33t
对于我的情况来说,使用 .NET Framework 4.8 并没有起到作用。但是根据 @EricSchaefer 的答案,使用 AssemblyInfo.cs 是有效的。 - JohnB

137
如果您想测试私有方法,请查看 Microsoft.VisualStudio.TestTools.UnitTesting 命名空间中的 PrivateObject 和 PrivateType。它们提供了易于使用的包装器,可围绕必要的反射代码进行操作。
文档: PrivateType, PrivateObject 对于 VS2017 和 2019 版本,您可以通过下载 MSTest.TestFramework nuget 来找到它们。

1
显然,使用TestFramework针对.net2.0或更新的应用程序存在一些问题:https://github.com/Microsoft/testfx/issues/366 - Johnny Wu
有人能在这个答案中添加一个代码示例吗? - dgellow

68
从.NET 5开始,您还可以在被测试项目的csproj文件中使用这种语法。
  <ItemGroup>
    <InternalsVisibleTo Include="MyProject.Tests" />
  </ItemGroup>

这个解决方案效果很好,而且代码量更少。赞一个! - undefined

20

我正在使用 .NET Core 3.1.101,对我有效的 .csproj 修改如下:

<PropertyGroup>
  <!-- Explicitly generate Assembly Info -->
  <GenerateAssemblyInfo>true</GenerateAssemblyInfo>
</PropertyGroup>

<ItemGroup>
  <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
  <_Parameter1>MyProject.Tests</_Parameter1>
  </AssemblyAttribute>
</ItemGroup>

2
添加显式生成汇编信息是最终让我也成功了。感谢您发布这个! - thevioletsaber

11

默认情况下保持私有。如果成员不应该超出该类型,即使在同一项目中也不应该超出该类型,这样可以更安全、更整洁-当使用对象时,清楚哪些方法是可以使用的。

话虽如此,我认为有时将自然私有方法设置为内部方法以进行测试目的是合理的。我更喜欢这种方式,而不是使用反射,后者难以重构。

需要考虑的一件事可能是添加“ForTest”后缀:

internal void DoThisForTest(string name)
{
    DoThis(name);
}

private void DoThis(string name)
{
    // Real implementation
}

那么当你在同一项目中使用该类时,显然(现在和将来)你不应该真正使用这个方法-它只是为了测试目的而存在。 这有点hacky,并不是我自己做的事情,但至少值得考虑。


2
如果该方法是 internal 的,那么这是否排除了它在测试程序集中的使用? - Ralph Shillington
7
有时我会使用“ForTest”方法,但我总是觉得它很丑陋(添加的代码在生产业务逻辑方面没有实际价值)。通常情况下,我发现我必须使用这种方法,是因为设计有些不幸(即需要在测试之间重置单例实例)。 - ChrisWue
2
被诱惑要点踩这个 - 这个 hack 和只是将类改为 internal 而不是 private 有什么区别?好吧,至少使用编译条件。然后就变得非常混乱了。 - CAD bloke
8
@CADbloke:你是不是想将这个方法改为internal而非private?区别在于,私有方法的需求更显然。在生产代码库中调用带有ForTest标记的方法是明显错误的,而如果你只是将该方法设为internal,则看起来使用它就像是可行的。 - Jon Skeet
2
@CADbloke:在发布版本中,你可以像使用部分类一样轻松地排除单个方法,而且可以在同一个文件中完成。如果你这样做了,那么就意味着你没有针对发布版本运行测试,这对我来说听起来不是一个好主意。 - Jon Skeet
显示剩余4条评论

10

你可以使用private,并且可以使用反射调用私有方法。如果你使用Visual Studio Team Suite,它具有一些很好的功能,可以为您生成代理来调用私有方法。这里有一篇代码项目文章,演示了如何自己完成单元测试私有和受保护的方法:

http://www.codeproject.com/KB/cs/testnonpublicmembers.aspx

在选择访问修饰符时,我的一般经验法则是从private开始,按需升级。这样,你将仅暴露出真正需要的类内部细节,有助于隐藏实现细节,使其得以保密。


4

对于.NET Core,您可以将属性添加到命名空间中,例如: [assembly: InternalsVisibleTo("MyUnitTestsAssemblyName")]. 类似这样的例子

using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Applications.ExampleApp.Tests")]
namespace Applications.ExampleApp
 internal sealed class ASampleClass : IDisposable
    {
        private const string ApiVersionPath = @"api/v1/";
        ......
        ......
        ......
        }
    }

4
在.NET Core 2.2中,将以下代码添加到您的Program.cs文件中:
using ...
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("MyAssembly.Unit.Tests")]

namespace
{
...

0
InternalsVisibleTo.cs文件添加到项目的根文件夹中,即.csproj文件所在的位置。 InternalsVisibleTo.cs文件的内容应如下:
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("AssemblyName.WhichNeedAccess.Example.UnitTests")]

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