Как получить доступ к закрытым полям статического класса для модульного тестирования его методов с помощью Microsoft Fakes в C#

У меня есть нижеприведенный статический класс и метод, который мне нужен для модульного тестирования. Я могу Но этот метод имеет условие if, которое использует логическую частную переменную, и если ее значение ложно, то он выполняет шаги в этом условии 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.


person krrishna    schedule 29.05.2015    source источник
comment
Что-то в коде Logger должно инициировать изменение переменной, иначе ее там не будет. Смоделируйте поведение, которое вызовет его, а затем проверьте поведение, если метод журнала правильный. Похоже, это то, что вам действительно будет интересно в любом случае.   -  person forsvarir    schedule 30.05.2015


Ответы (5)


Для целей UnitTesting Microsoft реализовала несколько вспомогательных классов (PrivateType и PrivateObject), которые используют отражение для подобных сценариев.

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

PrivateType предназначен для статического доступа, тогда как PrivateObject вместо этого предназначен для тестирования созданных объектов.

Вам потребуется включить пространство имен Microsoft.VisualStudio.TestTools.UnitTesting из Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll, чтобы использовать их.

person Martin Noreke    schedule 30.05.2015

Вы можете использовать Reflection, чтобы сделать это для целей тестирования. Хотя это совсем вне закона.

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);
person vendettamit    schedule 29.05.2015
comment
Я получаю нулевое значение в объекте поля. - person krrishna; 30.05.2015
comment
Образец, который вы привели, имеет статический класс и нестатический частный член, который не разрешен в С#. Проверьте, является ли ваш класс статическим или нет, если нет, вам нужно передать текущий экземпляр объекта, чтобы установить значение во время выполнения. Дайте мне знать. - person vendettamit; 30.05.2015
comment
К сожалению, этот участник также является статическим. Это опечатка от меня. - person krrishna; 31.05.2015

Иногда вы можете настолько приблизиться к коду, который пытаетесь протестировать, что забываете думать об общей картине и начинаете слишком сильно сосредотачиваться на реализации. Вместо того, чтобы думать «Когда происходит XXX, ...», вы начинаете думать «Когда эта переменная установлена, ...». Когда вы дойдете до этого момента, это будет признаком того, что вы, возможно, слишком много внимания уделяете реализации и рискуете создать очень хрупкие тесты, которые сломаются, если вы что-то измените в своей реализации.

В сообщении @Martin Noreke рассказывается, как сделать то, что вы пытаетесь сделать. Однако мне кажется, что вы, возможно, тестируете не ту вещь. Похоже, что следующий тест, который вы собираетесь написать, это «Выполнить XXX с помощью Logger и проверить, что bNoError установлено на false».

Очевидно, это немного зависит от остальной части класса Logger, но кажется, что альтернативный подход может быть таким:

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
person forsvarir    schedule 01.06.2015

Я не рекомендую использовать отражение при модульном тестировании. Сложно поддерживать большой проект с большим количеством разработчиков, потому что его нельзя легко рефакторить, найти.

Кроме того, модульный тест должен проверять некоторое поведение класса. Если в тестируемом классе есть какая-то обработка ошибок, она должна быть доступна не только юнит-тесту, но и разработчику (вызывающему) в реальном сценарии, а не скрыта инкапсуляцией. Если я вызываю какой-либо метод в реальном сценарии, я ожидаю, что смогу получить статус ошибки или поймать исключение. Вообще я предпочитаю исключения. Обработку ошибок исключений можно легко протестировать с помощью модульного тестирования, при этом не требуется отражения для нарушения инкапсуляции.

Поэтому мое предложение сделать это публичным:

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;
    }
} 
person Tomas Kubes    schedule 30.05.2015
comment
Учитывая, что класс называется Logger, я думаю, что вполне вероятно, что вызывающие абоненты не будут знать, что делать с ошибкой (последнее, что вы хотите сделать, это закрыть ваш веб-сайт из-за необработанной ошибки записи информационного события в файл журнала). Состояние ошибки, вероятно, обрабатывается самим регистратором (возможно, путем закрытия и открытия другого файла журнала или записи в журнал событий и т. д.) - person forsvarir; 30.05.2015

Что ж, один способ — добавить метод в исходный класс, а затем скрыть его:

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

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

Затем в вашем тесте:

ShimLogger.SetbNoErrorBool = flag => bNoError = flag;
person DrewJordan    schedule 29.05.2015