Порядок запуска тестов или как правильно спроектировать тесты

Здравствуйте

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

Например: На проекте имеется сущность “Project”, внутри это сущности можно так же добавлять другие сущности, и делать разные проверки на каждом уровне сущностей. Так вот сейчас у меня в каждом тесте реализовано так что в прекондишенах (Before*) создается и добавляется необходимая сущность, а в самом тесте выполняется уже какая то проверка, т.е до запуска самого теста я привожу приложение в состояние при котором тест выполнить свою проверку, а после самого теста в посткиндишенах выполняются операции по удалению это сущности дабы не накапливать их при каждом прогоне. В этом примере конкретно присутстсвует зависимость между тестами, так как без созданной сущности Project мы не сможем выполнять какие либо проверки. Какие есть решения в этой ситуации?

  1. Прямая зависимость между тестами типо Test2 depends Test1 (В тесте 1 мы создаем сущность Project и проверяем что она создалась, а во втором тесте мы делаем другие проверки внутри этой сущности)

  2. С использованием прекондишеном Before есть так же зависимость, но она уже не прямая, в прекондишенах мы не проверяем что сущность создалась, а выполняем последовательность шагов для ее создания. Но тут есть минус если например количество тестов будет все больше и больше перед каждым тестом у нас будет создаваться сущность Project, что соответственно увеличивает время прогона тестов

В общем хочу понять общие принципы, когда и как лучше использовать или не исопльзовать зависимости в тестах?

проектировать нужно так, чтобы зависимостей не было - это самая правильная позиция

2 лайка

Да в какой то степени я согласен, что от зависимостей нужно уходить, но с другой стороны правильная ли другая сторона, когда в каждом тесте перед выполнением тестов, будет проводиться какие то манипуляции по созданию и добавлению сущности (для создания нужно выполнить ряд действий по заполнению данных (полей) валидными значениями)?

Как и написали выше, убирайте зависимости. Каждый тест должен создавать для себя условия. Скажем если вам нужно проверить логин на веб страницу, то вы должны через взаимодействие с БД на прямую создать их.

Да, так правильно. Заполнение проводи через фабрики или вообще через SQL напрямую, так будет намного быстрее и тесты будут стабильнее.
А на TearDown удаляй созданные сущности: как те, что были созданы на Before, так и те, что были созданы в результате прогона теста.
Есть ещё 1 подход: на BeforeTest пересоздавать чистую базу из бекапа. Если это не накладно по времени - лучше использовать данный вариант. Это позволяет избежать гемора на TearDown, когда тест не прошёл или прошёл частично и сущности, созданные / изменённые в результате прогона теста или не существуют, или повреждены и их удаление сопряжено с трудностями.

2 лайка

Спасибо за совет, есть несколько но, на проекте для того чтобы можно было использоваться функционал необходимы обязательные критерии, такие например как Account (не аккаунт пользовательский), это скорее специфика на уровне компаний, у каждой компании есть аккаунт, создается он напрямую через БД со всякими добавлением зависимостей (это если говорить про чистый дамп БД) наверное это основная сущность на сайте. Так же в аккаунтах должны присутствовать Пользователи (У них есть разделение по ролям) в соответствии с ролью пользователя ему доступен или недоступен какой-либо функционал (взависимости от роли) т.е эти данные уже как минимум должны быть занесены Руками в БД на момент прогонки тестов.

А как такой вариант использовать такой вариант как полу-чистый дамп, иметь необходимый набор данные такие как Account и хотя бы одного пользователя, для добавления сущностей с помощью тестов дергать методы API перед выполнение тестов, т.е например отправлять POST на создание сущности, а в тесте уже использовать эту созданную сущность. Здесь я вижу два преимущества, если после отправки нам возвращается 40* ошибки, это значит что сущность не создалась, а сам тест игнориться так как для выполнения ему нужна эта сущность??

1 лайк

Используйте БД напрямую. Это быстрее и надёжнее чем API. API теоретически можно использовать, но только в очень редких специфических случаях, когда создание сущности через БД очень и очень геморойно. Самый идеальный вариант это завернуть БД в докер контейнер. Перед тестом его поднимаете, наливаете туда нужные сущности и гоняете тесты. В тирдауне просто тушите контейнер. Это проще чем после теста удалять сущности. Если с докером не по пути по каким либо причинам, советую очищать БД в сетапе теста т.к. в таком случае будет меньше зависимостей между тестами. Если на тирдауне что-то пойдёт не так, то последующие тесты могут огрести проблем. Очищая БД в сетапе вы будете уверены в том что тест пойдёт по нужному пути и с нужными сущностями.

5 лайков

Вы предлагаете использовать чистый дамб БД, который будет подниматься и находиться где-то в докере, на момент прогонки тестовых сьютов, конфигурировать и добавлять в БД все сущности необходимые для этих тестов? Или же для каждого теста по отдельности добавлять ту сущность, которая для него необходима???

