Тестовый сценарий command и query

Введение

В этом посте  я опишу, как делается тестирование command и query, используя уже готовые средства библиотеки incframework. Для этого мы рассмотрим  основы работы Mock Message, а потом  разберем сценарии тестирования из проекта IncMusicStore, чтобы более детально изучить, как устроено тестирование в incframework.

Mock Message

Mock message – это оболочка для быстрого написания качественных unit test, покрывающая сценарии command и query.Основная идея при написании mock message заключается в том, чтобы убрать детали настройки тестовой среды, которую приходилось повторять от тестового класса  к тестовому классу, что увеличивало объем кода  и наводило лишние “шумы”, мешает фокусироваться на конкретном задании.Для того, чтобы понять mock message, можно посмотреть на иерархию CommandBase и QueryBase, показанную на схеме.

messageClassDiagram

На схеме видно, что CommandBase и QueryBase являются по сути одним и тем же объектом все поля  которых содержатся в MessageBase из чего следует :

  • Сommand и query идентичны, разница только в работе с транзакцией ( command всегда делает commit, а query не делает )
  • Command также может  иметь Result, но это крайне редкий случай. В качестве примера, когда потребуется вернуть Result, при добавлении новой записи в базу данных, сразу получаем ее Id.

Рассмотрим подробней, как работает mock message ( пример показывает работу с command, но это справедливо и  для query )  на схеме.

mockCommand

Схема показывает, то что mock работает всегда с Repository и Event Broker используя mockup, а не реальные реализации, что позволяет тестировать вне контекста базы данных ( на основе личного опыта было выявлено, что тестирование с базой данных, необходимо только для проверки persistence объектов ) и подписчиков. Чтобы узнать, как это реализовано, можно посмотреть следующий код, взятый из исходников framework.

После изучения концептуальных основ mock command ( далее в тексте везде используется command, но для query это тоже будет работать ) рассмотрим основные методы и поля:

  • When – статический метод,  который  принимает экземпляр command.
  • Original – поле предоставляющие доступ к оригинальной Command.
  • StubGetById – метод позволяет делать stub  вызовов GetById из Repository .

  • StubQuery – метод позволяет делать stub вызовов Query из Repository.

  • StubPublish – метод устанавливает ожидаемые publish event

           примечание: сравнение по умолчанию event будет через  ShouldEqualWeak, что гарантирует проверку всех полей, но так же можно указать ожидаемый тип Event и воспользоваться action для более тонкой настройки.

  • ShouldBeSave – метод провереят был ли вызван save из repository.

     примечание: я предпочитаю сравнивать сущность с оригинальным command, потому что чаще всего поля command являются зеркальным отражением сохраняемой сущности (AddAlbum , AddProduct и т.д. ) , тем самым мы получаем постоянный контроль над добавлением новых полей, как в entity так и в command. Из личного опыта замечу, что с таким подходом можно избежать необходимости делать зависимости в  ctor для сущностей ( конечно же это надо смотреть в контексте задачи , иной раз может стоит и применить Builder ), а использовать более читабельный способ инициализацию.
    примечание: метод так же может принимать вторым параметром callCount ( по умолчанию = 0 ), что позволяет проверять случаи когда сохранение идет в цикле
  • ShouldNotBeSave – метод проверяет что не был вызван save у Repository

   примечание: метод является именованной версией ShouldBeSave с callCount = 0

  • ShouldBeIsResult – метод сравнивает поле Result с входящим аргументов

    примечание: сравнение будет с помощью ShouldEqualWeak
  • ShouldBeIsResult – метод вызывает Invoke у Action передавая Result в качестве параметра

В классе Mock Message есть ещё несколько методов, но они все являются перегрузками или именованными аналогами выше описанных методов. Теперь мы можем приступить к изучения mock message на основе реальных задач. Я постарался подобрать набор задач, которые покрывали бы большинство реальных сценариев встречающихся программистам в повседневной практике.

Изучение решения задач будет построено следующим образом :

  1. Пример кода
  2. Пример теста ( если код имеет более этого завершения, то тестов будет столько же )
  3. Review  с обзором Establish, Because , It ( для тех кто не знаком с MSpec могут посетить их сайт)

 Задачи

Задача 1: Sing in user

Описание: эта задача была выбрана, потому что тут можно увидеть несколько путей выполнения command, а так же демонстрируется работа с  EventBroker.

примечание: после  изучения кода видно, что алгоритм имеет два пути и поэтому тестов тоже должно быть два:

  1. Проверить ветку когда User не найден.
  2. Проверить ветку когда publish SingInUserEvent был вызван.

Тест 1

Review

  • Establish : создание объект command используя Invent, что гарантирует нам уникальность всех полей и новый экземпляр mockCommand.
  • Becasue: вызов Execute в рамках метода Catch.Exception для того что бы не остановилось выполнения приложения в случае ожидаемой ошибки. Полученный объект ошибки сохраняем в переменную.
  • It should be exception: проверяем то что ошибка была и поля проставлены так как и ожидалось.
    замечание: проверять, то что Publish SignInUserEvent не был вызван, не нужно потому, что мы его не указывали в establish и по этому если он по каким то причинам сработает, то тест провалится.

Тест 2

 Review

  • Establish – создание command, создание user и настройки StubQuery (указываем ожидаемые whereSpecification и entities которые должны вернутся ), StubPublish ( с помощью action проверяем на соответствие ожидаемого event )
  • Because – вызов Execute
  • Should be published  – проверяем, что все ожидаемые StubPublished были вызваны.

Задача 2: Add album

Описание: эта задача показывает StubGetById и проверку ShouldBeSave

 Тест

Задача 3: Search albums

Описание: эта задача показывает StubQuery и проверку ShouldBeIsResult

 Тест

примечание: я придерживаюсь одинакового именования во всех тестах , например поле expected , я не использую конкретные название ( albums, users и т.д. ), потому что тесты постоянно меняются вместе с задачами проекта ( а как показывает практика, они меняются очень часто )

 Review

  • Establish – создание query, создание коллекции album и настройки StubQuery (указываем ожидаемые whereSpecification ,fetchSpecification и entities)
  • Because – вызов Execute
  • Should be Result– проверяем Result, сравнивая его с созданным в Establish
    примечание: в нашем случае It очень простой, но если мы вернем из query объект List, то в тесте нам надо будет проверить правильность преобразования entity в view model.  Я предпочитаю логику преобразования entity в view model содержать в Controller ( и там ее и проверять ) , но для уменьшения сложности Fetch можно  построить VM на уровне query.

Заключение

Мы рассмотрели не самые простые задачи, но решение их было не сложными, а что главное унифицированным, что позволяет начать практиковать тестирование даже не опытным программистам.По началу может показаться не много сложным, но только по причине не знания синтаксиса и особенностей incoding framework, по этому для более простого запоминания, были разработаны live template для resharper, который можно скачать вот тут.

Vlad Kopachinsky

I am a Senior developer at Incoding Software in Russian Federation. I created the Incoding Framework for rapid development

More Posts - Website - Twitter - Facebook - LinkedIn

Leave a Reply