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

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


当前回答

现在是2022年了!

...我们有。net 6

虽然这并没有真正地回答问题,但我现在更喜欢的方法是在同一个c#项目中搭配代码和测试,使用<ClassName>. tests .cs这样的命名约定。然后我使用内部访问修饰符而不是私有。

在项目文件中,我有这样的东西:

<ItemGroup Condition="'$(Configuration)' == 'Release'">
  <Compile Remove="**\*.Tests.cs" />
</ItemGroup>

在发布版本中排除测试文件。根据需要进行修改。

FAQ 1:但是有时候你也想在发布(优化)版本中测试代码。

答:我觉得没必要。我相信编译器将完成它的工作而不会打乱我的意图。到目前为止,我还没有理由质疑它这样做的能力。

FAQ 2:但是我真的想保持方法(或类)私有。

答:本页有许多优秀的解决方案可供尝试。根据我的经验,将访问修饰符设置为内部通常就足够了,因为方法(或类)在它所定义的项目之外是不可见的。除此之外,没什么好隐瞒的了。

其他回答

现在是2022年了!

...我们有。net 6

虽然这并没有真正地回答问题,但我现在更喜欢的方法是在同一个c#项目中搭配代码和测试,使用<ClassName>. tests .cs这样的命名约定。然后我使用内部访问修饰符而不是私有。

在项目文件中,我有这样的东西:

<ItemGroup Condition="'$(Configuration)' == 'Release'">
  <Compile Remove="**\*.Tests.cs" />
</ItemGroup>

在发布版本中排除测试文件。根据需要进行修改。

FAQ 1:但是有时候你也想在发布(优化)版本中测试代码。

答:我觉得没必要。我相信编译器将完成它的工作而不会打乱我的意图。到目前为止,我还没有理由质疑它这样做的能力。

FAQ 2:但是我真的想保持方法(或类)私有。

答:本页有许多优秀的解决方案可供尝试。根据我的经验,将访问修饰符设置为内部通常就足够了,因为方法(或类)在它所定义的项目之外是不可见的。除此之外,没什么好隐瞒的了。

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

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

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

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

您可以使用嵌套类来测试私有方法。例如(使用NUnit v3):

    
    internal static class A
    {
        // ... other code

        private static Int32 Sum(Int32 a, Int32 b) => a + b;

        [TestFixture]
        private static class UnitTests
        {
            [Test]
            public static void OnePlusTwoEqualsThree()
            {
                Assert.AreEqual(3, Sum(1, 2));
            }
        }
    }

此外,可以使用“部分类”特性将测试相关代码移动到另一个文件中,使用“条件编译”将其排除在发布版本之外,等等。先进的例子:

文件交流

    
    internal static partial class A
    {
        // ... other code

        private static Int32 Sum(Int32 a, Int32 b) => a + b;
    }

文件A.UnitTests.cs


#if UNIT_TESTING
    partial class A
    {
        [TestFixture]
        private static class UnitTests
        {
            [Test]
            public static void OnePlusTwoEqualsThree()
            {
                Assert.AreEqual(3, Sum(1, 2));
            }
        }
    }
#endif

public static class PrivateMethodTester
{
    public static object InvokePrivateMethodWithReturnType<T>(this T testObject, string methodName, Type[] methodParamTypes, object[] parameters)
    {
        //shows that we want the nonpublic, static, or instance methods.
        var flags = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Instance;

        //gets the method, but we need the methodparamtypes so that we don't accidentally get an ambiguous method with different params.
        MethodInfo methodInfo = testObject.GetType().GetMethod(methodName, flags, null, methodParamTypes, null);
        if (methodInfo == null)
        {
            throw new Exception("Unable to find method.");
        }

        //invokes our method on our object with the parameters.
        var result = methodInfo.Invoke(testObject, parameters);
        if (result is Task task)
        {
            //if it is a task, it won't resolve without forcing it to resolve, which means we won't get our exceptions.
            task.GetAwaiter().GetResult();
        }

        return result;
    }
}

这样称呼它:

        Type[] paramTypes = new Type[] { typeof(OrderTender), typeof(string) };
        var parameters = new object[] { orderTender, OrderErrorReasonNames.FailedToCloneTransaction };

        myClass.InvokePrivateMethodWithReturnType("myPrivateMethodName", paramTypes, parameters);

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