В последнее время я много размышлял о наилучшем способе "Mock" статического метода, который вызывается из класса, который я пытаюсь проверить. Возьмите следующий код, например:
using (FileStream fStream = File.Create(@"C:\test.txt"))
{
string text = MyUtilities.GetFormattedText("hello world");
MyUtilities.WriteTextToFile(text, fStream);
}
Я понимаю, что это довольно плохой пример, но он имеет три вызова статических методов, которые все разные. Функция File.Create осуществляет доступ к файловой системе, и я не владею этой функцией. MyUtilities.GetFormattedText - это функция, которой я владею, и она не имеет апатридов. Наконец, MyUtilities.WriteTextToFile - это функция, которой я владею, и она обращается к файловой системе.
В последнее время я обдумывал, является ли это устаревшим кодом, как я могу его реорганизовать, чтобы сделать его более подверженным тестированию. Я слышал несколько аргументов о том, что статические функции не должны использоваться, потому что их трудно проверить. Я не согласен с этой идеей, потому что статические функции полезны, и я не думаю, что полезный инструмент следует отбрасывать только потому, что используемая среда тестирования не может справиться с этим очень хорошо.
После долгих поисков и обсуждений я пришел к выводу, что в основном существуют 4 шаблона или практики, которые могут использоваться для создания функций, которые вызывают статические функции, которые могут быть подвергнуты тестированию. К ним относятся следующие:
- Не выполняйте статическую функцию вообще и просто позвоните unit test.
- Оберните статический метод в классе экземпляра, который реализует интерфейс с функцией, которая вам нужна на нем, а затем используйте инъекцию зависимостей, чтобы использовать ее в своем классе. Я буду называть это как инъекции зависимостей интерфейса.
- Используйте Moles (или TypeMock), чтобы захватить вызов функции.
- Используйте функцию dependeny для функции. Я буду называть это как инъекции зависимостей функции.
Я слышал довольно много дискуссий о первых трех практиках, но поскольку я думал о решениях этой проблемы, четвертая идея пришла ко мне из функции зависимой инъекции. Это похоже на скрытие статической функции за интерфейсом, но без фактического создания интерфейса и класса оболочки. Примером этого может быть следующее:
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);
}
}
}
Иногда создание класса интерфейса и оболочки для вызова статической функции может быть громоздким и может загрязнять ваше решение множеством небольших классов, единственной целью которых является вызов статической функции. Я все для написания кода, который легко тестируется, но эта практика, похоже, является обходным решением для плохой среды тестирования.
Как я думал об этих различных решениях, я понял, что все четыре упомянутые выше практики могут применяться в разных ситуациях. Вот что я думаю, это правильные условия для применения вышеуказанных практик:
- Не ставьте статическую функцию, если она чисто апатридирована и не имеет доступа к системным ресурсам (например, файловой системе или базе данных). Конечно, можно аргументировать, что, если к системным ресурсам обращаются, то это все равно вводит состояние в статическую функцию.
- Используйте инъекцию зависимостей интерфейса, если вы используете несколько статических функций, которые можно логически добавить в один интерфейс. Ключевым моментом здесь является использование нескольких статических функций. Я думаю, что в большинстве случаев это не так. Вероятно, в функции будет только одна или две статические функции.
- Используйте Moles, когда вы издеваетесь над внешними библиотеками, такими как библиотеки пользовательского интерфейса или библиотеки баз данных (например, linq to sql). Мое мнение состоит в том, что если Moles (или TypeMock) используется для захвата CLR, чтобы издеваться над вашим собственным кодом, то это показатель того, что для декомпозиции объектов необходимо выполнить рефакторинг.
- Используйте инъекцию зависимостей функции, когда в тестируемом коде имеется небольшое количество вызовов статических функций. Это шаблон, к которому я склоняюсь в большинстве случаев, чтобы проверить функции, вызывающие статические функции в моих собственных классах утилиты.
Это мои мысли, но я был бы очень признателен за некоторые отзывы об этом. Каков наилучший способ проверки кода, в котором вызывается внешняя статическая функция?