#unit-testing #architecture #domain-driven-design #clean-architecture
Вопрос:
замечание :
- Я использую термин «насмешка» как общий термин, означающий все виды заменителей тестов, включая шпионов, подделки, подделки, заглушки и все остальное.
- При написании моего вопроса я говорил только о «вариантах использования» в чистой архитектуре, но мой вопрос также касается «Доменных служб» в DDD.
Пойдем :
В настоящее время я пытаюсь реализовать принципы DDD и чистой архитектуры в новом проекте. Тем не менее, прошла уже 1 неделя, как я застрял на — — — > Как написать модульный тест для моего варианта использования
Вот в чем моя проблема:
В чистой архитектуре, когда мы создаем вариант использования (или доменную службу в DDD), в большинстве случаев это будет зависеть от определенного количества сущностей остальное (репозиторий, api …)
Чтобы написать модульный тест своего варианта использования, я начинаю с:
— Имитируйте «остальные» зависимости, которые взаимодействуют с внешним миром (репозитории, API …)
— Но далее, что я должен делать с зависимостями сущностей в моем модульном тесте?
Вот решения, о которых я думал :
- Решение 1: Я вводлю поддельные сущности
— Однако, прочитав о лучших практиках модульного тестирования, я понял, что нам следует как можно больше избегать создания насмешек, потому что они «Пахнут кодом», и хороший дизайн должен позволять обходиться без них.
— Действительно, издевательство над моими сущностями подразумевает, что я ослабляю свой тест. Тест будет тесно связан с моими осмеянными сущностями.
— Кроме того, воссоздание структуры моих сущностей кажется мне бессмысленным …
* Если в моем случае использования используется несколько методов сущностей: тогда мне придется воссоздать возвращаемое значение каждого из этих методов.
* Если структура моих сущностей сложна, мне приходится писать сложные подделки, поэтому мой тест теряет большую надежность, и есть больше шансов, что моя подделка ошибочна, а не моя исходная сущность)
* Even worse, if I use a factory, then I will have to make a fake of the factory -> and that fake will have to build a fake entity …
- Solution 2: I don’t mock entities.
— On the other hand, if I do not mock my entities, then I take the way in my opinion into integration tests: testing the interactions between the different entities …
— Also as specified by some mocking supporters: If I don’t mock my dependencies, then even if my tested unit is valid, the test will fail if my dependency causes a bug. This will cause a false alarm signal on my test …
- Solution 3: Refactoring the production code
— By reading several articles some offer solutions to limit the coupling (Isolate the side effects from the rest of the logic, Isolate the logic from I/O, Use pure functions, Dependency injections, …) But even by applying all this, a use case will irremediably need these entities to work … Therefore it is not a solution.
But then how to do? Am I missing something?
How to do a GOOD unit test on a use case (or a service domain in DDD)? : how to manage the entities in a unit test in which the tested unit has entity depenencies ?
Exemple :
Чтобы проиллюстрировать мой вопрос и лучше понять ваши ответы, вот вымышленный пример проблемы:
Представьте, что у меня есть следующая сущность:
class HorseEntity()
constructor(name,age,health, ...)
equipSaddle()
makeShoeing()
checkHealth()
Я хочу создать вариант использования, чтобы добавить лошадь в свою конюшню:
class addHorseUseCase()
execute(requestDto,HorseRepo,HorseEntity, ...)
Это использование идет:
- Создайте сущность лошади
- Проверьте здоровье лошади
- Подковывай лошадь
- Оснастите лошадь седлом
- И добавь его в конюшню.
Когда я создаю свой тест, что я должен делать с зависимостью «Лошадиная сила»?
Краткое изложение моих вопросов:
— Как написать ХОРОШИЙ модульный тест для варианта использования и как обрабатывать зависимости сущностей в моем тесте ?
— В общем, что мне делать с зависимостями сущностей в модульных тестах?
— В этом примере выше, как написать хороший модульный тест для «addHorseUseCase»?
Спасибо вам за ваши будущие ответы.
PS: Я перевожу этот вопрос с французского на английский, если вы не понимаете формулировку одного из моих предложений, не стесняйтесь сообщить мне, чтобы я мог его отредактировать.
Ответ №1:
Вы ищете ответ «Что имеет смысл?», поэтому я могу сказать вам только то, что имеет смысл с моей точки зрения.
Во-первых, вам не нужно имитировать все зависимости или вы имитируете объекты списка строк и массивов в других тестах?
Моя цель-сделать мои модульные тесты как можно более быстрыми и простыми в настройке.
- Тесты будут очень быстрыми, если они будут работать на чистых POJOs. Ваши варианты использования используют сущности, а сущности являются POJO, поэтому ваши тесты вариантов использования будут выполняться быстро.
- Я издеваюсь над репозиториями, предоставляющими сущности, потому что репозитории обычно подключают варианты использования к внешним системам, например, базе данных или службе rest. Это системы, которые делают тесты медленными и сложными в настройке.
Итак, отвечу на ваши вопросы…
Как написать ХОРОШИЙ модульный тест для варианта использования и как обрабатывать зависимости сущностей в моем тесте ?
Используйте решение 2. Я не издеваюсь над сущностями. Я обычно так и делаю.
В общем, что мне следует делать с зависимостями сущностей в модульных тестах?
Вы можете издеваться над ними, но это усложняет ваш код. Поэтому просто используйте их и убедитесь, что это простые объекты.
В приведенном выше примере, как написать хороший модульный тест для «addHorseUseCase»?
class addHorseUseCase()
execute(requestDto,HorseRepo,HorseEntity, ...)
- Создайте
HorseRepo
макет arequestDto
, aHorstEntity
и все, что вам нужно, чтобы назвать прецедентом использования. - Вызовите вариант использования.
- Сделайте утверждения в ответе на вариант использования.
Редактировать
Я также принял решение не издеваться над своими сущностями, чтобы провести простейший модульный тест. Итак, вам не кажется, что вы проводите интеграционный тест?
Это своего рода интеграционное тестирование, поскольку вы тестируете отдельные программные модули, сгруппированные вместе. Но вы не интегрируетесь с внешними системами. Я думаю, речь идет о том, чтобы определить, что для вас значит интеграция.
Итак, вот мои 3 теста на определение:
- единица измерения: изолированный api одного класса или функции.
- компонент: группа классов или функций, которые обслуживают одну проблему без участия внешних систем.
- интеграция: поведение при взаимодействии с внешними системами — их API.
Мое взвешивание этих различных тестов лучше всего можно визуализировать с помощью пирамиды.
/ <-- integration
/--
/ <-- component
/
/--------
/ <-- unit
------------
Как вы можете видеть, я обычно стараюсь сделать как можно больше компонентных и модульных тестов, и я делаю несколько интеграционных тестов по мере необходимости (обязательно). Я делаю это, чтобы достичь своей цели: «Я хочу, чтобы как можно больше тестов выполнялось быстро и их было легко настроить».
Теперь может возникнуть вопрос: «А как насчет локализации сбоя?»
Например, когда вы проводите изолированные тесты варианта использования и уровня сущности, легко увидеть, где произошла ошибка. Когда тесты проверяют оба в сочетании, вы не можете сказать, произошла ли ошибка в сущности или на уровне вариантов использования. Это верно, но у вас также должны быть модульные тесты для сущностей. Если это так, они тоже могут потерпеть неудачу, и в качестве архитектурного следствия (варианты использования зависят от сущностей) у вас есть либо сбои на уровне вариантов использования и сущностей, либо сбои вариантов использования являются результатом сбоев на уровне сущностей. Таким образом, вы должны следовать архитектуре и сначала исправить тесты сущностей, поскольку от них зависят варианты использования. После этого вы увидите, сохранились ли какие-либо сбои в использовании.
Я хочу сказать, что изоляция тестов вариантов использования путем насмешек над сущностями дает вам небольшое преимущество перед тестами компонентов, когда дело доходит до места сбоя, но создает гораздо больше кода для управления. Так что это компромисс.
Комментарии:
1. Спасибо вам за ваш ответ. После того, как продолжу думать об этом. Я также принял решение не издеваться над своими сущностями, чтобы провести простейший модульный тест. Итак, вам не кажется, что вы проводите интеграционный тест?
2. @ThezozolinoL Я попытался ответить на ваш другой вопрос так хорошо, как только мог, но нам еще многое предстоит обсудить по поводу тестирования. 🙂