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

Unit testing private methods in C#

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//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
}

运行时错误为:

1
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_访问器类型。但在运行时,它是typea类型,因此列表添加失败。

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


可以使用privateobject类

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


是的,不要测试私有方法….单元测试的思想是通过它的公共"api"来测试单元。

如果你发现你需要测试很多私人行为,很可能你有一个新的"类"隐藏在你试图测试的类中,提取它并通过它的公共接口测试它。

一条建议/思考工具……有一种观点认为任何方法都不应该是私有的。这意味着所有方法都应该位于对象的公共接口上….如果你觉得你需要让它私有化,它很可能生活在另一个物体上。

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


"没有所谓的标准或最佳实践,可能它们只是流行的观点"。

这个讨论也是如此。

enter image description here

这完全取决于你认为单元是什么,如果你认为单元是一个类,那么你只会碰到公共方法。如果你认为单位是一行代码,打私人方法不会让你感到内疚。

如果要调用私有方法,可以使用"privateobject"类并调用invoke方法。你可以观看这段深入的YouTube视频(http://www.youtube.com/watch?v=vq6gcs9lrpq),说明如何使用"privateobject",并讨论私有方法的测试是否合乎逻辑。


这里的另一个想法是将测试扩展到"内部"类/方法,使测试有更多的白盒意义。可以在程序集上使用InternalsVisibleToAttribute将它们公开给单独的单元测试模块。

结合密封类,您可以接近这样的封装,这样测试方法只能从UnitTest程序集中看到您的方法。考虑到密封类中的受保护方法实际上是私有的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[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()
       {            
       }
   }
}

单元测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
}
}


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

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


可以围绕包含要测试的私有方法的类创建包装类。这个包装类包含一个名为call-myprivatefunction的公共方法,该方法反过来调用其基类的私有函数。

请注意,方法的访问级别将更改为[受保护]

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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();
    }
}

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

1
2
3
4
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.


Ermh…这里出现了完全相同的问题:测试一个简单但关键的私有方法。看完这条线后,似乎像是"我想在这块简单的金属上钻这个简单的孔,我想确保质量符合规格",然后出现"好吧,这不容易。首先,没有合适的工具可以做到这一点,但是你可以在你的花园里建造一个重力波观测站。请阅读我在http://foobar.brigther-than-einstein.org/上的文章。首先,当然,你必须参加一些高级量子物理课程,然后你需要大量的超冷氮,当然,还有我在亚马逊上的书"…

换句话说…

不,第一件事。

每一种方法,可以是私有的、内部的、受保护的、公共的,都必须是可测试的。必须有一种方法来实现这样的测试,而不需要像这里所介绍的那样做。

为什么?正是因为到目前为止一些贡献者提到了体系结构。也许对软件原则的简单重申可以消除一些误解。

在这种情况下,通常的嫌疑犯是:OCP、SRP和KIS。

但是等一下。让一切公开化的想法更多的是政治上的和一种态度。但是。当涉及到代码时,即使在当时的开源社区中,这也不是教条。相反,"隐藏"一些东西是一个很好的实践,可以使熟悉某个API变得更容易。例如,你会隐藏你新推出的面向市场的数字温度计构建块的核心计算——不是为了向好奇的代码阅读器隐藏实际测量曲线背后的数学,而是为了防止你的代码变得依赖于某些突然重要的用户,他们无法抗拒使用你以前的私有、内部和保护。TED代码来实现自己的想法。

我在说什么?

private double TranslateMeasurementIntoLinear(double actualMeasurement);

它很容易宣布水瓶座的年龄或什么是现在被称为,但如果我的一块传感器从1.0到2.0,执行翻译…可能会从一个简单的线性方程,很容易理解,对每个人都"可用",变为一个相当复杂的计算,使用分析或任何东西,所以我会破坏其他人的代码。为什么?因为他们不理解软件编码的原则,甚至连kis都不懂。

为了使这个童话故事简短化:我们需要一种简单的方法来测试私有方法——不用麻烦。

第一:大家新年快乐!

第二:排练你的建筑师课程。

第三,"公共"修饰语是宗教,不是解决问题的方法。


tl;dr:将私有方法提取到另一个类中,在该类上进行测试;了解更多有关srp原则(单一责任原则)的信息。

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

我们有以下场景:

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

我们需要测试_someLogic的逻辑;但是看起来Class A的作用比它需要的要大(违反srp原则);只是重构成两个类。

1
2
3
4
5
Class A1
    + A1(logicHandler: A2) # take A2 for handle logic
    + outputFile: Stream
Class A2
    + someLogic(arg1, arg2)

这样,可以在a2上测试someLogic;在a1中,只需创建一些假的a2,然后注入到构造函数,以测试a2是否被调用到名为someLogic的函数。


在VS2005/2008中,您可以使用私有访问器来测试私有成员,但这种方法在较新版本的VS中消失了。