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

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


当前回答

遗憾的是。net6中没有PrivateObject类

不过,我写了一个小型扩展方法,能够使用反射调用私有方法。

看一下示例代码:

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

var test = new Test();
var res = test.Invoke<string>("GetStr", "testparam", 123);
Console.WriteLine(res); // "Success! testparam 123"

下面是扩展方法的实现:

/// <summary>
/// Invokes a private/public method on an object. Useful for unit testing.
/// </summary>
/// <typeparam name="T">Specifies the method invocation result type.</typeparam>
/// <param name="obj">The object containing the method.</param>
/// <param name="methodName">Name of the method.</param>
/// <param name="parameters">Parameters to pass to the method.</param>
/// <returns>The result of the method invocation.</returns>
/// <exception cref="ArgumentException">When no such method exists on the object.</exception>
/// <exception cref="ArgumentException">When the method invocation resulted in an object of different type, as the type param T.</exception>
/// <example>
/// class Test
/// {
///   private string GetStr(string x, int y) => $"Success! {x} {y}";
/// }
///
/// var test = new Test();
/// var res = test.Invoke&lt;string&gt;("GetStr", "testparam", 123);
/// Console.WriteLine(res); // "Success! testparam 123"
/// </example>
public static T Invoke<T>(this object obj, string methodName, params object[] parameters)
{
  var method = obj.GetType().GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  if (method == null)
  {
    throw new ArgumentException($"No private method \"{methodName}\" found in class \"{obj.GetType().Name}\"");
  }

  var res = method.Invoke(obj, parameters);
  if (res is T)
  {
    return (T)res;
  }

  throw new ArgumentException($"Bad type parameter. Type parameter is of type \"{typeof(T).Name}\", whereas method invocation result is of type \"{res.GetType().Name}\"");
}

其他回答

如果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来测试公开的方法和属性。

你可以使用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的项目

提取私有方法到另一个类,在该类上进行测试;阅读更多关于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的函数中。

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

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

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

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

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

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