【问题标题】:C# "internal" access modifier when doing unit testing进行单元测试时的 C#“内部”访问修饰符
【发布时间】:2010-09-26 08:53:27
【问题描述】:

我是单元测试的新手,我想知道是否应该开始使用更多的internal 访问修饰符。我知道如果我们使用internal 并设置程序集变量InternalsVisibleTo,我们可以测试我们不想从测试项目中公开的函数。这让我觉得我应该总是使用internal,因为至少每个项目(应该?)都有自己的测试项目。你们能告诉我为什么我不应该这样做吗?我什么时候应该使用private

【问题讨论】:

  • 值得一提 - 您通常可以通过在方法本身中使用 System.Diagnostics.Debug.Assert() 来避免对内部方法进行单元测试。

标签: c# .net unit-testing tdd


【解决方案1】:

内部类需要测试,有组装属性:

using System.Runtime.CompilerServices;

[assembly:InternalsVisibleTo("MyTests")]

将此添加到项目信息文件中,例如Properties\AssemblyInfo.cs,用于测试中的项目。在这种情况下,“MyTests”是测试项目。

【讨论】:

  • 这确实应该是公认的答案。我不了解你们,但是当测试与他们正在测试的代码“太远”时,我往往会感到紧张。我完全赞成避免测试任何标记为private 的东西,但是太多private 的东西很可能指向一个正在努力提取的internal 类。 TDD 或没有 TDD,我更喜欢有更多的测试来测试大量的代码,而不是有很少的测试来执行相同数量的代码。并且避免测试internal 的东西并不能完全帮助实现良好的比率。
  • @DerickBailey 和 Dan Tao 之间就 internalprivate 之间的语义差异以及测试的需要进行了一场精彩的 discussion i>内部组件。值得一读。
  • 包裹在 #if DEBUG, #endif 块中将仅在调试版本中启用此选项。
  • 这是正确答案。任何说只有公共方法才应该进行单元测试的答案都错过了单元测试的要点并找借口。功能测试是面向黑盒的。单元测试是面向白盒的。他们应该测试功能的“单元”,而不仅仅是公共 API。
  • 对于 .NET Core - 将其添加到应用程序中的任何 .cs 文件中。在此处查看详细信息 - stackoverflow.com/a/42235577/968003
【解决方案2】:

对于 .NET 核心,您可以将属性添加到命名空间 [程序集: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/";
        ......
        ......
        ......
        }
    }

【讨论】:

    【解决方案3】:

    InternalsVisibleTo.cs 文件添加到存在.csproj 文件的项目根文件夹中。

    InternalsVisibleTo.cs 的内容应该如下

    using System.Runtime.CompilerServices;
    
    [assembly: InternalsVisibleTo("AssemblyName.WhichNeedAccess.Example.UnitTests")]
    

    【讨论】:

      【解决方案4】:

      我正在使用.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>
      

      【讨论】:

      • 添加显式生成程序集信息是最终使它对我有用的原因。感谢您发布此信息!
      【解决方案5】:

      在 .NET Core 2.2 中,将此行添加到您的 Program.cs:

      using ...
      using System.Runtime.CompilerServices;
      
      [assembly: InternalsVisibleTo("MyAssembly.Unit.Tests")]
      
      namespace
      {
      ...
      

      【讨论】:

        【解决方案6】:

        除了 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://stackoverflow.com/a/49978185/1678053
        示例:https://github.com/gldraphael/evlog/blob/master/Directory.Build.props#L5-L12

        【讨论】:

        • 这应该是 imo 的最佳答案。所有其他答案都非常过时,因为 .net 正在远离程序集信息并将功能转移到 csproj 定义中。
        • 这种方法在 Unity 项目中不起作用,因为 csproj 文件是由 Unity 重建的。在这种情况下,接受的答案就是要走的路。
        • 非常好的答案!这比添加这个 AssemblyInfo 文件要干净得多。
        【解决方案7】:

        如果您想测试私有方法,请查看 Microsoft.VisualStudio.TestTools.UnitTesting 命名空间中的 PrivateObjectPrivateType。它们围绕必要的反射代码提供易于使用的包装器。

        文档: PrivateType, PrivateObject

        对于 VS2017 和 2019,您可以通过下载 MSTest.TestFramework nuget 找到这些

        【讨论】:

        • 显然,将 TestFramework 用于面向 .net2.0 或更高版本的应用程序存在一些问题:github.com/Microsoft/testfx/issues/366
        • 有人可以在这个答案中添加一个代码示例吗?
        【解决方案8】:

        默认情况下继续使用私有。如果一个成员不应该暴露在那个类型之外,那么它不应该暴露在那个类型之外,即使在同一个项目中也是如此。这使事情更安全、更整洁 - 当您使用该对象时,您可以更清楚地了解您应该能够使用哪些方法。

        话虽如此,我认为有时出于测试目的将自然私有方法设为内部是合理的。我更喜欢使用反射,因为反射对重构不友好。

        需要考虑的一点可能是“ForTest”后缀:

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

        然后,当您在同一个项目中使用该类时,很明显(现在和将来)您不应该真正使用此方法 - 它仅用于测试目的。这有点老套,不是我自己做的,但至少值得考虑。

        【讨论】:

        • 如果方法是内部的,这不排除它在测试程序集中的使用吗?
        • 我偶尔会使用ForTest 方法,但我总是觉得它很难看(添加在生产业务逻辑方面没有实际价值的代码)。通常我发现我不得不使用这种方法,因为设计有点不幸(即必须在测试之间重置单例实例)
        • 很想对此投反对票 - 这种 hack 与简单地将类设为内部而不是私有之间有什么区别?好吧,至少有编译条件。然后它变得非常混乱。
        • @CADbloke:您的意思是使 方法 内部化而不是私有化吗?不同之处在于很明显您真的希望它是私有的。您的生产代码库中使用ForTest 调用方法的任何代码显然 是错误的,而如果您只是将方法设为内部,它看起来可以使用。
        • @CADbloke:您可以在同一文件中排除发布版本中的各个方法,就像使用部分类 IMO 一样容易。如果您这样做 这样做,则表明您没有针对您的发布版本运行测试,这对我来说听起来是个坏主意。
        【解决方案9】:

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

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

        就您应该使用哪个访问修饰符而言,我的一般经验法则是从私有开始并根据需要升级。这样,您将尽可能少地公开真正需要的类的内部细节,并有助于隐藏实现细节,因为它们应该是。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-12-24
          • 2022-11-18
          • 1970-01-01
          • 1970-01-01
          • 2017-11-11
          相关资源
          最近更新 更多