首页 文章

单元测试C#中的私有方法

提问于
浏览
208

Visual Studio允许通过自动生成的访问器类对私有方法进行单元测试 . 我编写了一个成功编译的私有方法的测试,但它在运行时失败了 . 一个相当小的代码和测试版本是:

//in project MyProj
class TypeA
{
    private List<TypeB> myList = new List<TypeB>();

    private class TypeB
    {
        public TypeB()
        {
        }
    }

    public TypeA()
    {
    }

    private void MyFunc()
    {
        //processing of myList that changes state of instance
    }
}    

//in project TestMyProj           
public void MyFuncTest()
{
    TypeA_Accessor target = new TypeA_Accessor();
    //following line is the one that throws exception
    target.myList.Add(new TypeA_Accessor.TypeB());
    target.MyFunc();

    //check changed state of target
}

运行时错误是:

Object of type System.Collections.Generic.List`1[MyProj.TypeA.TypeA_Accessor+TypeB]' cannot be converted to type 'System.Collections.Generic.List`1[MyProj.TypeA.TypeA+TypeB]'.

根据intellisense - 因此我猜编译器 - 目标是TypeA_Accessor类型 . 但在运行时它的类型为TypeA,因此列表添加失败 .

有什么办法可以阻止这个错误吗?或者,或许更有可能的是,其他人有什么其他建议(我预测可能“不测试私有方法”和“没有单元测试操纵对象的状态”) .

8 回答

  • 234

    是的,不要测试私有方法....单元测试的想法是通过公共'API'测试单元 .

    如果您发现需要测试很多私有行为,那么很可能您在要测试的类中隐藏了一个新的“类”,将其解压缩并通过其公共接口进行测试 .

    一条建议/思考工具.....有一种想法,任何方法都不应该是私密的 . 这意味着所有方法都应该存在于对象的公共接口上....如果您觉得需要将其设置为私有,则很可能存在于另一个对象上 .

    这条建议在实践中并没有很好的解决,但它的建议大多是好的,而且往往会促使人们将对象分解成更小的对象 .

  • 1

    你可以使用PrivateObject Class

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

    “没有什么叫做标准或最佳实践,可能它们只是流行的观点” .

    同样适用于此讨论 .

    enter image description here

    这一切都取决于你认为是一个单位,如果你认为UNIT是一个类,那么你只会点击公共方法 . 如果你认为UNIT是代码行,打私人方法不会让你感到内疚 .

    如果要调用私有方法,可以使用"PrivateObject"类并调用invoke方法 . 您可以观看此深入视频(http://www.youtube.com/watch?v=Vq6Gcs9LrPQ),其中显示了如何使用"PrivateObject",还讨论了私有方法的测试是否合乎逻辑 .

  • -2

    这里的另一个想法是将测试扩展到“内部”类/方法,给出更多白盒感测的测试 . 您可以在程序集上使用InternalsVisibleToAttribute将这些内容公开给单独的单元测试模块 .

    结合密封类,您可以进行这样的封装,即测试方法只能从您的方法的unittest程序集中看到 . 考虑密封类中的受保护方法事实上是私有的 .

    [assembly: InternalsVisibleTo("MyCode.UnitTests")]
    namespace MyCode.MyWatch
    {
        #pragma warning disable CS0628 //invalid because of InternalsVisibleTo
        public sealed class MyWatch
        {
            Func<DateTime> _getNow = delegate () { return DateTime.Now; };
    
    
           //construktor for testing purposes where you "can change DateTime.Now"
           internal protected MyWatch(Func<DateTime> getNow)
           {
               _getNow = getNow;
           }
    
           public MyWatch()
           {            
           }
       }
    }
    

    和单元测试:

    namespace MyCode.UnitTests
    {
    
    [TestMethod]
    public void TestminuteChanged()
    {
        //watch for traviling in time
        DateTime baseTime = DateTime.Now;
        DateTime nowforTesting = baseTime;
        Func<DateTime> _getNowForTesting = delegate () { return nowforTesting; };
    
        MyWatch myWatch= new MyWatch(_getNowForTesting );
        nowforTesting = baseTime.AddMinute(1); //skip minute
        //TODO check myWatch
    }
    
    [TestMethod]
    public void TestStabilityOnFebruary29()
    {
        Func<DateTime> _getNowForTesting = delegate () { return new DateTime(2024, 2, 29); };
        MyWatch myWatch= new MyWatch(_getNowForTesting );
        //component does not crash in overlap year
    }
    }
    
  • 485

    测试私有方法的一种方法是通过反射 . 这也适用于NUnit和XUnit:

    MyObject objUnderTest = new MyObject();
    MethodInfo methodInfo = typeof(MyObject).GetMethod("SomePrivateMethod", BindingFlags.NonPublic | BindingFlags.Instance);
    object[] parameters = {"parameters here"};
    methodInfo.Invoke(objUnderTest, parameters);
    
  • 29

    您可以围绕包含要测试的私有方法的类创建包装类 . 这个包装类包含一个名为Call_MyPrivateFunction的公共方法,该方法又调用其基类的私有函数 .

    请注意,方法的访问级别应更改为[protected]

    代码示例:

    public class Order
    {
        public int Quantity { get; set; }
    
        protected bool OrderIsBig ()
        {
            //This is the method we want to test. It needs to be protected in stead of private. Else... no cigar
            return Quantity > 100;
        }
    }
    
    //Use this wrapper class in your unit test.
    public class FakeOrder : Order
    {
    
        public bool Call_OrderIsBig()
        {
            //This makes the actual call to the protected method "OrderIsBig"
            return OrderIsBig();
        }
    }
    

    单元测试代码可能如下所示:

    FakeOrder order = new FakeOrder();
    order.Quantity = 200;
    
    bool isBig = order.Call_OrderIsBig();   //Make a call to a public method of the FakeOrder class which in turn makes a call to the protected method.
    
  • 5

    TL;DR: 将私有方法解压缩到另一个类,对该类进行测试;了解更多关于SRP原则(单一责任原则)

    看来你需要将 private 方法提取到另一个类;在这应该是 public . 您应该测试另一个类的 public 方法,而不是尝试测试 private 方法 .

    我们有以下场景:

    Class A
    + outputFile: Stream
    - _someLogic(arg1, arg2)
    

    我们需要测试 _someLogic 的逻辑;但似乎 Class A 承担的角色超出了它的需要(违反了SRP原则);只需重构为两个类

    Class A1
        + A1(logicHandler: A2) # take A2 for handle logic
        + outputFile: Stream
    Class A2
        + someLogic(arg1, arg2)
    

    这样 someLogic 可以在A2上测试;在A1中只创建一些假的A2然后注入构造函数来测试A2被调用到名为 someLogic 的函数 .

  • 73

    在VS 2005/2008中,您可以使用private accessor来测试私有成员,但这种方式在VS的后续版本中是 disappear

相关问题