Модульный тест "структура" метода?
Простите за длинный пост...
Будучи знакомым с проектом "коричневого поля", у меня есть сомнения относительно определенных наборов модульных тестов и что думать. Скажем, у вас был класс репозитория, включающий хранимую процедуру и в руководстве разработчика, определенный набор руководящих принципов (правил), описывающих, как этот класс должен быть построен. Класс может выглядеть следующим образом:
public class PersonRepository
{
public PersonCollection FindPersonsByNameAndCity(string personName, string cityName)
{
using (new SomeProfiler("someKey"))
{
var sp = Ioc.Resolve<IPersonStoredProcedure>();
sp.addNameArguement(personName);
sp.addCityArguement(cityName);
return sp.invoke();
}
} }
Теперь я, конечно, написал бы несколько интеграционных тестов, проверяющих, что SP может быть вызван и что поведение соответствует ожидаемому. Тем не менее, я бы написал модульные тесты, которые утверждают, что:
- Вызывается конструктор для SomeProfiler с входным параметром "someKey"
- Конструктор PersonStoredProcedure называется
- Метод addNameArgument в хранимой процедуре вызывается с параметром personName
- Метод addCityArgument в хранимой процедуре вызывается с параметром cityName
- Метод invoke вызывается для хранимой процедуры -
Если это так, я потенциально буду тестировать всю структуру метода, кроме поведения. Моя первоначальная мысль, что это излишне. Тем не менее, что касается практики кодирования, применяемой командой, эти тесты обеспечивают единую и "правильную" структуру, и что следующий уровень вызывается правильно (от DAL до DB, от BLL до DAL и т. Д.).
В моем случае эти типы тестов выполняются для каждого уровня приложения.
Последующий вопрос - использование класса SomeProfiler для меня немного пахнет конвенцией - Вместо того, чтобы создавать для этого явные тесты, можно ли создать тест в стиле конвенции с использованием статического анализа кода или юнит-теста + отражения?
Заранее спасибо.
2 ответа
Я думаю, что ваша первоначальная мысль была правильной - это перебор. Хотя вы можете использовать рефлексию, чтобы убедиться, что у класса есть ожидаемые методы, я не уверен, что вы захотите проверить это таким образом.
Возможно, вместо модульного тестирования вы должны использовать какой-нибудь инструмент, такой как FxCop/StyleCop или nDepend, чтобы убедиться, что все классы в конкретной сборке / dll имеют эти свойства.
Сказав, что я сторонник "только того кода, который вам нужен", зачем тестировать существование метода, либо вы используете его где-то в своем коде, и в этом вы можете протестировать конкретный случай, либо нет - и так не имеет значения.
Модульные тесты должны быть сосредоточены на поведении, а не на реализации. Таким образом, написание теста для проверки того, что определенные аргументы установлены или переданы, не увеличивает ценность вашей стратегии тестирования.
Поскольку представленный пример взаимодействует с вашей базой данных, его нельзя считать "модульным тестом", поскольку он должен взаимодействовать с физическими зависимостями, которые имеют дополнительные настройки и предварительные условия, такие как доступность среды, схема базы данных, существующие данные. хранимые процедуры и т. д. Любой тест, который вы пишете, на самом деле также проверяет эти предварительные условия.
В его нынешнем состоянии лучше всего для этих типов тестов проверять поведение, предоставляемое классом, - вызывать метод в своем хранилище, а затем проверять, что результаты соответствуют ожидаемым. Однако вы внезапно поймете, что здесь скрытая стоимость - база данных поддерживает состояние между запусками теста, и вам потребуется дополнительная логика настройки или разборки, чтобы убедиться, что база данных находится в известном состоянии.
Хотя я понимаю, что целью вопроса было тестирование "черного ящика", кажется очевидным, что в вашем API есть какая-то скрытая магия. Я предпочитаю решать общеизвестную проблему состояния - использовать базу данных в памяти, ориентированную на текущий тест, которая изолирует меня от условий среды и позволяет распараллеливать интеграционные тесты. Держу пари, что при нынешнем дизайне нет "шва" для программного введения конфигурации базы данных, поэтому вы "зажаты". По моему опыту, магия болит.
Тем не менее, небольшое изменение существующего дизайна решает эту проблему, и "волшебство" исчезает:
public class PersonRepository : IPersonRepository
{
private ConnectionManager _mgr;
public PersonRepository(ConnectionManager mgr)
{
_mgr = mgr;
}
public PersonCollection FindPersonsByNameAndCity(string personName, string cityName)
{
using (var p = _mgr.CreateProfiler("somekey"))
{
var sp = new PersonStoredProcedure(p);
sp.addArguement("name", personName);
sp.addArguement("city", cityName);
return sp.invoke();
}
}
}