Предыдущий ролик Следующий ролик  

Видео урок: Practicing test-driven development

Основы ASP.NET MVC 5

В этом видео в рамках создания важной отсутствующей составляющей нашей системы банкоматов я познакомлю вас с методом разработки через тестирование. Базовая разработка через тестирование, или TDD, начинается с создания теста, который завершается неудачей. А завершается он неудачно потому, что мы еще не написали код. Еще нам хочется иметь возможность отслеживать тот момент, когда тест перестает быть неудачным и выполняется успешно, поскольку это указывает на то, что мы что-то совершили. Затем мы выполняем этот тест самым простейшим из возможных способов, чтобы получить базовую структуру теста и убедиться в корректности нашего оператора проверки.

Наконец, мы выполняем рефакторинг кода, чтобы более изящно или эффективно решить более общую проблему и убедиться в том, что тест все равно выполняется успешно. Этот процесс носит название "Red, Green, Refactor" (красный, зеленый, рефакторинг). Давайте продолжим работу над тестом, который мы написали в предыдущем видео и который проверяет корректность баланса после внесения средств на счет. Следующее, что нам нужно сделать, - установить свойство fakeDb.Transactions равным экземпляру вымышленной совокупности операций. Далее объявляем новый экземпляр transactionController и передаем в его конструктор fakeDb.

Опять же, это позволит нам протестировать действие Deposit, не задействуя реальную базу данных. Наконец, вызываем действие Deposit, передавая в него новую операцию. Убедитесь, что свойство CheckingAccountId имеет такое же значение, которое мы использовали для свойства Id при создании текущего счета. Я перегруппирую эти операторы немного по-другому. Самым первым идет оператор подготовки. Оператор выполнения - это реальная операция внесения средств на счет. А на этапе проверки можно попробовать узнать, равен ли текущий баланс счета 25.

Перед тем как запустить тест, нам нужно добавить ссылку на Entity Framework, чтобы этот проект мог корректно преобразовать эти fakeDbSet в IDbSet. Итак, получим эту ссылку с помощью пакетов NuGet. Нажмем правой кнопкой мыши на папке References, выберем пункт "Управление пакетами NuGet", затем в левой панели перейдем в раздел "В сети" (Online) и установим пакет EntityFramework. Теперь просто нажмем правую кнопку мыши и выполним тесты. Итак, мы получили неудачный тест, поскольку еще не написали код, который будет обновлять баланс счета.

Но не забывайте, что перед тем как написать такой код, нужно сделать так, чтобы тест выполнялся успешно, внеся для этого самые простые изменения. В крайнем случае один из вариантов - просто поменять checkingAccount.Balance на 25. Скорее всего, это не сильно нам поможет. Чтобы тест стал успешным, можно оставить этот оператор проверки и задать здесь checkingAccount.Balance равным 25. Опять же, это не сильно повлияет на наш тест, но в более сложном примере этот шаг поможет нам понять, валиден ли наш оператор проверки.

Вернемся теперь к окну Обозревателя тестов и выберем опцию "Выполнить неудачные тесты". Как видите, этот тест выполняется успешно, но пока у нас все равно нет реального решения. Рассмотрим наш TransactionController. Мы хотим, чтобы сразу после выполнения этой операции обновлялся баланс соответствующего текущего счета. Здесь хорошо бы использовать CheckingAccountService. Добавим метод под названием UpdateBalance, который в качестве параметра принимает CheckingAccountId.

Сначала получим рассматриваемый текущий счет. Поскольку fakeDbSet действительно не реализует метод Find, я воспользуюсь методом Where и возьму первый полученный результат. Я хочу, чтобы свойство Id текущего счета было равно переданному CheckingAccountId. Затем берем первый из полученных результатов. После этого обновляем баланс счета. Устанавливаем его равным сумме всех операций, связанных с этим CheckingAccountId.

Так можно сделать, поскольку, если вы помните, операции внесения и списания средств обозначаются одинаково, но только со знаком + или -. Опять же, отфильтруем значения с помощью метода Where, а затем применим метод расширения Sum для Amount. Я не буду использовать свойство Navigation для упрощения кода, поскольку для fakeDbSet это свойство не задано. Не забудьте вызвать метод db.SaveChanges. На данный момент CheckingAccountService не использует IApplicationDbContext, поэтому давайте это исправим и сделаем так, что конструктор будет принимать его в качестве параметра.

Вернемся к контроллеру TransactionController. Можно создать новый сервис и вызвать метод UpdateBalance. Передадим в него CheckingAccountId этой операции. Не забудьте указать, какую базу данных мы передаем в конструктор CheckingAccountService - реальную или вымышленную. Вернувшись к тесту, удалим этот жестко-закодированный баланс и снова выполним тест, чтобы проверить, выполнится ли он удачно после рефакторинга.

Итак, выбираем опцию "Повторить последний запуск" (Repeat Last Run). Можно также выбрать опцию "Выполнить все". Как видите, все тесты выполнились успешно. Перед тем как завершить это видео, хочется сказать еще пару слов о нашем подходе в целом. Во-первых, если он кажется вам сложным, утомительным или в каком-то смысле ограниченным, то это абсолютно понятно. Мы только что выполнили большую работу вручную, чтобы продемонстрировать основы разработки через тестирование. Но существует множество инструментов и фреймворков, которые могут упростить этот процесс. Есть фреймворки, с помощью которых можно имитировать объекты, например, Rhino Mocks или Moq (пишется M-O-Q). Они помогают создавать mock-реализации зависимостей.

Есть еще такие DI-контейнеры, как Castle Windsor или Ninject, которые могут следить за тем, чтобы созданные экземпляры ваших контроллеров имели корректный тип DbContext и другие зависимости. Кроме того, они облегчают процесс переключения между вымышленными и реальными версиями объектов в вашем приложении. Именно так выглядит базовый процесс разработки через тестирование. Я думаю, что если вы начнете практиковаться в этом виде тестирования, вы осознаете, что оно действительно помогает решать многие проблемы и принимать взвешенные решения. И опять же, весь разработанный в этой главе код можно найти в файлах упражнений, расположенных в папке Chapter 6 -> Final.