zoukankan      html  css  js  c++  java
  • 如何测试私有/受保护的方法? (译文)


     

     

     

    1.   简介

    Test Driven Development 的步骤是先写测试,然后写代码让测试通过,然后再重构。这些概念在.net环境中由于对软件质量要求的提高,越来越受到重视。测试公有方法是很容易的,自然而然的就有问题产生了“如何测试protectedprivate方法”?本文包括:

    l         总结一些测试私有方法的常用做法;

    l         给出一些有用的方法,即使你不知道哪种方法最优;

    l         提高代码演示这些测试技术。

     

     

    2.   背景

    2.1. 是否应该测试私有方法?

    GOOGLE(Google search)上搜索一下就会发现无数相关讨论,更不用说实际的测试实现了。下面的表格对这些讨论做了总结,并把利弊都列出如下:

     

    观点

    使用私有方法

    • 封装- 私有函数提供了封装,方便了客户端的调用。
    • 重构- 重构私有方法很容易,因为没有被外部调用,因此修改其声明方式不会破坏外面的任何调用。
    • 校验- 不象公有方法必须要校验外部的输入数据,私有方法不需要复杂的校验(因为在公有方法中已经校验过)。
    • 测试范围- 将所有方法设成公有,扩大了测试的范围。私有方法只有在开发人员需要时才测试,公有方法使用广泛,必须要全部测试。
    • 不重构- 如果一个类复杂到不需要使用私有方法,则应该进行重构。
    • 功能隐藏- 私有方法(如果设计正确的话)提供了有用的特性,如果需要客户端访问而测试的话,则应该设置为公有。

    测试私有方法

    • 测试控制- 私有方法可能包含了复杂的逻辑,测试应该包含这些方法的测试,而不能因为公有方法中调用了这些方法就认为没有问题。
    • 原则- 单元测试用于测试最小的代码片段,私有方法是一段功能代码,因此应该被测试。
    • 已经覆盖- 只有公共接口应该被测试,在测试公有方法时已经内部调用测试了私有方法。
    • 不好的代码 如果你重构代码的时候改变了私有方法,并且你的测试中连接到了相关的私有方法,则你需要再修改测试代码。

    赞成和反对的人都是比较厉害的角色。我既不打算也不期望结束这些争论,我只想了解他们各自在测试中的用处。即使你不想测试私有方法:

    l         但如果你知道如何测试,你的意见就更有效(而不会因为你不知道就简单的说“不测试”);

    l         针对测试非公有方法可以帮助你的团队工作的更好;

    l         只要可能,就值得找到一个简便的方法测试他们。

     

     

    2.2. 好的标准和不准确的技术

    Andrew Hunt and David Thomas 在他的书中, Pragmatic Unit Testing in C# with NUnit, 说明好的单元测试应该具有下面的特点(ATRIP):

    l         自动化

    l         充分;

    l         可重复;

    l         独立的;

    l         专业的;

    针对私有/保护方法的完全测试,还有其它三项标准

    l         透明:不要修改系统到测试环境(system under test),如在发布代码中封装测试代码;

    l         范围:能在debug/release模式下运行;

    l         简单性:使用简单,易于修改且风险小。

     

    记住这些标准,这里有几个简单的策略:

    策略

    问题

    不要有任何私有方法

    • 回避了这个问题。

    使用#if DEBUG ... #endif 封装私有方法为公有方法,这样测试方法就可以通过公有的封装接口访问私有方法。(但此方法背离了OO思想).

    • 这种方法只能在调试模式有效。
    • 过程式而非OO。需要在发布和单元测试中封装各个方法。
    • 通过增加公有方法违背了上面的准则。

    在封装私有方法的公有接口上使用[Conditional("DEBUG")] 属性

    • 只能在调试模式下工作。

    创建内部方法访问这些私有方法,并在程序集中其它地方声明一些公有方法封装这些内部方法。

    • 这修改了发布产品的代码,并使得发布产品中包含有测试代码。
    • 需要大量额外的编程,而且容易有问题。

    3.   测试Protected 方法

    Protected方法只在派生类可见,因此测试类中不能直接看到。如测试ClassLibrary1.MyObject下面的方法:

    protected string MyProtectedMethod(string strInput, int i32Value)

    {

        return this.Name + ": " + strInput + ", " +

         i32Value.ToString();

    }

    Pragmatic Unit Testing in C# with NUnit 中提供了一个方法,从MyObject派生一个类MyObjectTester,然后建立一个公有方法TestMyProtectedMethod,封装上面的protected方法,如下:

     

    public new string TestMyProtectedMethod(string strInput, int i32Value)

    {

        return base.MyProtectedMethod(strInput,

         i32Value);

    }

    该方法很简单,满足了上面的所有标准:

    标准

    满足性

    透明性

    使用继承将MyObjectTester 类放到UnitTests 程序集中,没有在产品代码中添加新的代码。

    范围

    与是否调试模式无关。

    简单性

    虽然本方法需要一个继承的类,但符合OO思想。

    4.   测试私有方法

    测试私有方法稍微复杂些,但我们可以利用System.Reflection。你可以利用反射动态的访问一个类型的方法,包括实例和静态的私有方法。要注意的是访问私有方法需要ReflectionPermission,但在开发机或BUILD机上运行单元测试不是问题。

    假设我们要测试ClassLibrary1.MyObject类的MyPrivateMethod私有方法:

    private string MyPrivateMethod(string strInput, DateTime dt, double

     dbl)

    {

        return this.Name + ": " + strInput + ", " +

         dt.ToString() + ", " + dbl.ToString();

    }

    一种解决方法是建立一个UnitTestUtilities 工程包含一个UnitTestUtilities.Helper类,用于通过反射调用测试方法。

    public static object RunStaticMethod(System.Type t, string strMethod,

     object [] aobjParams)

    {

        BindingFlags eFlags =

         BindingFlags.Static | BindingFlags.Public |

         BindingFlags.NonPublic;

        return RunMethod(t, strMethod,

         null, aobjParams, eFlags);

    } //end of method

    public static object RunInstanceMethod(System.Type t, string strMethod,

     object objInstance, object [] aobjParams)

    {

        BindingFlags eFlags = BindingFlags.Instance | BindingFlags.Public |

         BindingFlags.NonPublic;

        return RunMethod(t, strMethod,

         objInstance, aobjParams, eFlags);

    } //end of method

    private static object RunMethod(System.Type t, string

     strMethod, object objInstance, object [] aobjParams, BindingFlags eFlags)

    {

        MethodInfo m;

        try

        {

            m = t.GetMethod(strMethod, eFlags);

            if (m == null)

            {

                 throw new ArgumentException("There is no method '" +

                  strMethod + "' for type '" + t.ToString() + "'.");

            }

                                   

            object objRet = m.Invoke(objInstance, aobjParams);

            return objRet;

        }

        catch

        {

            throw;

        }

    } //end of method

    私有方法RunMethod利用反射时必须的参数,调用相关的被测试方法并返回值。这里有两个封装的方法RunStaticMethodRunInstanceMethod,分别针对静太方法和实例方法。

    大概看一下这两个方法的代码,首先取得类型的MethodInfo,因为我们调用的是已经存在的方法,如果是空方法,则产生一个异常。一旦有了MethodInfo,我们就可以调用实例(如果是static,则为null)的私有方法,并传入参数数组。

    我们可以在NUnit中这样使用:

     

     [Test] public void TestPrivateInstanceMethod()

    {

        string strExpected = "MyName: Hello, 5/24/2004

         12:00:00 AM, 2.1";

        

        ClassLibrary1.MyObject objInstance

         = new MyObject("MyName");

       

        object obj =

         UnitTestUtilities.Helper.RunInstanceMethod(

         typeof(ClassLibrary1.MyObject), "MyPrivateMethod",

         objInstance, new object[3] {"Hello",

         new DateTime(2004,05,24), 2.1});

       

        string strActual = Convert.ToString(obj);

       

        Assert.AreEqual(strExpected,strActual);

    }

    标准

    满足性

    透明性

    唯一的额外代码- UnitTestUtilities,不会包含在产品中一起发布。

    范围

    该方法中没有任何东西是仅依赖于调试模式。

    简单性

    该方法可以在一个单独调用中调用任何方法。利用UnitTestUtilities的唯一问题就是为RunInstanceMethodRunStaticMethod创建正确的参数(方法名、数据类型等)。因为方法是动态调用的,参数在编译时不会被检查。

     

    5.   结论

    虽然对私有方法的测试存在争论,至少我们现在知道如何进行测试了。我们可以通过包装基类建立一个派生类TesterClass用于测试其protected方法,通过UnitTestUtility帮助类利用反射可以测试类中的私有方法。所有这些技术都可利于提高测试覆盖率。


    参考:

    http://www.codeproject.com/csharp/TestNonPublicMembers.asp 

  • 相关阅读:
    URL编码和Bsae64编码
    在控制台保存下载数据文件方法
    前端下载文件的几种方式
    npm i error:0909006C:PEM routines:get_name:no start line 遇到问题解决
    MapTalks.js 使用小结(三): 各类地图加载
    在线瓦片地图服务资源 总结
    npm webpack 遇到的问题解决方案总结
    MapTalks.js 使用小结(二)
    可拖动的进度条 in vue
    滚动条vue-seamless-scroll的使用
  • 原文地址:https://www.cnblogs.com/margiex/p/128840.html
Copyright © 2011-2022 走看看