Работа над TinRoll еще продолжается. В этой статье мы собираемся установить несколько архитектурных шаблонов серверной части. Сегодня мы собираемся сделать две основные вещи:

  1. Создайте проект модульного тестирования и используйте модульные тесты в нашей разработке
  2. Создайте слой логики в нашем приложении, который содержит логику нашего приложения.

Есть много дел, так что давайте приступим.

Статьи о TinRoll

Эта статья - одна из многих, в которых описывается мой простой клон stackoverflow под названием TinRoll. Другие статьи из этой серии:

Начало работы, часть 1

Начало работы, часть 2

Код можно найти в Github project.

Я создал тег git под названием blog / unitTests, который содержит код, специфичный для этого сообщения в блоге. Убедитесь, что вы просматриваете этот тег, если пытаетесь следить за ним.

Новые проекты

В прошлый раз, когда мы покинули наш проект, мы создали страницы для просмотра всех вопросов, создания нового вопроса и просмотра одного вопроса.

Сегодня давайте начнем с добавления двух новых проектов. Нам нужно добавить библиотеку классов под названием TinRoll.Logic и тестовый проект xUnit под названием TinRoll.Test.

Если вы помните из предыдущих статей, наши контроллеры напрямую обращались к нашему TinRollContext.

Довольно плохой дизайн. Сегодня мы собираемся настроить репозиторий, который обрабатывает взаимодействие с нашей базой данных. Также менеджер, который будет содержать всю нашу логику и данные процесса от контроллера и уровня данных. Мы также создадим преобразователи, которые будут обрабатывать данные преобразования нашего Dto в наши классы сущностей Db. Начнем с картографов.

Код сопоставления

Давайте сначала рассмотрим наш код отображения. Я собираюсь использовать модульные тесты, чтобы помочь в моем развитии. Использование юнит-тестов является основой Test Driven Development. В последнее время я все чаще использую модульные тесты и обнаружил в них много преимуществ.

Что мы надеемся, что наши модульные тесты помогут нам в проверке? Для нашего кода сопоставления мы можем правильно сопоставить наши классы Dto и Db. Мы продемонстрируем это с помощью нашего объекта User.

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

Итак, в нашем тестовом проекте создайте новую папку с именем Mapper и в ней создайте класс с именем UserMapperTests.

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

Прямо сейчас этот код не создается, потому что UserMapper не существует. Итак, у нас есть неудачный модульный тест, который мы пытаемся применить.

В нашем проекте Logic добавьте папку Mapper и в ней создайте класс UserMapper. В этом классе мы напишем простой Mapper из нашего DbEntity в наш Dto.

Вернитесь к нашему модульному тесту, убедитесь, что наша ссылка на UserMapper верна и теперь модульный тест должен пройти. Поздравляю!

Давайте сделаем нечто подобное для метода ToDb. Модульный тест:

Затем напишите еще один простой картограф для сопоставления этих двух объектов.

Отлично, теперь у нас есть сопоставления наших объектов User. Теперь мы создадим наш первый класс репозитория.

Репозиторий

Шаблон репозитория помогает нам абстрагироваться от взаимодействия с базой данных. В нашем проекте данных создайте новую папку под названием Repository. Поскольку мы хотим использовать внедрение зависимостей с нашим репозиторием, также создайте папку с именем Interface в нашей папке Repository. В нашей папке Interface создайте интерфейс с именем IUserRepository. И определите следующие методы:

Теперь, когда у нас есть интерфейс, давайте вернемся к нашему UserRepositry, унаследуем интерфейс, передадим TinRollContext в конструктор и предоставим методы, которые генерируют NotImplementedExceptiosn.

А теперь перейдем к нашим модульным тестам. В нашем тестовом проекте создайте новую папку под названием Repository, внутри которой создайте класс под названием UserRepositoryTests. Нашим первым модульным тестом будет тестирование метода CreateUser. Мы собираемся использовать EF Core в базе данных памяти для тестирования нашего TinDbContext.

Если мы запустим этот модульный тест, он потерпит неудачу, потому что он выбрасывает NotImplementedException. Давайте напишем наш UserRepository, чтобы пройти тест.

Это оказалось довольно просто,

Просто добавьте объект в магазин, вызовите save и вернитесь. Запустите модульный тест еще раз, и вы увидите зеленый цвет!

Получить пользователя

Теперь давайте напишем тест для нашей конечной точки GetUserRepository. Этот модульный тест будет очень похож на Create User, просто вызывается другой метод.

Если мы запустим этот модульный тест, мы обнаружим «Не реализованное исключение» в репозитории. Итак, у нас есть неудачный модульный тест, теперь давайте его реализуем.

Это супер-тривиально, просто верните нужный объект. Запустите модульный тест еще раз, чтобы убедиться, что он прошел успешно и все в порядке!

Тесты логического уровня

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

В нашем проекте TinRoll.Logic создайте папку с именем Manager, в этой папке создайте еще одну папку с именем Interface.

