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

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


当前回答

提取私有方法到另一个类,在该类上进行测试;阅读更多关于SRP原则(单一责任原则)

看起来你需要将私有方法提取到另一个类;在这方面应该是公开的。您应该测试另一个类的公共方法,而不是试图测试私有方法。

我们有以下场景:

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

我们需要测试_someLogic;但A类似乎扮演了过多的角色(违反SRP原则);只需将其重构为两个类

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

这样就可以在A2上测试一些逻辑;在A1中,只需创建一些伪A2,然后注入到构造函数中,以测试A2被调用到名为someLogic的函数中。

其他回答

Ermh... Came along here with exactly the same problem: Test a simple, but pivotal private method. After reading this thread, it appears to be like "I want to drill this simple hole in this simple piece of metal, and I want to make sure the quality meets the specs", and then comes "Okay, this is not to easy. First of all, there is no proper tool to do so, but you could build a gravitational-wave observatory in your garden. Read my article at http://foobar.brigther-than-einstein.org/ First, of course, you have to attend some advanced quantum physics courses, then you need tons of ultra-cool nitrogenium, and then, of course, my book available at Amazon"...

换句话说……

不,重要的事先做。

每一个方法,可能是私有的,内部的,受保护的,公共的都必须是可测试的。必须有一种方法可以毫不费力地实现这样的测试,就像这里介绍的那样。

为什么?正是因为到目前为止一些贡献者所做的架构介绍。也许简单地重申一下软件原则就可以消除一些误解。

在这种情况下,通常的怀疑对象是:OCP、SRP和KIS。

But wait a minute. The idea of making everything publicly available is more of less political and a kind of an attitude. But. When it comes to code, even in then Open Source Community, this is no dogma. Instead, "hiding" something is good practice to make it easier to come familiar with a certain API. You would hide, for example, the very core calculations of your new-to-market digital thermometer building block--not to hide the maths behind the real measured curve to curious code readers, but to prevent your code from becoming dependent on some, perhaps suddenly important users who could not resist using your formerly private, internal, protected code to implement their own ideas.

我在说什么?

private double translatemmeasurementintolinear (double actualMeasurement);

现在很容易宣布水瓶座时代或被称为什么,但如果我的传感器从1.0到2.0,翻译的实现……可能会从一个简单的易于理解和对每个人都“可重复使用”的线性方程,变成一个相当复杂的计算,使用分析或其他东西,所以我会破坏别人的代码。为什么?因为他们不懂软件编码的基本原理,甚至KIS也不懂。

简而言之:我们需要一种简单的方法来测试私有方法——毫不费力。

第一:大家新年快乐!

第二:预习你的建筑师课程。

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

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

结合密封类,你可以达到这样的封装,测试方法只能从单元测试程序集你的方法可见。考虑到密封类中的受保护方法实际上是私有的。

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

你可以使用PrivateObject类:

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

注意:PrivateObject和PrivateType不适用于针对netcoreapp2.0 - GitHub Issue 366的项目

如果PrivateObject不可用,并且被测试的类不是密封类,则可以将想要公开的方法和属性设置为受保护的。在单元测试文件中创建一个带有内部方法的继承类,这些内部方法公开了测试中的私有方法/属性。

如果被测试的类是:

class MyClass{private string GetStr(string x, int y) => $"Success! {x} {y}";}

改为:

class MyClass{protected string GetStr(string x, int y) => $"Success! {x} {y}";}

在单元测试文件中创建一个继承类,如下所示:

class MyClassExposed: MyClass 
{
    internal string ExposedGetStr(string x, int y)
    {
        return base.GetStr(x, y);
    }
}

现在您可以使用继承的类MyClassExposed来测试公开的方法和属性。

. net不再允许使用访问器。您可以使用我在这里发布的代码来回答类似的问题。

如何对私有方法进行单元测试?