Шаблоны или практики для методов модульного тестирования, которые вызывают статический метод

В последнее время я много размышлял о том, как лучше всего "имитировать" статический метод, который вызывается из класса, который я пытаюсь протестировать. следующий код, например:

using (FileStream fStream = File.Create(@"C:\test.txt"))
{
    string text = MyUtilities.GetFormattedText("hello world");
    MyUtilities.WriteTextToFile(text, fStream);
}

Я понимаю, что это довольно плохой пример, но он имеет три вызова статических методов, которые все немного отличаются: Файл. Создать функцию доступа к файловой системе, и я не владею этой функцией. MyUtilities.GetFormattedText - это принадлежащая мне функция, которая не имеет состояния. Наконец, MyUtilities.WriteTextToFile - это функция, которой я владею, и она обращается к файловой системе.

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

После долгих поисков и размышлений, Я пришел к выводу, что есть в основном 4 шаблона или практики , которые можно использовать, чтобы сделать функции, вызывающие статические функции, тестируемыми по модулю. К ним относятся следующие:

  1. Не имитируйте статическую функцию, просто позвольте модульному тесту вызвать ее.
  2. Оберните статический метод в класс экземпляра, который реализует интерфейс с нужной вам функцией. на нем, а затем используйте внедрение зависимостей, чтобы использовать его в своем классе. Я буду называть это внедрением зависимостей интерфейса .
  3. Используйте Moles (или TypeMock) для перехвата вызова функции.
  4. Используйте внедрение зависимостей для функции. Я буду называть это инъекцией зависимости функции .

Я слышал довольно много дискуссий о первых трех практиках, но когда я размышлял о решениях этой проблемы, мне пришла в голову четвертая идея внедрения зависимостей функций . Это похоже на скрытие статической функции за интерфейсом, но без необходимости создавать интерфейс и класс-оболочку. Примером этого может быть следующее:

public class MyInstanceClass
{
    private Action<string, FileStream> writeFunction = delegate { };

    public MyInstanceClass(Action<string, FileStream> functionDependency)
    {
        writeFunction = functionDependency;
    }

    public void DoSomething2()
    {
        using (FileStream fStream = File.Create(@"C:\test.txt"))
        {
            string text = MyUtilities.GetFormattedText("hello world");
            writeFunction(text, fStream);
        }
    }
}

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

Когда я думал об этих различных решениях, я пришел к пониманию, что все 4 практики, упомянутые выше могут применяться в разных ситуациях. Вот что я думаю, это правильные варианты применения описанных выше методов :

  1. Не имитируйте статическую функцию, если она не имеет состояния и не имеет доступа к системным ресурсам (например, файловая система или база данных). Конечно, можно привести аргумент, что если к системным ресурсам осуществляется доступ, это все равно вводит состояние в статическую функцию.
  2. Используйте внедрение зависимостей интерфейса , когда есть несколько статических функций, которые вы используете, которые могут все логически должно быть добавлено в единый интерфейс. Ключевым моментом здесь является то, что используется несколько статических функций. Я думаю, что в большинстве случаев это не так. Вероятно, в функции будут вызываться только одна или две статические функции.
  3. Используйте Moles , когда вы создаете макеты внешних библиотек, таких как библиотеки пользовательского интерфейса или библиотеки баз данных (например, linq to sql). Я считаю, что если Moles (или TypeMock) используется для взлома CLR с целью имитации вашего собственного кода, то это показатель того, что необходимо провести некоторый рефакторинг для разделения объектов.
  4. Используйте зависимость функции инъекция , когда в тестируемом коде есть небольшое количество вызовов статических функций. Это шаблон, к которому я склоняюсь в большинстве случаев, чтобы тестировать функции, вызывающие статические функции в моих собственных служебных классах.

Это мои мысли, но я бы очень признателен за некоторые отзывы по этому поводу. Как лучше всего проверить код, в котором вызывается внешняя статическая функция?

34
задан Jacob Adams 1 April 2011 в 17:36
поделиться