【问题标题】:Unit testing C# protected methods单元测试 C# 保护方法
【发布时间】:2012-11-05 03:45:45
【问题描述】:

我来自 Java EE 世界,但现在我正在从事一个 .Net 项目。在 Java 中,当我想测试一个受保护的方法时,这很容易,只需让测试类具有相同的包名就足够了。

C# 有类似的东西吗?对受保护的方法进行单元测试有什么好的做法吗?我只发现框架和人们说我应该只测试公共方法。

应该可以在没有任何框架的情况下做到这一点……

【问题讨论】:

    标签: c# .net unit-testing protected


    【解决方案1】:

    你可以在你的测试类上继承你正在测试的类。

    [TestClass]
    public class Test1 : SomeClass
    {
        [TestMethod]
        public void MyTest
        {
            Assert.AreEqual(1, ProtectedMethod());
        }
    
    }
    

    【讨论】:

    • 感谢您的快速答复。我测试过,它正在工作。我不知道这是否是一个不错的方法,但至少我可以从单元测试开始。
    • @unarity 如果我的测试类没有 args 构造函数(例如它只有 4 个 args 构造函数)怎么办?当我从中派生时,我的测试类不会编译 TestClass does not contain a constructor that takes 0 arguments 错误
    • 我在我的单元测试项目中添加了一个“可测试”类扩展类,并用“可测试方法”包装所有受保护的方法,并在必要时重新实现匹配的构造函数。结果相同,但我喜欢将测试与实现分开。
    • 我认为这实际上可能是Asert.AreEqual(1, Test1.ProtectedMethod());,因为需要在内部调用派生类型。尽管继承自,基方法仍然受到保护。
    • 这是错误的答案 测试类不应该与测试类相同......在很多情况下测试类需要特殊初始化(fx它仅在某些情况下有效容器),这个解决方案会导致问题
    【解决方案2】:

    另一种选择是对这些方法使用internal,然后使用InternalsVisibleTo 允许您的测试程序集访问这些方法。这不会阻止同一程序集中的其他类使用方法,但会阻止不是您的测试程序集的其他程序集访问它们。

    这并没有为您提供尽可能多的封装和保护,但它非常简单并且很有用。

    在包含内部方法的程序集中添加到AssemblyInfo.cs

    [assembly: InternalsVisibleTo("TestsAssembly")]
    

    【讨论】:

    • protected 方法对子类(包括程序集外的子类)可见(并可被覆盖),而 internal 则不可见。它们在功能上并不等效,因此此选项仅适用于您不需要真正的 protected 方法的情况。
    • @E-Riz 如果您希望您的内部方法可以被程序集外部的子类覆盖,您可以使用受保护的内部docs.microsoft.com/en-us/dotnet/csharp/language-reference/…(受保护的内部和内部都不等同于受保护,但它们给了您测试时有不同的选择)。使用子类的最佳答案可能是大多数情况下的最佳解决方案。
    【解决方案3】:

    您可以在继承您要测试的类的新类中公开受保护的方法。

    public class ExposedClassToTest : ClassToTest
    {
        public bool ExposedProtectedMethod(int parameter)
        {
            return base.ProtectedMethod(parameter);
        }
    }
    

    【讨论】:

      【解决方案4】:

      您可以使用 PrivateObject 类来访问所有私有/受保护的方法/字段。

      PrivateObject 是 Microsoft 单元测试框架中的一个类,它是一个包装器,可以调用通常无法访问的成员进行单元测试。

      【讨论】:

      【解决方案5】:

      您可以使用反射来调用私有和受保护的方法。

      查看这里了解更多:

      http://msdn.microsoft.com/en-us/library/66btctbe.aspx

      【讨论】:

      • 也许我错了,但我觉得如果测试需要反射是因为我做错了什么。但是如果在 C# 中这很正常,那么我会习惯的。
      • 我也不想使用它。我真的只是想指出这种技术,以防你不知道。至少在我看来,您可能有一个私有方法要单独测试,以便能够轻松地使用大量输入和预期结果对其进行测试。
      • 我认为使用反射是一种测试类的非公共特性的合理方法。我一直在 Java 中这样做,因为否则您必须使用“不正确”修饰符才能测试正确构造的类。如果您需要 Private 或 Protected,您仍然应该能够在不向真实类添加垃圾代码以允许访问的情况下进行测试(从安全角度来看,这可能会导致您的代码失败)。
      • 反射,无论感觉如何,在语义上都是正确的,因为您可以保留方法的可见性,而不必将方法封装在方法中。
      【解决方案6】:

      虽然公认的答案是最好的,但它并没有解决我的问题。从受保护的类派生出来的许多其他东西污染了我的测试类。最后,我选择将待测试逻辑提取到公共类中并对其进行测试。当然,这对每个人都不起作用,可能需要进行相当多的重构,但是如果您一直滚动到这个答案,它可能会对您有所帮助。 :) 这是一个例子

      旧情况:

      protected class ProtectedClass{
         protected void ProtectedMethod(){
            //logic you wanted to test but can't :(
         }
      }
      

      新情况:

      protected class ProtectedClass{
         private INewPublicClass _newPublicClass;
      
         public ProtectedClass(INewPublicClass newPublicClass) {
            _newPublicClass = newPublicClass;
         }
      
         protected void ProtectedMethod(){
            //the logic you wanted to test has been moved to another class
            _newPublicClass.DoStuff();
         }
      }
      
      public class NewPublicClass : INewPublicClass
      {
         public void DoStuff() {
            //this logic can be tested!
         }
      }
      
      public class NewPublicClassTest
      {
          NewPublicClass _target;
          public void DoStuff_WithoutInput_ShouldSucceed() {
              //Arrange test and call the method with the logic you want to test
              _target.DoStuff();
          }
      }
      

      【讨论】:

        【解决方案7】:

        您可以使用从基类调用受保护方法的公共方法创建存根。这也是您在生产中使用这种受保护方法的方式。

        public class FooStub : Bar 
        {
            public string MyMethodFoo()
            {
                return MyMethodBar();
            }
        }
        
        public abstract class Bar 
        {
            protected string MyMethodBar()
            {
                return "Hello World!"
            }
        }
        

        【讨论】:

          【解决方案8】:

          这是我在测试项目中使用的扩展方法(what is the extension method?)。 唯一的缺点是您必须将名称写成string,因为nameof() 遵循保护规则,并且不允许您引用受保护或私有成员。

                  public static MethodInfo GetNonPublicMethod(this Type type, string method)
                  {
                      MemberInfo[] temp = type.GetMember(method, MemberTypes.Method, BindingFlags.NonPublic | BindingFlags.Instance);
                      if (temp.Length == 1)
                      {
                          if (temp[0] is MethodInfo ret)
                          {
                              return ret;
                          }
                          else
                          {
                              throw new ArgumentException("Not a method.");
                          }
                      }
                      else
                      {
                          if (temp.Length == 0)
                          {
                              throw new ArgumentException("Method was not found.");
                          }
                          else
                          {
                              throw new ArgumentException("Multiple methods found.");
                          }
                      }
                  }
          

          更多关于此方法的信息:https://docs.microsoft.com/en-us/dotnet/api/system.type.getmember?view=net-5.0

          PS:使用methodInfo.Invoke(instance, params)调用它。

          【讨论】:

          • 已经有一个答案建议使用反射,作为这个答案的 cmets,这不是保持代码清洁的方法。
          • 您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center
          • 1.这是一个在测试环境中获取受保护方法的工作示例,虽然有使用它的建议,但没有可复制粘贴的示例。 2. 这段代码只能在测试环境中使用——一般来说,在不向原始代码库引入任何测试代码的情况下,为了测试事物而打破约定是更容易接受的。我承认代码可以改进,但这段代码完全满足了我们的要求。
          猜你喜欢
          • 1970-01-01
          • 2013-10-10
          • 1970-01-01
          • 2010-11-10
          • 1970-01-01
          • 2017-06-24
          • 1970-01-01
          • 2012-02-22
          • 1970-01-01
          相关资源
          最近更新 更多