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

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


当前回答

在VS 2005/2008中,你可以使用私有访问器来测试私有成员,但是这种方法在后来的VS版本中消失了

其他回答

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

我有另一种适合我的方法。因为我总是在调试模式下运行我的测试,所以我使用#if debug在我的私有方法之前添加public。我的私有方法是这样的

public class Test
{
    #if (DEBUG)
      public
    #endif
    string PrivateMehtod()
    {
      return "PrivateMehtod called";
    }
}

摘自《有效使用遗留代码》一书:

“如果我们需要测试一个私有方法,我们应该让它公开。如果 让它公开让我们很困扰,在大多数情况下,这意味着我们的类是 做得太多了,我们应该解决它。”

根据作者的说法,修复它的方法是创建一个新类并将该方法添加为public。

作者进一步解释说:

“好的设计是可测试的,不能测试的设计是糟糕的。”

因此,在这些限制范围内,您唯一真正的选择是将方法设为公共的,无论是在当前类中还是在新类中。

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

同样的道理也适用于这个讨论。

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

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