首页 文章

你如何对私人方法进行单元测试?

提问于
浏览
456

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

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

30 回答

  • 0

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

    Microsoft为此提供了两种机制:

    Accessors

    • 转到类定义的源代码

    • 右键单击该类的名称

    • 选择"Create Private Accessor"

    • 选择应在其中创建访问者的项目=>最终将得到一个名为foo_accessor的新类 . 此类将在编译期间动态生成,并且所有成员都可以公开 .

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

    PrivateObject class 另一种方法是使用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[] {/* ... */} );
    
  • 0

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

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

    /**
       *
       * @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));

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

  • 4

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

    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);
    }
    
  • 10

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

    您可以尝试仅使用公共接口覆盖所有内部情况,理论上可以通过使用公共接口完全测试每个内部方法(或至少每个重要的方法),但您可能必须站在头上才能实现这个以及通过公共接口运行的测试用例与他们设计用于测试的解决方案的内部部分之间的联系可能很难或不可能辨别 . 有针对性的,确保内部机器正常工作的个别测试非常值得重构的小测试变化 - 至少这是我的经验 . 如果你必须对每次重构的测试做出巨大的改变,那么也许这没有意义,但在这种情况下,也许你应该完全重新考虑你的设计 . 一个好的设计应该足够灵活,以允许大多数更改,而无需大量重新设计 .

  • 50

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

    这就是为什么我们创建了Visual T#:以便进行更简单的测试 . 它是一种免费的.NET编程语言(兼容C#v2.0) .

    我们添加了'.-'运算符 . 它只是表现得像' . '运算符,除了您还可以从测试中访问任何隐藏的声明而不更改测试项目中的任何内容 .

    看看我们的网站:download it for free .

  • 5

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

    此外,在脚本语言(具有OO功能,如Python,Ruby和PHP)中,您可以在运行时使文件自行测试 . 确保您的更改不会破坏任何内容的快速方法 . 这显然是一个可扩展的解决方案来测试所有类:只需运行它们 . (您也可以使用其他语言执行此操作,其中void main也始终运行其测试) .

  • 113

    在我看来,你应该只测试你的classe的公共API .

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

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

  • 21

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

  • 3

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

    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

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

    • 如果您要测试的方法确实值得测试,那么将它移动到自己的类中可能是值得的 .

    • 向调用私有方法的公共方法添加更多测试,测试私有方法's functionality. (As the commentators indicated, you should only do this if these private methods'的功能实际上是公共接口的一部分 . 如果它们实际执行对用户隐藏的功能(即单元测试),则这可能是坏的) .

  • 75

    一种方法是让您的方法 protected 并编写一个继承您要测试的类的测试夹具 . 这样,你也不会改变你的方法 public ,但你启用了测试 .

  • 0
    CC -Dprivate=public
    

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

  • 114

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

    class :

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

    用于测试的子类:

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

    声明它们 internal ,然后使用InternalsVisibleToAttribute允许单元测试程序集查看它们 .

  • 8

    我认为应该问一个更基本的问题是你为什么要首先尝试测试私有方法 . 这是一个代码气味,你试图通过该类的公共接口测试私有方法,而该方法是私有的,因为它是一个实现细节 . 人们应该只关注公共接口的行为而不是关于它如何在封面下实施 .

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

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

    然后,当我尝试测试原始类时,我可以模拟它的行为,这样我就可以集中精力测试该类的公共接口的行为,而不必测试公共接口的组合爆炸及其所有私有方法的行为 .

    我认为这类似于驾驶汽车 . 当我驾驶汽车时,我不会带着发动机罩,所以我可以看到发动机正在工作 . 我依靠汽车提供的接口,即转速计和速度表来了解发动机是否正常工作 . 当我按下油门踏板时,我依靠汽车实际移动的事实 . 如果我想测试引擎,我可以单独检查它 . :d

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

  • 1

    我还使用了InternalsVisibleToAttribute方法 . 值得一提的是,如果你为了实现这一目标而让你以前的私人方法内部感到不舒服,那么也许他们不应该成为直接单元测试的主题 .

    毕竟,你正在测试你的类的行为,而不是它的具体实现 - 你可以改变后者而不改变前者,你的测试仍然应该通过 .

  • 2

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

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

    在调试模式下构建时,您还可以将其声明为public或internal(使用InternalsVisibleToAttribute):

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

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

  • 337

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

  • 10

    另请注意,InternalsVisibleToAtrribute要求您的程序集为strong named,这使得它在以前没有该要求的解决方案中工作 . 我使用访问器来测试私有方法 . 有关此示例,请参阅this question .

  • 0

    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;
        }
    
  • 0

    关于私有方法的单元测试,这是好的article . 但是我更好,使你的应用程序专门用于测试(就像创建测试只是为了测试)或使用反射进行测试 . 我们大多数人都会选择第二种方式 .

  • 0

    您可以从Visual Studio 2008为私有方法生成测试方法 . 当您为私有方法创建单元测试时,Test References文件夹将添加到您的测试项目中,并且访问者将添加到该文件夹中 . 访问器也在单元测试方法的逻辑中被引用 . 此访问器允许您的单元测试在您正在测试的代码中调用私有方法 . 对于细节看看

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

  • 0

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

    原因是,一旦开始测试类的私有方法和内部,就会将类的实现(私有事物)与测试结合起来 . 这意味着当您决定更改实施细节时,您还必须更改测试 .

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

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

  • 17

    MbUnit为这个名为Reflector的包装器提供了一个很好的包装器 .

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

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

    dogReflector.GetProperty("Age");
    

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

  • 3

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

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

    • 使用反射(复杂)

    • 在同一个类中编写单元测试(通过在其中包含测试代码使 生产环境 代码变得丑陋)

    • 在某种util类中重构并使方法公开

    • 使用@VisibleForTesting注释并删除私有

    我更喜欢注释方法,最简单,最简单 . 唯一的问题是我们提高了知名度,我认为这不是一个大问题 . 我们应该总是编写接口,所以如果我们有一个接口MyService和一个实现MyServiceImpl,那么我们就可以拥有相应的测试类,即MyServiceTest(测试接口方法)和MyServiceImplTest(测试私有方法) . 无论如何,所有客户端都应该使用该接口,即使私有方法的可见性已经增加,它也应该无关紧要 .

  • 5

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

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

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

  • 2

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

  • 2

    那么你可以用两种方式对私有方法进行单元测试

    • 你可以创建 PrivateObject 类的实例语法如下
    PrivateObject obj= new PrivateObject(PrivateClass);
    //now with this obj you can call the private method of PrivateCalss.
    obj.PrivateMethod("Parameters");
    
    • 您可以使用反射 .
    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 });
    
  • 1

    私人类型,内部和私人成员是由于某种原因,并且通常你不想直接搞砸它们 . 如果你这样做,很可能会在以后破解,因为无法保证创建这些程序集的人将保留私有/内部实现 .

    但是,有时候,在对编译或第三方程序集进行一些黑客攻击/探索时,我最终想要使用私有或内部构造函数初始化私有类或类 . 或者,有时,在处理我无法更改的预编译遗留库时 - 我最终会针对私有方法编写一些测试 .

    因此诞生了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();
    

相关问题