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

时间:2012-02-03 01:50:03

标签: c# unit-testing

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,因此列表添加失败。

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

12 个答案:

答案 0 :(得分:552)

您可以使用PrivateObject Class

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

答案 1 :(得分:248)

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

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

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

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

答案 2 :(得分:78)

“没有什么可以作为标准或最佳实践,可能它们只是流行的观点”。

同样适用于此讨论。

enter image description here

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

如果要调用私有方法,可以使用“PrivateObject”类并调用invoke方法。你可以观看这个暗淡的youtube视频(http://www.youtube.com/watch?v=Vq6Gcs9LrPQ),它展示了如何使用“PrivateObject”,还讨论了私有方法的测试是否合乎逻辑。

答案 3 :(得分:37)

这里的另一个想法是将测试扩展到“内部”类/方法,给出更多白盒感测的测试。您可以在程序集上使用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
}
}

答案 4 :(得分:20)

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

include()

答案 5 :(得分:6)

您可以围绕包含要测试的私有方法的类创建包装类。 这个包装类包含一个名为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.

答案 6 :(得分:5)

Ermh ...出现了完全相同的问题:测试一个简单但却是关键的 private 方法。读完该线程后,看起来就像“我想在这种简单的金属上钻出这个简单的孔,然后确保质量符合规格”,然后出现“好吧,这不是首先,没有合适的工具可以这样做,但是您可以在花园中建造一个重力波观测台,请阅读http://foobar.brigther-than-einstein.org/上的文章首先,当然,您必须参加一些高级研究物理课程,那么您需要大量的超冷氮,然后,当然,我的书就可以在亚马逊上找到”。

换句话说...

不,首先是

每种方法,无论是私有的,内部的,受保护的,公开的,都可以进行测试。必须有一种方法来实现这种测试,而无需如本文所述。

为什么?正是因为到目前为止,一些贡献者所做的体系结构提及。简单重申一下软件原理可能会消除一些误会。

在这种情况下,通常的嫌疑人是:OCP,SRP和一如既往的KIS。

但是请稍等。使所有内容公开可用的想法更多是出于政治目的,而不是一种态度。但。关于代码,即使在当时的开源社区中,这也不是教条。相反,“隐藏”某些东西是使您更容易熟悉某个API的好习惯。例如,您将隐藏新上市的数字温度计构建模块的核心计算,而不是将数学运算隐藏在真实测得的曲线后面,以免引起好奇的代码阅读者,但要防止您的代码变得依赖某些代码,也许突然之间,重要的用户无法抗拒使用您以前私有的,内部受保护的代码来实现自己的想法。

我在说什么?

  

private double TranslateMeasurementIntoLinear(double actualMeasurement);

很容易宣布水瓶座时代或当今所谓的水瓶座时代,但是如果我的传感器从1.0升级到2.0,那么Translate ...的实现可能会从简单的线性方程式改变,该线性方程式易于理解并且对于每个人来说都是“可重用的”,可以使用分析或其他方式进行相当复杂的计算,因此我会破坏他人的代码。为什么?因为他们不了解软件编码的基本原理,甚至都不了解KIS。

简而言之,我们需要一种简单的方法来测试私有方法,而无需费力。

第一:大家新年快乐!

第二:演练您的建筑师课程。

第三:“公共”修饰语是宗教,而不是解决方案。

答案 7 :(得分:2)

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

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

我们有以下情况:

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的函数。

答案 8 :(得分:0)

另一个未提及的选项是将单元测试类创建为要测试的对象的子代。 NUnit示例:

<canvas id="canvas" width=48 height=48 style="background: #383838">

这将允许轻松测试私有和受保护(但不是继承的私有)方法,并且可以将所有测试与真实代码分开,从而避免将测试程序集部署到生产环境中。在许多继承的对象中,将您的私有方法切换到受保护的方法是可以接受的,并且这是非常简单的更改。

但是...

尽管这是解决如何测试隐藏方法的有趣方法,但是我不确定我会主张在所有情况下这都是解决问题的正确方法。在内部测试对象似乎有些奇怪,我怀疑在某些情况下这种方法会对您造成打击。 (例如,不变的对象可能会使某些测试变得非常困难)。

虽然我提到了这种方法,但我认为这更多是集思广益的建议,而不是合法的解决方案。撒一粒盐。

答案 9 :(得分:0)

摘自这本书有效地使用旧版代码

  

“如果需要测试私有方法,则应将其公开。如果   在大多数情况下,将其公之于众使我们感到困扰。   做得太多,我们应该修复它。”

作者认为,解决此问题的方法是创建一个新类,并将方法添加为my_dict = {'motorway': {'left': [100, 120, 130, 600, 800]}, 'trunk': {'left': [200, 240, 290, 345, 980]}, 'primary': {'left': [320, 340, 330]}}

作者进一步解释:

  

“好的设计是可以测试的,而不能测试的设计是不好的。”

因此,在这些限制内,您唯一的实际选择是在当前类或新类中使用方法public

答案 10 :(得分:0)

我使用这个助手(对象类型扩展)

 public static  TReturn CallPrivateMethod<TReturn>(
        this object instance,
        string methodName,
        params object[] parameters)
    {
        Type type = instance.GetType();
        BindingFlags bindingAttr = BindingFlags.NonPublic | BindingFlags.Instance;
        MethodInfo method = type.GetMethod(methodName, bindingAttr);

        return (TReturn)method.Invoke(instance, parameters);
    }

你可以这样称呼它

Calculator systemUnderTest = new Calculator();
int result = systemUnderTest.CallPrivateMethod<int>("PrivateAdd",1,8);

优点之一是它使用泛型来预先确定返回类型。

答案 11 :(得分:-3)

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