【问题标题】:How do you unit test private methods?你如何对私有方法进行单元测试?
【发布时间】:2010-09-20 00:35:37
【问题描述】:

我正在构建一个包含一些公共和私有方法的类库。我希望能够对私有方法进行单元测试(主要是在开发时,但它也可能对未来的重构有用)。

这样做的正确方法是什么?

【问题讨论】:

标签: .net unit-testing tdd private


【解决方案1】:

测试私有方法可能没有用。但是,我有时也喜欢从测试方法中调用私有方法。大部分时间是为了防止代码重复生成测试数据...

微软为此提供了两种机制:

访问器

  • 转到类定义的源代码
  • 右键单击类的名称
  • 选择“创建私有访问器”
  • 选择应在其中创建访问器的项目 => 你最终会得到一个名为 foo_accessor 的新类。 此类将在编译期间动态生成,并提供所有成员 public 可用。

但是,当涉及到更改原始类的接口时,该机制有时会有点棘手。所以,大多数时候我都避免使用它。

PrivateObject 类 另一种方法是使用 Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject

// Wrap an already existing instance
PrivateObject accessor = new PrivateObject( objectInstanceToBeWrapped );

// Retrieve a private field
MyReturnType accessiblePrivateField = (MyReturnType) accessor.GetField( "privateFieldName" );

// Call a private method
accessor.Invoke( "PrivateMethodName", new Object[] {/* ... */} );

【讨论】:

  • 如何调用私有静态方法?
  • 私人访问者是deprecated in Visual Studio 2012
  • 测试私有方法的访问器方法从 VS 2011 开始被弃用。 blogs.msdn.com/b/visualstudioalm/archive/2012/03/08/…
  • 阅读 Microsoft 网站 here 上的文档我没有看到任何提及已弃用 PrivateObject 类的内容。我正在使用 MSVS 2013,它按预期工作。
  • @RyanGates 您引用的网站上的第一个解决方案指出,访问私有成员的解决方案是 “使用 PrivateObject 类来协助访问代码中的内部和私有 API。这是在 Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll 程序集中找到。"
【解决方案2】:
CC -Dprivate=public

“CC”是我使用的系统上的命令行编译器。 -Dfoo=bar 相当于 #define foo bar。因此,此编译选项有效地将所有私有内容更改为公共内容。

【讨论】:

  • 这是什么?这适用于 Visual Studio 吗?
  • "CC" 是我使用的系统上的命令行编译器。 “-Dfoo=bar”相当于“#define foo bar”。因此,此编译选项有效地将所有私有内容更改为公共内容。哈哈!
  • 在 Visual Studio 中,在构建环境中设置定义。
【解决方案3】:

适用于 JAVA 语言

在这里,您可以使用模拟行为覆盖测试类的特定方法。

以下代码:

public class ClassToTest 
{
    public void methodToTest()
    {
        Integer integerInstance = new Integer(0);
        boolean returnValue= methodToMock(integerInstance);
        if(returnValue)
        {
            System.out.println("methodToMock returned true");
        }
        else
        {
            System.out.println("methodToMock returned true");
        }
        System.out.println();
    }
    private boolean methodToMock(int value)
    {
        return true;
    }
}

测试类是:

public class ClassToTestTest{

    @Test
    public void testMethodToTest(){

        new Mockup<ClassToTest>(){
            @Mock
            private boolean methodToMock(int value){
                return true;
            }
        };

        ....    

    }
}