Есть 2 варианта:
1). Создаём чистую базу затем добавляем все прекондишены на Before.
2). Имеем бекап базы с готовыми Account, User и что там ещё нужно и восстанавливаемся из него.
2-й вариант быстрее, но имеет большую проблему: в случае изменений прекондишенов (например, нужно не 1 пользователь, а 2 или что-то в их ролях поменялось или ещё что-нить произошло) надо готовить новый чистый бекап + при падении тестов становится менее очевидным, что именно произошло, поскольку часть настроек скрыты в бекапе.
Я бы использовал 2-й вариант только в случае 100% уверенности, что прекондишены меняться не будут в течение длительного времени.

1 лайк

А если например банковское ПО, где база может весить несколько терабайт?
И тестовые данные прямыми запросами просто очень геморройно создавать, слишком много зависимостей?

О прямых зависимостях между тестами: однозначно избегать.
О зависимости тестов от пре-кондишенов - минимизировать вероятность отказа сетап-методов. Как тут уже было сказано, АПИ - наименее стабильный вариант и оптимальнее всего создавать требуемые сущности взаимодействуя с базой напрямую из кода. Еще более стабильный вариант - это любой способ создания нужных записей в базе еще до проведения тестов. Я не сторонник такого варианта так как он, имхо, очень не гибкий. Конечно, при определенных ухищрениях этот метод позволит вообще избежать необходимости в пре-кондишенах, но стоимость изменения старых и написания/отладки новых тестов при таком подходе будет сравнительно высока.
Тем не менее, любой из вариантов выше имеет право на жизнь. Приведу свое видение возможных вариантов (список неполный, пишу что первое в голову приходит):

  1. Создание через АПИ - если создание через базу очень сложное (множество зависимостей) или вообще невозможно (нет доступа)
  2. Создание через взаимодействие с базой из кода - если не требуется создания большого количества данных
  3. Создание перед тестами - если нужно большое количество данных

Мой вариант решения проблемы такой. Для 1-го и 2-го вариантов я создаю интерфейс с нужными мне сигнатурами в контексте требуемой сущности. Например, IProjectSetupHelper с сигнатурами createProject(ProjectDTO dto), createProject(String companyName), removeProject() и т.д. Методы позволяют мне создавать/удалять/модифицировать объект. Для интерфейса создаю разные реализации - через dao (предпочтительный вариант), через api, гибридные, короче, в зависимости от обстоятельств. Затем этот объект использую в тестах в сетап-методах, а иногда и в самих тестовых методах. Такой подход хорош тем что он, во-первых, готов ко многим изменениям без модификации самих тестов, и во-вторых, он сам по себе может быть протестирован.
Для 3-го варианта я использую потокобезопасный реестр, который управляет “выдачей” нужных сущностей под нужды какого-либо теста.
Короче, написал я много, а суть одна: зависимости - это плохо, но их не избежать, так как плата за полный уход от них не оправдает профита.

2 лайка

Спасибо за информацию, все детально и понятно)

Я предлагаю для каждого теста заливать только те данные, которые ему нужны. Если же у вас тесты, скажем так, только на чтение и не аффектят данные в системе, то можете перед сьютом залить пачку данных необходимых для этих тестов.

Да вот в том то и дело что примерно больше 80% всех тестовых покрытий так или иначе проводят какие-то манипуляции с данными или сущностями. Да можно было бы перед каждым запуском добавлять весь набор данных которые будут задействованы самими тестами, но здесь появляется но как отслеживать состояние которое необходимо для конкретного теста, если есть допустим 100 тестов которые взаимодействуют с одной сущностью, и в какой-то момент тест меняется состояние этой сущности, впоследствии следущий тест не выполняется из за измененного состояния. Да можно добавить чтобы после выполнения теста, сущность возвращалась к дефолтному состоянию, только чем этот вариант лучше нежели перед тестом добавлять новую сущностью, и не думать в каком она состоянии и нужно ли ее аффектить и возвращать с исходное.

Вы используете свой ORM или тот что сделала разработка?

Ну тут достаточно все просто. В большинстве случаев тесты не аффектящие данные в БД, это тесты на простое получение данных, будь то API на получение ФИО из БД (только в том случае если при запросе эта сущность не генерируется кодом) и тесты на проверку содержательности WEB страниц, тобишь текст, наличие элементов, контент в выпадающих списках т.п. Для всех остальных случаев готовьте данные для каждого теста свои.
P.S.: Да это больно делать кучу данных, но такова плата за изолированность тестов и их надежность.

Да все таки хочется придти к какому-то конкретному решению и подходу, либо для каждого теста в коде создавать свои данные (либо через API, либо напрямую в БД) и только тест знает что он может работать с этими данными, либо делать набор данных для всех тестов, и тогда все тесты будут работать с этими данными?

у меня была похожая ситуация. нужно было выполнять некоторые действия с папкой или с юзером.
я выстроил цепочку тестов так, чтобы сначала создавать папку, делать с ней что-то, а потом удалять.
в любом случае, мне нужно было тестить отдельно создание и удаление папки, и операции над ней. подобным подходом я убил двух зайцев.
подумайте, может у вас возможно сделать тоже самое

Как и писал смотрите в сторону для каждого теста свои независимые данные (через API я бы не советовал все же, только в крайнем случае). Для тестов только на чтение можете и извернуться и подготовить единый пак данных. Как и писал выше, таких тестов не много и в большинстве случаев они не высокого приоритета, только если у вас от красоты и корректности текста на UI не зависит доход компании :slight_smile: