在C#中对私有方法进行单元测试


291

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]'.

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

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


您需要私有类TypeB的访问器。访问器TypeA_Accessor提供对TypeA的私有方法和受保护方法的访问。但是TypeB不是方法。这是一堂课。
Dima

访问器提供对私有/受保护的方法,成员,属性和事件的访问。它不提供对您班级内私有/受保护班级的访问。私有/受保护的类(TypeB)仅应由拥有类(TypeA)的方法使用。因此,基本上,您正在尝试将TypeA之外的私有类(TypeB)添加到私有的“ myList”中。由于您正在使用访问器,因此访问myList没有问题。但是,您不能通过访问器使用TypeB。可能的解决方案是将TypeB移到TypeA之外。但这会破坏您的设计。
Dima

Answers:


274

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

如果发现需要测试许多私有行为,则很可能在试图测试的类中隐藏了一个新的“类”,将其提取并通过其公共接口对其进行测试。

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

这条建议在实践中并没有完全解决,但它基本上是很好的建议,并且经常会促使人们将其对象分解为较小的对象。


384
我不同意。在OOD中,私有方法和属性是一种不重复自己的固有方法(en.wikipedia.org/wiki/Don%27t_repeat_yourself)。黑匣子编程和封装背后的想法是向订户隐藏技术细节。因此,确实有必要在代码中包含非平凡的私有方法和属性。而且,如果它不是很简单的话,则需要进行测试。
AxD

8
它不是内在的,某些OO语言没有私有方法,私有属性可以包含带有可测试的公共接口的对象。
基思·尼古拉斯

7
该建议的重点是,如果您的对象做一件事并且是DRY,则通常没有理由拥有私有方法。私有方法通常会执行对象真正不负责的事情,但是很有用,如果不是很琐碎,那么它通常是另一个对象,因为它可能违反了SRP
Keith Nicholas

28
错误。您可能希望使用私有方法来避免代码重复。或用于验证。或者出于公共世界不应该知道的许多其他目的。
Jorj

37
当您被丢到如此糟糕的OO代码库上并被要求“逐步改进”时,发现我无法对私有方法进行一些首次测试令人失望。是的,也许在教科书中没有这些方法,但是在现实世界中,我们有满足产品需求的用户。我不能只进行大量的“看似干净的代码”重构,而不能在项目中进行一些测试来构建。感觉就像是另一个强迫实践中的程序员进入天真的看似好的途径,却没有考虑到真正混乱的情况的另一种情况。
dune.rocks

666

您可以使用PrivateObject类

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

25
现在,Microsoft已添加PrivateObject,这是正确的答案。
Zoey 2014年

4
好的答案,但请注意,需要对PrivateMethod进行“保护”,而不是“私有”。
HerbalMart 2014年

23
@HerbalMart:也许我对您有误解,但是如果您建议PrivateObject只能访问受保护的成员,而不能访问私有的成员,那您就错了。
kmote

17
@JeffPearce对于静态方法,可以使用“ PrivateType pt = new PrivateType(typeof(MyClass));”,然后像在私有对象上调用Invoke一样在pt对象上调用InvokeStatic。
史蒂夫·希伯特

12
万一有人想知道自MSTest.TestFramework v1.2.1起-面向.NET Core 2.0的项目无法使用PrivateObject和PrivateType类-对此存在github问题:github.com/Microsoft/testfx/issues/366
shiitake

98

“没有所谓的标准或最佳实践,可能只是流行的意见”。

对于此讨论也是如此。

在此处输入图片说明

这完全取决于您认为的单元,如果您认为UNIT是一个类,那么您只会使用public方法。如果您认为UNIT是代码行,那么使用私有方法不会让您感到内gui。

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


55

这里的另一个想法是将测试扩展到“内部”类/方法,从而使这种测试具有更多的白盒子意义。您可以在程序集上使用InternalsVisibleToAttribute将其暴露给单独的单元测试模块。

与密封类结合使用时,您可以采用这样的封装方式:仅从单元测试程序集的方法中可见测试方法。考虑到密封类中的受保护方法实际上是私有的。

[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
}
}

我不确定我是否理解。InternalsVisibleToAttribute使标记为“内部”的方法和属性可以访问,但是我的字段和方法是“私有”的。您是在建议我将事情从私人变为内部吗?我想我误会了。
junichiro

2
是的,这就是我的建议。有点“ hacky”,但至少它们不是“公开的”。
杰夫

27
这是一个很好的答案,只是因为它没有说“不测试私有方法”,但是是的,它相当“ hacky”。我希望有一个解决方案。IMO很难说“不应该测试私有方法”,因为我的看法:这等效于“私有方法不应该正确”。
2013年

5
ya ken,我也对那些声称不应在单元测试中测试私有方法的人感到困惑。公共API是输出,但是有时错误的实现也会提供正确的输出。否则该实现会带来一些不良影响,例如,保留不必要的资源,引用对象以防止其被gc收集等。除非他们提供其他可以覆盖私有方法而不是单元测试的测试,否则我会认为它们无法维护100%已测试的代码。
小马先生2013年

我同意MasterMastic。这应该是公认的答案。
XDS

27

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

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

call methods 静态非静态
Kiquenet

2
依赖反射的方法的缺点是,当您使用R#重命名方法时,它们往往会中断。在小型项目上,这可能不是什么大问题,但是在庞大的代码库上,以这种方式破坏单元测试然后不得不四处寻找并快速修复它们就显得有些na。从这个意义上说,我的钱就交给了杰夫。
XDS

2
@XDS糟糕的nameof()无法从其类外部获取私有方法的名称。
加布里埃尔·莫林

12

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

换一种说法...

不,第一件事。

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

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

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

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

我在说什么

私人double TranslateMeasurementIntoLinear(double actualMeasurement);

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

简而言之:我们需要一种简单的方法来测试私有方法-事不宜迟。

第一:大家新年快乐!

第二:演练您的架构师课程。

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


5

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

[TestFixture]
public class UnitTests : ObjectWithPrivateMethods
{
    [Test]
    public void TestSomeProtectedMethod()
    {
        Assert.IsTrue(this.SomeProtectedMethod() == true, "Failed test, result false");
    }
}

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

然而...

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

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

编辑:我发现人们投票否决这个答案确实很可笑,因为我明确地将其描述为一个坏主意。这是否意味着人们同意我的观点?我感到很困惑.....


这是一个创造性的解决方案,但有点怪异。
shinzou

3

从《有效使用旧版代码工作》一书中:

“如果我们需要测试一个私有方法,我们应该将其公开。如果公开它会使我们困扰,在大多数情况下,这意味着我们班级做了太多事情,我们应该修复它。”

根据作者的说法,解决此问题的方法是创建一个新类,并将方法添加为public

作者进一步解释:

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

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


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


0

在VS 2005/2008中,您可以使用私有访问器来测试私有成员,但是这种方式在更高版本的VS中消失


1
从2008年到2010年初,这是一个很好的答案。现在,请参考PrivateObject和Reflection替代方法(请参见上面的几个答案)。VS2010存在访问器错误,MS在VS2012中弃用了它。除非您被迫使用VS2010或更早版本(> 18岁的构建工具),否则请避免使用私有访问器,以节省时间。:-)。
Zephan Schroeder
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.