如何访问静态 Class 私有字段以在 C# 中使用 Microsoft Fakes 对其方法进行单元测试

How to access a Static Class Private fields to unit test its methods using Microsoft Fakes in C#

我有下面的静态 class 和其中的一个方法,我需要对其进行单元测试。我可以 但是这个方法有一个 if 条件,它使用一个布尔私有变量,如果它的值为 false,那么它会执行该 if 条件中的步骤。

public static class Logger
{
    private static bool bNoError = true;
    public static void Log()
    {
        if (!bNoError)
        {
            //Then execute the logic here
        }
        else
        {
            //else condition logic here
        }
    }
} 

有没有一种方法可以将私有字段 bNoError 值设置为 true,这样我就可以使用一种测试方法来测试 if 条件下的逻辑。

嗯,一个方法就是在原来的class上加一个方法然后shim一下:

public static class Logger
{
    // .... other stuff here

    private static void SetbNoError(bool flag)
    {
        // leave implementation empty 
    }
}

然后在你的测试中:

ShimLogger.SetbNoErrorBool = flag => bNoError = flag;

您可以使用反射来进行测试。虽然这很违法。

using System.Reflection;
................
................
var field = typeof(Logger).GetField("bNoError", 
                            BindingFlags.Static | 
                            BindingFlags.NonPublic);

        // Normally the first argument to "SetValue" is the instance
        // of the type but since we are mutating a static field we pass "null"
        field.SetValue(null, false);

出于单元测试的目的,Microsoft 已经实现了一些帮助程序 类 (PrivateType and PrivateObject),它们在此类场景中使用反射。

PrivateType myTypeAccessor = new PrivateType(typeof(TypeToAccess));
myTypeAccessor.SetStaticFieldOrProperty("bNoError", false);

PrivateType 用于静态访问,而 PrivateObject 用于针对实例化对象进行测试。

您需要包含 Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll 中的 Microsoft.VisualStudio.TestTools.UnitTesting 命名空间才能使用它们。

我不建议在单元测试中使用反射。在有很多开发人员的大型项目上很难维护,因为它不容易重构,发现。

此外,单元测试应该测试 class 的某些行为。如果在被测试的 class 中存在某种错误处理,它应该不仅可以被单元测试访问,而且在真实场景中也可以被开发人员(调用者)访问,而不是被封装隐藏。如果我在实际场景中调用某个方法,我希望我可以获得错误状态或捕获异常。 通常我更喜欢异常。 异常错误处理可以通过单元测试轻松测试,不需要反射来破坏封装。

所以我的建议是public:

public static class Logger
{
    private bool bNoError = true;
    public static void Log()
    {
        if (!bNoError)
        {
            //Then execute the logic here
        }
        else
        {
            //else condition logic here
        }
    }

    public static bool IsAnyError()
    {
        return !bNoError;
    }
} 

有时,您可能过于接近要测试的代码,以至于忘记了考虑全局并开始过多地关注实现。你开始思考 "When this variable is set, ...",而不是思考 "When XXX happens, ..."。当您达到这一点时,这表明您可能过于关注实施,并且您 运行 面临创建非常脆弱的测试的重大风险,如果您对实施进行任何更改,这些测试将会中断。

@Martin Noreke 的 post 介绍了如何做您正在尝试做的事情。不过对我来说,感觉就像你在测试错误的东西。看来您要编写的下一个测试是 "Do XXX with Logger and test that bNoError is set to false"

显然它有点依赖于 Logger 的其余部分 class,但感觉也许另一种方法可能是:

Call Logger method XXX, shimming dependencies if necessary in order to trigger error state.
Call Logger.Log and validate expected behaviour

假设 bNoError 有办法重置回 true,那么您可以:

Call Logger method YYY, shimming dependencies if necessary to trigger error cleanup
Call Logger.Log and validate expected behaviour