В папке Interface создайте файл с именем IUserManager. Наши классы Manager будут находиться между нашими контроллерами и нашими репозиториями. Менеджеры будут содержать большую часть нашей бизнес-логики, а также любые преобразования, которые потребуются нашему слою пользовательского интерфейса. Наш первый UserManager будет очень тонким, в основном он будет просто вызывать нашу базу данных с отображением объектов до и после каждого вызова. По мере того, как наше приложение растет и требуется больше бизнес-логики, наши менеджеры начнут делать больше вещей.

Для нашей сущности User нам нужен способ связи из нашего пользовательского интерфейса и нашего уровня данных.

Вместе с нашими менеджерами мы наконец удаляем классы сущностей нашей базы данных из нашего контроллера. Как видите, типы ввода и возврата из Manager - это наш класс UserDto.

В папке Manager создайте класс с именем UserManager, пусть он реализует интерфейс IUserManager с генерируемыми исключениями NotImplementedExceptions.

Тесты менеджера

Теперь, когда у нас есть наш класс, давайте создадим модульные тесты, прежде чем реализовывать наши методы UserManager. Этот модульный тест на самом деле станет интересным, потому что нам придется иметь дело с нашим интерфейсом IUserRepository.

Когда это приложение запущено, этот интерфейс будет содержать реальный UserRepository и взаимодействовать с базой данных. Мы могли бы передать этот класс, чтобы он разговаривал с базой данных в памяти. Но при написании модульных тестов очень важно свести к минимуму то, что вы тестируете. Модульные тесты предназначены для тестирования отдельных фрагментов кода, а не того, как код взаимодействует между несколькими уровнями вашего приложения.

Кроме того, мы уже написали модульные тесты для репозитория. Поэтому мы не хотим снова тестировать эту функцию.

Чтобы протестировать наш менеджер, не имея дела с нашим репозиторием, мы собираемся имитировать наш репозиторий. Мокинг позволяет нам детально определить, что в нашем коде тестируется. Все, что мы решаем не тестировать (в данном случае наш репозиторий), мы имитируем, чтобы убедиться, что оно ведет себя должным образом, и убираем эту сложность из нашего теста.

Мы будем использовать библиотеку Moq, чтобы имитировать наш класс. Добавьте ссылку на пакет Moq nuget. Затем вернитесь к нашему модульному тесту и создайте наш тест.

Позвольте мне рассказать вам, что мы делаем. При использовании Moq и mock мы создаем, а затем используем mock-объекты в наших тестах. Нам нужно создать объекты и методы, которые нужны нашим модульным тестам для успешного выполнения. В нашем IUserRepository в настоящее время есть три метода, но в этом конкретном модульном тесте нас интересует только метод CreateUserAsync (). Итак, мы создадим имитацию IUserRepository и настроим фиктивный метод для нашего CreateUserAsync (), указав объект, который будет возвращаться при каждом вызове этого метода.

Когда мы создаем UserManager, и вместо передачи реального класса UserRepository мы передаем наш фиктивный объект с определенным фиктивным методом.

Затем мы выполняем свою работу, затем утверждаем свои результаты. Довольно простая концепция для освоения. Когда мы запускаем это, мы получаем исключение, потому что мы еще ничего не реализовали. Итак, давайте сделаем это.

Наша реализация очень проста, мы просто используем Mapper для преобразования типов за нас с вызовом UserRepository посередине.

Если мы снова запустим наш тест, он пройдет!

Затем давайте добавим модульный тест для GetUser по той же схеме.

Затем перейдите в UserManager и реализуйте необходимый функционал.

Последнее, что нам нужно сделать, это обновить наш UserController, чтобы вызвать UserManager вместо прямого использования TinRollContext и EntityFramework напрямую.

Довольно очевидно, мы просто используем класс Manager, чтобы абстрагироваться от сохранения и получения пользователей. Это делает наш контроллер более тонким и радует меня передовым опытом.

Я реализовал нечто очень похожее для наших объектов Question. Но поскольку многое из этого очень похоже, возникает много дублирования кода. Одна из моих следующих задач - создать общие версии репозиториев и менеджеров, чтобы сократить время, которое мне нужно писать и модульное тестирование одного и того же кода.

Заключение

Наш API предпринял значительные шаги для определения шаблонов, которые нам понадобятся в дальнейшем. Мы добавили в наше решение репозитории и менеджеров. Мы также добавили модульное тестирование, которое поможет убедиться в правильности логики нашего приложения.

В будущем нас ждет еще много интересного. Мне действительно нужно реализовать некоторые передовые практики Blazor, создать несколько общих версий объектов, которые мы здесь создали, я также думаю о том, как мне добавить F # в этот проект. Меня всегда это интересовало, и я хотел бы вскоре им воспользоваться. Быть в курсе.

Я Морган Кеньон. Я разработчик .NET, работающий в области DFW. Я считаю C # отличным языком для использования, и он также поддерживает отличную экосистему, я люблю решать сложные проблемы и хочу продолжать говорить о технологиях, которые я использую. Если вы нашли эту статью полезной или наводящей на размышления, оставьте комментарий и давайте подключимся через LinkedIn!

Github Repo