【讨论】:

    【解决方案4】:

    如果您使用的是 .net,则应使用 InternalsVisibleToAttribute

    【讨论】:

    • 呸。这会编译到您发布的程序集中。
    • @Jay - 不能在InternalsVisibleTo 属性周围使用#if DEBUG 使其不适用于发布代码吗?
    • @Mike,你可以,但是你只能在调试代码上运行单元测试,而不是发布代码。由于发布代码经过优化,您可能会看到不同的行为和不同的时间。在多线程代码中,这意味着您的单元测试将无法正确检测竞争条件。更好的是通过下面@AmazedSaint 的建议使用反射或使用内置的 PrivateObject/PrivateType。假设您的测试工具以完全信任的方式运行(MSTest 在本地运行),这允许您在发布版本中查看私有信息
    • 我错过了什么?当它实际上并没有回答测试私有方法的具体问题时,为什么这是公认的答案? InternalsVisibleTo 仅公开标记为内部的方法,而不公开 OP 要求的标记为私有的方法(以及我登陆这里的原因)。我想我需要继续使用七号回答的 PrivateObject?
    • @Jay 我知道这有点晚了,但一种选择是像 Mike 建议的那样在InternalsVisibleTo 周围使用#if RELEASE_TEST 之类的东西,并复制你的发布构建配置来定义RELEASE_TEST。你可以通过优化来测试你的发布代码,但是当你真正为发布构建时,你的测试将被省略。
    【解决方案5】:

    我使用PrivateObject 类。但如前所述,最好避免测试私有方法。

    Class target = new Class();
    PrivateObject obj = new PrivateObject(target);
    var retVal = obj.Invoke("PrivateMethod");
    Assert.AreEqual(retVal);
    

    【讨论】:

      【解决方案6】:

      在我看来,你应该只对你的类的公共 API 进行单元测试。

      将方法公开,以便对其进行单元测试,会破坏暴露实现细节的封装。

      一个好的公共 API 解决了客户端代码的直接目标并完全解决了这个目标。

      【讨论】:

      • 这应该是 IMO 的正确答案。如果你有很多私有方法,那么这可能是因为你有一个隐藏的类,你应该将它分解成它自己的公共接口。
      【解决方案7】:

      适用于任何想要运行私有方法而没有所有麻烦和混乱的人。这适用于任何单元测试框架,只使用良好的旧反射。

      public class ReflectionTools
      {
          // If the class is non-static
          public static Object InvokePrivate(Object objectUnderTest, string method, params object[] args)
          {
              Type t = objectUnderTest.GetType();
              return t.InvokeMember(method,
                  BindingFlags.InvokeMethod |
                  BindingFlags.NonPublic |
                  BindingFlags.Instance |
                  BindingFlags.Static,
                  null,
                  objectUnderTest,
                  args);
          }
          // if the class is static
          public static Object InvokePrivate(Type typeOfObjectUnderTest, string method, params object[] args)
          {
              MemberInfo[] members = typeOfObjectUnderTest.GetMembers(BindingFlags.NonPublic | BindingFlags.Static);
              foreach(var member in members)
              {
                  if (member.Name == method)
                  {
                      return typeOfObjectUnderTest.InvokeMember(method, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod, null, typeOfObjectUnderTest, args);
                  }
              }
              return null;
          }
      }
      

      然后在你的实际测试中,你可以这样做:

      Assert.AreEqual( 
        ReflectionTools.InvokePrivate(
          typeof(StaticClassOfMethod), 
          "PrivateMethod"), 
        "Expected Result");
      
      Assert.AreEqual( 
        ReflectionTools.InvokePrivate(
          new ClassOfMethod(), 
          "PrivateMethod"), 
        "Expected Result");
      

      【讨论】:

        【解决方案8】:

        您还可以在调试模式下构建时将其声明为公共或内部(使用 InternalsVisibleToAttribute):

            /// <summary>
            /// This Method is private.
            /// </summary>
        #if DEBUG
            public
        #else
            private
        #endif
            static string MyPrivateMethod()
            {
                return "false";
            }
        

        它会使代码膨胀,但在发布版本中它将是 private

        【讨论】:

          【解决方案9】:

          我想在这里创建一个清晰的代码示例,您可以在任何要测试私有方法的类上使用它。

          在您的测试用例类中只包含这些方法,然后按照指示使用它们。

            /**
             *
             * @var Class_name_of_class_you_want_to_test_private_methods_in
             * note: the actual class and the private variable to store the 
             * class instance in, should at least be different case so that
             * they do not get confused in the code.  Here the class name is
             * is upper case while the private instance variable is all lower
             * case
             */
            private $class_name_of_class_you_want_to_test_private_methods_in;
          
            /**
             * This uses reflection to be able to get private methods to test
             * @param $methodName
             * @return ReflectionMethod
             */
            protected static function getMethod($methodName) {
              $class = new ReflectionClass('Class_name_of_class_you_want_to_test_private_methods_in');
              $method = $class->getMethod($methodName);
              $method->setAccessible(true);
              return $method;
            }
          
            /**
             * Uses reflection class to call private methods and get return values.
             * @param $methodName
             * @param array $params
             * @return mixed
             *
             * usage:     $this->_callMethod('_someFunctionName', array(param1,param2,param3));
             *  {params are in
             *   order in which they appear in the function declaration}
             */
            protected function _callMethod($methodName, $params=array()) {
              $method = self::getMethod($methodName);
              return $method->invokeArgs($this->class_name_of_class_you_want_to_test_private_methods_in, $params);
            }
          

          $this->_callMethod('_someFunctionName', array(param1,param2,param3));

          只需按照参数在原始私有函数中出现的顺序发出参数即可

          【讨论】:

            【解决方案10】:

            1) 如果您有遗留代码,那么测试私有方法的唯一方法是通过反射。

            2) 如果是新代码,那么您有以下选择:

            • 使用反射(复杂)
            • 在同一个类中编写单元测试(使生产代码难看 里面还有测试代码)
            • 重构并在某种实用程序类中公开方法
            • 使用@VisibleForTesting 注释并删除私有

            我更喜欢注解的方式,最简单,最不复杂。唯一的问题是我们提高了知名度,我认为这不是一个大问题。 我们应该总是对接口进行编码,所以如果我们有一个接口 MyService 和一个实现 MyServiceImpl 那么我们可以有相应的测试类,即 MyServiceTest(测试接口方法)和 MyServiceImplTest(测试私有方法)。无论如何,所有客户端都应该使用该接口,因此即使私有方法的可见性已经增加,它也并不重要。

            【讨论】:

              【解决方案11】:

              一种方法是使用您的方法protected 并编写一个测试夹具,该夹具继承您要测试的类。这样一来,您也不会转动您的方法public,而是启用测试。

              【讨论】:

              • 我不同意这一点,因为您还将允许您的消费者从基类继承并使用受保护的函数。这是您首先希望通过将这些功能设为私有或内部来防止的事情。
              【解决方案12】:

              您首先不应该测试代码的私有方法。您应该测试“公共接口”或 API,即类的公共事物。 API 是您向外部调用者公开的所有公共方法。

              原因是,一旦您开始测试您的类的私有方法和内部结构,您就会将您的类的实现(私有的东西)耦合到您的测试中。这意味着当您决定更改实现细节时,您还必须更改测试。

              因此,您应该避免使用 InternalsVisibleToAtrribute。

              这是 Ian Cooper 的精彩演讲,涵盖了这个主题:Ian Cooper: TDD, where did it all go wrong

              【讨论】:

                【解决方案13】:

                我认为应该问一个更基本的问题是,您为什么要首先尝试测试私有方法。这是您试图通过该类的公共接口测试私有方法的代码味道,而该方法是私有的,因为它是一个实现细节。人们应该只关心公共接口的行为,而不是它是如何在幕后实现的。

                如果我想测试私有方法的行为,通过使用常见的重构,我可以将其代码提取到另一个类中(可能具有包级别的可见性,因此确保它不是公共 API 的一部分)。然后我可以单独测试它的行为。

                重构的产物意味着私有方法现在是一个单独的类,它已成为原始类的协作者。它的行为将通过它自己的单元测试得到很好的理解。

                然后,当我尝试测试原始类时,我可以模拟它的行为,这样我就可以专注于测试该类的公共接口的行为,而不必测试公共接口的组合爆炸和所有的行为它的私有方法。

                我认为这类似于驾驶汽车。当我开车时,我不会开着引擎盖开车,所以我可以看到引擎在工作。我依靠汽车提供的界面,即转速表和车速表来了解发动机是否在工作。我相信当我踩下油门踏板时汽车实际上会移动。如果我想测试引擎,我可以单独对其进行检查。 :D

                当然,如果您有遗留应用程序,直接测试私有方法可能是最后的手段,但我更希望重构遗留代码以实现更好的测试。 Michael Feathers 就这个主题写了一本很棒的书。 http://www.amazon.co.uk/Working-Effectively-Legacy-Robert-Martin/dp/0131177052

                【讨论】:

                • 这个答案的逻辑完全错误。需要私有方法的经典案例是当您需要一些代码被公共方法重用时。你把这段代码放到一些 PrivMethod 中。此方法不应公开,但需要测试以确保依赖 PrivMethod 的公共方法可以真正依赖它。
                • 在初始开发过程中是有意义的,但是您想在标准回归套装中测试私有方法吗?如果是这样,如果实现发生变化,它可能会破坏测试套件。 OTOH,如果您的回归测试仅关注外部可见的公共方法,那么如果私有方法稍后中断,回归套件仍应检测到错误。然后,如有必要,您可以根据需要清除旧的私人测试。
                • 不同意,您应该只测试公共接口,否则为什么需要私有方法。在这种情况下将它们全部公开并测试所有它们。如果您正在测试私有方法,那么您正在破坏封装。如果你想测试一个私有方法并且它在多个公共方法中使用,那么它应该被移动到它自己的类并单独测试。然后所有公共方法都应该委托给那个新类。这样你仍然可以测试原始类的接口,您可以验证行为没有改变,并且您对委托的私有方法有单独的测试。
                • @Big Kahuna - 如果您认为不需要对私有方法进行单元测试,那么您从未使用过足够大/复杂的项目。很多时候,像客户端特定验证这样的公共函数最终只需要 20 行代码,只需调用非常简单的私有方法以使代码更具可读性,但您仍然必须测试每个单独的私有方法。测试 20 次 public 函数,在单元测试失败时,很难首次亮相。
                • 我在一家富时 100 指数公司工作。我想我在我的时间里见过几个复杂的项目,谢谢。如果您需要测试到该级别,那么每个私有方法作为单独的协作者,都应该单独测试,因为这意味着它们具有需要测试的个人行为。对主要中介对象的测试就变成了交互测试。它只是测试是否调用了正确的策略。您的场景听起来像是有问题的课程没有遵循 SRP。它没有一个改变的理由,而是 20 => SRP 违规。读一读 GOOS 书或鲍勃叔叔。 YMWV
                【解决方案14】:

                这是一个例子,首先是方法签名:

                private string[] SplitInternal()
                {
                    return Regex.Matches(Format, @"([^/\[\]]|\[[^]]*\])+")
                                        .Cast<Match>()
                                        .Select(m => m.Value)
                                        .Where(s => !string.IsNullOrEmpty(s))
                                        .ToArray();
                }
                

                这是测试:

                /// <summary>
                ///A test for SplitInternal
                ///</summary>
                [TestMethod()]
                [DeploymentItem("Git XmlLib vs2008.dll")]
                public void SplitInternalTest()
                {
                    string path = "pair[path/to/@Key={0}]/Items/Item[Name={1}]/Date";
                    object[] values = new object[] { 2, "Martin" };
                    XPathString xp = new XPathString(path, values);
                
                    PrivateObject param0 = new PrivateObject(xp);
                    XPathString_Accessor target = new XPathString_Accessor(param0);
                    string[] expected = new string[] {
                        "pair[path/to/@Key={0}]",
                        "Items",
                        "Item[Name={1}]",
                        "Date"
                    };
                    string[] actual;
                    actual = target.SplitInternal();
                    CollectionAssert.AreEqual(expected, actual);
                }
                

                【讨论】:

                  【解决方案15】:

                  这里是很好的article 关于私有方法的单元测试。但我不确定哪个更好,让您的应用程序专门为测试而设计(就像为测试创建测试一样)或使用反射进行测试。 很确定我们大多数人都会选择第二种方式。

                  【讨论】:

                    【解决方案16】:

                    你可以通过两种方式对私有方法进行单元测试

                    1. 你可以创建PrivateObject类的实例,语法如下

                      PrivateObject obj= new PrivateObject(PrivateClass);
                      //now with this obj you can call the private method of PrivateCalss.
                      obj.PrivateMethod("Parameters");
                      
                    2. 你可以使用反射。

                      PrivateClass obj = new PrivateClass(); // Class containing private obj
                      Type t = typeof(PrivateClass); 
                      var x = t.InvokeMember("PrivateFunc", 
                          BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public |  
                              BindingFlags.Instance, null, obj, new object[] { 5 });
                      

                    【讨论】:

                    【解决方案17】:

                    有两种类型的私有方法。静态私有方法和非静态私有方法(实例方法)。以下 2 篇文章通过示例解释了如何对私有方法进行单元测试。

                    1. Unit Testing Static Private Methods
                    2. Unit Testing Non Static Private Methods

                    【讨论】:

                    • 提供一些例子,不要只给链接
                    • 看起来很丑。没有智能。来自 MS 的错误解决方案。我很震惊!
                    • 测试私有静态方法的最简单方法
                    【解决方案18】:

                    我很惊讶还没有人这么说,但我采用的一个解决方案是在类中创建一个静态方法来测试自己。这使您可以访问所有要测试的公共和私有内容。

                    此外,在脚本语言(具有 OO 能力,如 Python、Ruby 和 PHP)中,您可以让文件在运行时自行测试。确保您的更改不会破坏任何东西的好方法。这显然为测试所有类提供了一个可扩展的解决方案:只需运行它们。 (你也可以在其他语言中使用 void main 来执行此操作,它也总是运行它的测试)。

                    【讨论】:

                    • 这个虽然实用,但不是很优雅。这可能会造成代码库的混乱,也不允许您将测试与真实代码分开。外部测试的能力开启了脚本自动化测试的能力,而不是手动编写静态方法。
                    • 这并不妨碍您进行外部测试...只要您喜欢调用静态方法即可。代码库也不混乱……您可以相应地命名方法。我使用“runTests”,但任何类似的作品都可以。
                    • 你说得对,它并不排除外部测试,但它确实会生成更多代码,即使代码库变得混乱。每个类可能有很多私有方法来测试哪个在它的一个或多个构造函数中初始化它的变量。要进行测试,您将必须编写至少与要测试的方法一样多的静态方法,并且测试方法可能必须很大才能初始化正确的值。这将使代码的维护更加困难。正如其他人所说,测试类的行为是一种更好的方法,其余的应该足够小以进行调试。
                    • 我使用与其他人相同的行数进行测试(实际上会更少,您稍后会读到)。您不必测试所有私有方法。只是那些需要测试的 :) 你也不需要用单独的方法测试每一个。我一个电话就搞定了。这实际上使代码的维护变得更加困难,因为我所有的类都有相同的单元测试方法,它逐行运行所有私有和受保护的单元测试。然后,整个测试工具在我的所有类上调用相同的方法,并且维护都驻留在我的类中 - 测试和所有。
                    【解决方案19】:

                    还请注意,InternalsVisibleToAtrribute 要求您的程序集为strong named,如果您正在使用以前没有该要求的解决方案,这会产生它自己的一组问题。我使用访问器来测试私有方法。有关此示例,请参阅 this question

                    【讨论】:

                    • 不,InternalsVisibleToAttribute 确实 要求您的程序集被强命名。我目前在一个并非如此的项目中使用它。
                    • 澄清这一点:“当前程序集和朋友程序集都必须是未签名的,或者两者都必须使用强名称签名。” - 来自 MSDN
                    【解决方案20】:

                    我不同意“您应该只对测试外部接口感兴趣”的理念。这有点像说汽车修理店应该只测试车轮是否转动。是的,最终我对外部行为感兴趣,但我喜欢我自己的、私人的、内部测试更具体、更切题。是的,如果我重构,我可能不得不更改一些测试,但除非它是一个大规模的重构,否则我只需要更改一些并且其他(未更改的)内部测试仍然有效的事实是一个很好的指标重构成功。

                    您可以尝试仅使用公共接口覆盖所有内部案例,理论上可以完全使用公共接口测试每个内部方法(或至少每个重要的方法),但您最终可能不得不站在您的要实现这一点,通过公共接口运行的测试用例与其设计用于测试的解决方案的内部部分之间的联系可能很难或不可能辨别。已经指出,保证内部机器正常工作的个别测试非常值得重构带来的微小测试更改 - 至少这是我的经验。如果每次重构都必须对测试进行重大更改,那么这可能没有意义,但在这种情况下,也许您应该完全重新考虑您的设计。一个好的设计应该足够灵活,可以在不进行大规模重新设计的情况下进行大多数更改。

                    【讨论】:

                    • 恐怕我还是不同意你的观点。将每个组件视为黑匣子,可以毫无问题地换入/换出模块。如果您有一个必须执行XFooService,那么您应该关心的是它确实在请求时执行X如何它应该不重要。如果类中存在无法通过接口识别的问题(不太可能),它仍然是有效的FooService。如果 is 通过界面可见是一个问题,那么对公共成员的测试应该可以检测到它。重点应该是,只要轮子转动得当,就可以当轮子使用。
                    • 一种通用的方法是,如果您的内部逻辑足够复杂以至于您觉得它需要进行单元测试,那么可能需要将其提取到某种具有可以进行单元测试的公共接口的帮助类中。然后你的“父”类可以简单地使用这个助手,每个人都可以适当地进行单元测试。
                    • @Basic:这个答案的逻辑完全错误。需要私有方法的经典案例是当您需要一些代码被公共方法重用时。你把这段代码放到一些 PrivMethod 中。此方法不应该公开,但需要测试以确保依赖 PrivMethod 的公共方法可以真正依赖它。
                    • @Dima 当然,如果PrivMethod 有问题,那么在调用PrivMethodPubMethod 上进行测试应该会暴露它吗?当您将SimpleSmtpService 更改为GmailService 时会发生什么?突然之间,您的私人测试指向的代码不再存在或可能以不同的方式工作并且会失败,即使应用程序可能按设计完美运行​​。如果有适用于两个电子邮件发件人的复杂处理,也许它应该在EmailProcessor 中,两者都可以使用并单独测试?
                    • @miltonb 我们可能会从不同的开发风格来看待这个问题。 WRT 内部,我不倾向于对它们进行单元测试。如果有问题(通过接口测试确定),要么通过附加调试器很容易追踪,要么类太复杂,应该拆分(使用新类的公共接口进行单元测试)恕我直言
                    【解决方案21】:

                    您可以从 Visual Studio 2008 为私有方法生成测试方法。当您为私有方法创建单元测试时,Test References 文件夹会添加到您的测试项目中,并且访问器会添加到该文件夹​​中。在单元测试方法的逻辑中也提到了访问器。此访问器允许您的单元测试调用您正在测试的代码中的私有方法。 详情请看

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

                    【讨论】:

                      【解决方案22】:

                      由于某种原因,私有类型、内部结构和私有成员如此,而且您通常不想直接与它们打交道。如果你这样做了,你以后很可能会崩溃,因为无法保证创建这些程序集的人会保留私有/内部实现。

                      但是,有时,在对已编译或第三方程序集进行一些破解/探索时,我自己最终想要初始化一个私有类或具有私有或内部构造函数的类。或者,有时,在处理我无法更改的预编译遗留库时 - 我最终会针对私有方法编写一些测试。

                      因此诞生了 AccessPrivateWrapper - http://amazedsaint.blogspot.com/2010/05/accessprivatewrapper-c-40-dynamic.html - 它是一个快速包装类,使用 C# 4.0 动态特性和反射可以轻松完成工作。

                      您可以创建内部/私有类型,例如

                          //Note that the wrapper is dynamic
                          dynamic wrapper = AccessPrivateWrapper.FromType
                              (typeof(SomeKnownClass).Assembly,"ClassWithPrivateConstructor");
                      
                          //Access the private members
                          wrapper.PrivateMethodInPrivateClass();
                      

                      【讨论】:

                        【解决方案23】:

                        有时,测试私有声明可能会很好。 从根本上说,编译器只有一个公共方法:Compile(string outputFileName, params string[] sourceSFileNames)。我相信你明白,如果不测试每个“隐藏”的声明,就很难测试这样的方法!

                        这就是我们创建 Visual T#: 以简化测试的原因。它是一种免费的 .NET 编程语言(兼容 C# v2.0)。

                        我们添加了“.-”运算符。它的行为就像'。运算符,除非您还可以访问测试中的任何隐藏声明,而无需更改测试项目中的任何内容。

                        看看我们的网站:download免费

                        【讨论】:

                          【解决方案24】:

                          MbUnit 有一个很好的包装器,叫做 Reflector。

                          Reflector dogReflector = new Reflector(new Dog());
                          dogReflector.Invoke("DreamAbout", DogDream.Food);
                          

                          您还可以从属性中设置和获取值

                          dogReflector.GetProperty("Age");
                          

                          关于“私人测试”,我同意……在完美世界中。进行私人单元测试是没有意义的。但在现实世界中,您最终可能想要编写私有测试而不是重构代码。

                          【讨论】:

                          【解决方案25】:

                          在 CodeProject 上,有一篇文章简要讨论了测试私有方法的优缺点。然后它提供了一些反射代码来访问私有方法(类似于上面 Marcus 提供的代码。)我在示例中发现的唯一问题是代码没有考虑重载方法。

                          你可以在这里找到这篇文章:

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

                          【讨论】:

                            【解决方案26】:

                            如果您想对私有方法进行单元测试,可能会出现问题。单元测试(一般而言)旨在测试类的接口,即其公共(和受保护)方法。您当然可以“破解”解决方案(即使只是通过公开方法),但您可能还需要考虑:

                            1. 如果您要测试的方法确实值得测试,则可能值得将其移到自己的类中。
                            2. 为调用私有方法的公共方法添加更多测试,测试私有方法的功能。 (正如评论员所指出的,只有当这些私有方法的功能确实是公共接口的一部分时,您才应该这样做。如果它们实际上执行对用户隐藏的功能(即单元测试),这可能很糟糕)。

                            【讨论】:

                            • 选项 2 使单元测试必须了解函数的底层实现。我不喜欢那样做。我通常认为单元测试应该测试功能而不假设任何实现。
                            • 测试实现的缺点是,如果您对实现进行任何更改,测试将很容易被破坏。这是不可取的,因为重构与在 TDD 中编写测试一样重要。
                            • 好吧,如果您更改实现,测试应该会中断。 TDD 意味着首先更改测试。
                            • @sleske - 我不太同意。如果功能没有改变,那么测试就没有理由中断,因为测试应该真正测试行为/状态,而不是实现。这就是 jtr 的含义,它使您的测试变得脆弱。在理想情况下,您应该能够重构您的代码并让您的测试仍然通过,从而验证您的重构没有改变您系统的功能。
                            • 为天真道歉(目前还没有太多的测试经验),但是单元测试的想法不是单独测试每个代码模块吗?我真的不明白为什么私有方法应该被排除在这个想法之外。
                            【解决方案27】:

                            MS Test 内置了一个不错的功能,通过创建一个名为 VSCodeGenAccessors 的文件,使私有成员和方法在项目中可用

                            [System.Diagnostics.DebuggerStepThrough()]
                                [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
                                internal class BaseAccessor
                                {
                            
                                    protected Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject m_privateObject;
                            
                                    protected BaseAccessor(object target, Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
                                    {
                                        m_privateObject = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject(target, type);
                                    }
                            
                                    protected BaseAccessor(Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
                                        :
                                            this(null, type)
                                    {
                                    }
                            
                                    internal virtual object Target
                                    {
                                        get
                                        {
                                            return m_privateObject.Target;
                                        }
                                    }
                            
                                    public override string ToString()
                                    {
                                        return this.Target.ToString();
                                    }
                            
                                    public override bool Equals(object obj)
                                    {
                                        if (typeof(BaseAccessor).IsInstanceOfType(obj))
                                        {
                                            obj = ((BaseAccessor)(obj)).Target;
                                        }
                                        return this.Target.Equals(obj);
                                    }
                            
                                    public override int GetHashCode()
                                    {
                                        return this.Target.GetHashCode();
                                    }
                                }
                            

                            使用从 BaseAccessor 派生的类

                            比如

                            [System.Diagnostics.DebuggerStepThrough()]
                            [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
                            internal class SomeClassAccessor : BaseAccessor
                            {
                            
                                protected static Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType m_privateType = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType(typeof(global::Namespace.SomeClass));
                            
                                internal SomeClassAccessor(global::Namespace.Someclass target)
                                    : base(target, m_privateType)
                                {
                                }
                            
                                internal static string STATIC_STRING
                                {
                                    get
                                    {
                                        string ret = ((string)(m_privateType.GetStaticField("STATIC_STRING")));
                                        return ret;
                                    }
                                    set
                                    {
                                        m_privateType.SetStaticField("STATIC_STRING", value);
                                    }
                                }
                            
                                internal int memberVar    {
                                    get
                                    {
                                        int ret = ((int)(m_privateObject.GetField("memberVar")));
                                        return ret;
                                    }
                                    set
                                    {
                                        m_privateObject.SetField("memberVar", value);
                                    }
                                }
                            
                                internal int PrivateMethodName(int paramName)
                                {
                                    object[] args = new object[] {
                                        paramName};
                                    int ret = (int)(m_privateObject.Invoke("PrivateMethodName", new System.Type[] {
                                            typeof(int)}, args)));
                                    return ret;
                                }
                            

                            【讨论】:

                            • 生成的文件只存在于VS2005中。 2008 年,它们在幕后产生。他们令人厌恶。并且相关的 Shadow 任务在构建服务器上是不稳定的。
                            • 在 VS2012-2013 中也不推荐使用访问器。
                            【解决方案28】:

                            在极少数情况下我想测试私有函数,我通常将它们修改为受保护,并且我编写了一个带有公共包装函数的子类。

                            班级:

                            ...
                            
                            protected void APrivateFunction()
                            {
                                ...
                            }
                            
                            ...
                            

                            用于测试的子类:

                            ...
                            
                            [Test]
                            public void TestAPrivateFunction()
                            {
                                APrivateFunction();
                                //or whatever testing code you want here
                            }
                            
                            ...
                            

                            【讨论】:

                            • 您甚至可以将该子类放在您的单元测试文件中,而不是弄乱真正的类。 +1 狡猾。
                            • 如果可能,我总是将所有与测试相关的代码放在单元测试项目中。这只是伪代码。
                            • 这个函数不是私有的,它是受保护的,最终结果......你让你的代码不那么安全/暴露给子类型私有功能
                            【解决方案29】:

                            我倾向于不使用编译器指令,因为它们很快就会使事情变得混乱。如果您确实需要它们,一种缓解方法是将它们放在部分类中,并让您的构建在制作生产版本时忽略该 .cs 文件。

                            【讨论】:

                            • 您将在生产版本中包含测试访问器(以测试编译器优化等),但在发布版本中排除它们。但是我正在分裂头发并且无论如何我都赞成这个,因为我认为将这些东西放在一个地方是个好主意。谢谢你的想法。
                            【解决方案30】:

                            声明它们internal,然后使用InternalsVisibleToAttribute 允许您的单元测试程序集看到它们。

                            【讨论】:

                            • 我不喜欢使用 InternalsVisibleTo,因为我将方法设为私有是有原因的。
                            猜你喜欢
                            • 2018-08-01
                            • 2022-11-30
                            • 2018-06-09
                            • 1970-01-01
                            • 1970-01-01
                            • 2011-08-10
                            • 2017-09-11
                            • 1970-01-01
                            • 2021-12-07
                            相关资源
                            最近更新 更多