Комментарии 47
Интеграционный тест с использованием @SpringBootTest нужен как минимум для проверки того, что контекст поднимается и нет никаких ошибок в конфигурации.
И тут уже встает вопрос, а если мы всё равно будем поднимать полный контекст, зачем нам дополнительно ещё поднимать отдельные слои контекста?
Потому что при полноценном интегрционном тесте необходимо создавать настоящие данные, чтобы вся цепочка от данных до API корректно отработала. Вместо того чтобы эмулировать поведение среды, которая окружает тестируемый фунционал, её приходится буквально создавать. Тут можно апелировать, что есть же MockBean и т.д., но при его использовании будет поднят отдельный контекст, что ещё больше увеличит время.
Так в вашем примере со слоями тот же MockBean используется.
Я в своих проектах выработал простое решение - везде применяются SpyBean, и объявляются они один раз в базовом классе. Там где нужно - им можно задать поведение, как мокам. Где не нужно - они будут вести себя как настоящие бины. И контекст при этом будет создан только один раз.
необходимо создавать настоящие данные
Можно взять обезличенные и обфусцированные (затираем персональные данные) с прода, которые будут максимально приближены к реальным данным, с которыми придётся работать во время эксплуатации.
Также возникают следующие вопросы:
насколько данные для unit-тестов адекватны и будут соответствовать тому, с чем придётся столкнуться в проде? Не будет ли ситуации, что тестируем одно, а в реальности будет происходить другое?
как избежать ситуации, когда user story или бизнес фича по частям проходит unit-тесты, но в собранном виде уже не работает (а такое нередко случается)?
любой код меняется в ходе жизненного цикла и меняется часто. Как не тратить кучу времени на адаптацию unit-тестов из-за каждого рефакторинга?
Одним из аргументов избегания интеграционных тестов и писать больше unit-тесты является, что unit-тесты быстрее и требуют меньше времени. Если мы говорим, что что-то быстрее, то надо указывать насколько быстрее? На 10 секунд? Допустим, интеграционный тест занимает 10 секунд (чтобы поднять контекст микросервиса и протестировать), unit-тест занимает 5 секунд, то 20 интеграционных тестов потребуют 200 секунд (на самом деле меньше если не злоупотреблять @DirtiesContext), что, следуя вашему аргументу, дольше 2 интеграционных тестов и 40 unit-тестов, которые займут 220 секунд.
Интеграционные тесты переиспользуют контекст (если не злоупотреблять @DirtiesContext), поэтому у нас новый контекст поднимается не на каждый интеграционный тест, а 1-2 раза. Да, это дольше unit-тестов, но вряд ли 10 секунд стоят того.
Обфусцированные и обезличенные данные обычно все же используются для end to end тестов на отдельном стенде, п не в рамках CI/CD. Для обычного пайплайна сборки асе же более характерно поднятие чистой БД в контейнере, ну или H2 на худой конец.
Теперь к тезисам:
Такая ситуация возможна, но это уже зависит от качества проведенного системного анализа.
Очевидно лучше писать тесты) Вы же можете протестировать не только отдельные классы с логикой, но и компонующий их сервис, исходя из того, что его составляющие работают корректно (вы же их протестировали).
Правильные юнит-тесты исподволь заставляют писать менее связанный код, поэтому рефакторинг не такой болезненный как может показаться. Если все таки больно, возможно вы все таки что-то делаете не так.
Юнит тесты отрабатывают за милисекунды.
Как уже ранее писал, тот же MockBean тоже пачкает контекст.
Согласен - лучше писать тесты, интеграционные тесты :) Когда поднимаем микросервис и тестируем его.
Никто не мешает взять часть данных с прода для тестов микросервиса и в рамках pipeline-а. А по остальным пунктам - всё это делается тем, что поднимается микросервис и тестируется.
Можно не использовать MockBean, поднимаем весь микросервис (postgresql, kafka и всё нужно через testcontainers) один 1 раз, а дальше также прогоняем интеграционные тесты за миллисекунды.
У меня как раз спор был с тимлидом на прошлом проекте. Этот человек тоже считал, что unit-тесты слишком фиксируют имплементацию, и их дорого поддерживать. И поэтому у нас было очень много интеграционных тестов и почти не было unit-тестов. Итого:
Билд занимал минут по 15
Если что-то где-то меняешь, и что-то где-то ломаешь, то вместо того, чтобы получить четкую ошибку, что, где и почему сломалась, получаешь в консоли HTTP 500, и удачи тебе дебажить весь процесс в попытке найти ошибку
Ну и код в проекте был полное говно, что тоже частично следствие отсутствия unit-тестов
Интеграционные тесты должны быть, с этим я не спорю. Но они никоим образом не заменяют unit-тесты, которые тоже должны быть:
Unit-тесты хоть как-то заставляют следить за качеством кода. Если написание теста становится сложным, то это сразу сигнал о том, что код - говно. На хорошо структурированный код с небольшим количеством зависимостей и не перемудренной логикой тест писать легко и приятно
Если на любой чих в коде проходится менять тест, то это тоже сразу сигнал, что где-то что-то не в порядке: либо сам тест хреновый, либо, опять же, код - говно
Unit-тесты сами по себе являются документацией к коду. Ты сразу видишь, что и как должно работать
Хорошее покрытые тестами позволяет рефакторить что угодно и не опасаться сломать какой-то неочевидный случай работы, который не покрыт интеграционным тестом
С помощью unit-тестов я могу заглянуть в любое место выполнения программы и посмотреть, что и как там работает. А также как угодно изменять условия и смотреть на изменения результата. Интеграционные тесты такой возможности не дают в принципе
Ну и вообще в статье правильно все описано: у unit-тестов и интеграционных тестов разные цели, и заменят первые вторыми - это крайне сомнительная затея
Лучше и не скажешь.
Билд можно ускорить, в конце концов можно просто параллелить запуск.
Во-первых, это можно поправить. Во-вторых, это же тест, он независимый и быстрый - запустил в его дебаге и посмотрел
Связи не вижу. Любой плохой код можно юнит тестом покрыть, от этого он лучше автоматически не станет.
А может быть и так - на плохой код написаны тесты и теперь мы не можем этот плохой код переделать в хороший, т.к все тесты просто сломаются и будем вечно жить с плохим кодом. Вообще почему мы тут говорим о написании тестов на код, мы же хотим TDD, сначала тесты писать, а потом код? Перемудренная логика - это требование бизнеса. Ее можно и нужно упрощать, но это не всегда возможно. А обычно в бизнес логике и есть ценность it-продукта.
Либо проект в активной разработке и постоянно обрастает функционалом
Юнит тест отдельного метода - с точки зрения документации кода не много информации приносит. Мы же пишем понятный код с короткими методами, стараемся соблюдать single layer of abstraction и прочее. Чтобы понять, что метод делает должно быть проще сам метод посмотреть. А общую картину юнит тест на отдельный метод - не дает.
Рефакторить отдельный метод? Если рефакторить что - то серьезное, то тут только интеграционные тесты могут регресс поймать, а то и e2e. После рефакторинга у нас половина юнит-тестов будет пытаться несуществующие методы вызывать.
Тут тоже самое - если метод реально сложный - то ок, юнит тест необходим. Но считать, что по юнитам можно понять как работает фича - это странно на мой взгляд. А то будет ситуация, что замок на двери зарыли, а саму дверь захлопнуть забыли. Хотя оба теста на замок и дверь отлично работают. А в реальности фича, это не 2 составные части, а 50.
Ну, допустим, можно ускорить, тут согласен
Я не хочу дебажить тесты. Я хочу, чтобы тест мне ясно сообщал, в чем проблема, и что я сломал. Тесты должны упрощать жизнь, а не привносить в неё новые сложности
Если сначала писать код без тестов вообще, а потом пытаться покрывать уже существующие решения (очевидно, плохие решения) тестами, ничего не меняя, то да, мы все так же останемся с плохим кодом, на который просто еще будут тесты. Если изначально исходить из того, что на код должен быть тест, то сильно более вероятно, что код будет хоть как-то стремиться быть нормальным
Любой тест всегда можно удалить, если он мешает, и это нормально. Задача теста - не быть вечно, а защищать от случайного изменения логики там, где она не должна меняться. Требования бизнеса могут быть запутанными, но, блин, а мы кто тогда? Просто code monkey, которым что сказали, то они и пишут, прям в лоб и без никаких оптимизаций и структуры? Или все-таки у нас есть какие-то знания, умения и квалификации, как и выполнять запутанные требования, и писать при этом нормальный код
Изменение существующий логики должно приводить к падению теста - это его задача. Добавление новой логики не должно проводить к падению тестов, потому что они за новую логику никак не отвечают. Иначе, опять же, где-то наговнокодили: либо в самом коде, либо в тестах
Тест даёт понимание, при каких входных аргументах какие результаты мы получаем. Хорошее покрытие тестами даёт это понимание на каждом уровне, на любой вложенности кода. От самого мелкого метода, который, условно, складывает 2 числа, до сервисной логики, которая вызывает другие сервисы и как-то жонглирует данными
А что плохого в том, чтобы рефакторить отдельные методы? Особенно если в угоду спешке написали что-то, лишь бы работало, а на оптимизации пока что забили. Это как раз более чем валидный кейс. Можно написать за 5 минут логику какого-нибудь полного перебора, и двигаться дальше, а если именно это место начнёт бить по перформансу, то именно это место и изменить. Можно, конечно с самого начала расчехлить свои умения с leetcode, и целый рабочий день фигачить какие-то алгоритмы, но как раз именно это может быть пустой потерей времени, если окажется, что этод метод вызывается раз в день, например
Ну так напиши ещё один тест на всю фичу целиком, в чем проблема-то?
У SpringBoot отдельные аннотации для тестирования отдельных слоёв приложения: сериализации JSON, контроллеров MVC, слоя доступа к данным и т. д. Они используются, чтобы весь контекст спринга не поднимать, если я правильно помню.
Описанный вами подход подойдет далеко не всем проектам. Он может быть уместен для домашнего пет-проекта или коробочного коммерческого продукта, который не планируется поддерживать в будущем — сделал и отдал.
Но для продуктовой разработки он совершенно не подходит. Особенность такой разработки — в постоянных изменениях кодовой базы по мере добавления новых функциональностей. Главной задачей разработчика становится поддержание evolvability — способности системы к изменениям. Это означает, что фича одинаковой сложности на первом и пятом году жизни сервиса будет требовать примерно одинаковых усилий. То есть сервис с годами не превращается в монолитный, неподдерживаемый ком.
Ошибочно ставить во главу угла только скорость выполнения тестов — не менее важна и глубина обратной связи. Интеграционный тест дает полноценное представление о работе приложения. Юнит-тест же сообщает лишь о корректности одного из сотен или тысяч классов и ничего не говорит о работоспособности фичи в целом.
Сильный упор на юнит-тесты ведет к цементированию кода: значимый рефакторинг становится дорогим, потому что изменение дизайна тянет за собой переписывание множества тестов. А если тесты меняются следом за кодом, можно ли им доверять? О хрупкости кода при чрезмерном использовании юнит-тестов много пишет Владимир Хориков в книге Принципы юнит-тестирования.
На мой взгляд, при тестировании микросервисов в продуктовой разработке приоритет следует отдавать интеграционным тестам. Вот хорошая статья на эту тему: https://engineering.atspotify.com/2018/01/testing-of-microservices/
Я отчасти согласен с тем, что большое количество юнит тестов затруднит существенный рефакторинг, но это сделают и интеграционные тесты, однако юнит тесты при этом стимулируют писать менее связанный код.
На счёт важности скорости тестов я в корне не согласен, так как от этого зависит скорость обратной связи при разработке, чем быстрее разработчик получает обратную связь, тем легче двигается разработка, особенно в продукте с историей, в котором много логических нюансов и взаимосвязей.
Интеграционные тесты никак не могут помешать рефакторингу, так как они не привязаны ни к дизайну кода, ни к контрактам методов и классов — в отличие от юнит-тестов. То есть интеграционный тест веб-сервиса — это отправить JSON в HTTP-эндпоинт и проверить ответ. У вас полная свобода для рефакторинга: можно даже заменить Java на Kotlin, и грамотно написанный тест вам не помешает, а наоборот — поможет, проверив контракт сервиса.
То, что юнит-тесты якобы способствуют написанию менее связанного кода, само по себе требует подтверждения и не вытекает из самой природы этих тестов.
Теперь о фидбэке от тестов. Уточню, что речь идёт о разработке сервисов в продуктовой разработке, где фичи скорее сквозные — проходят через все слои. Есть два варианта:
Если вы не используете TDD, то обратная связь от тестов игнорируется на большей части разработки. То есть это уже не та обратная связь, о которой идёт речь.
Если вы используете TDD, то до этапа рефакторинга код по определению не имеет устоявшегося дизайна и структуры. Значит, юнит-тесты либо будут мешать — «цементируя» драфтовый код, либо будут фактически превращены в интеграционные.
И вот какой вывод я делаю: если мы всерьёз говорим о важности обратной связи от тестов, то речь, скорее всего, идёт про TDD. А в случае сквозных фичей в продуктовых сервисах только интеграционные тесты позволяют не мешать разработчику писать код, а помогать — проверяя контракт.
Я работал на проекте, где предпочтение отдавалось интеграционным тестам или acceptance, как их там называли. Проблема в таком подходе заключается в том, что эти тесты не проверяют внутреннюю логику и взаимодействие разных модулей. Да, они отлично проверяют контракт. Но если не писать юнит-тесты, то как проверить, что внутри вызывается именно тот модуль, который нужен? В моём случае было очень много багов из-за того, что разработчики писали тесты только под контракт API, не обращая внимание на юнит-тесты.
> эти тесты не проверяют внутреннюю логику и взаимодействие разных модулей
И да и нет. Они не проверяют особенность реализации, но проверяют контракт. Функциональные и нефункциональные требования предъявляются к контракту сервиса. Внутреннее устройство не должно трактовать условия, а служить средством достижения ожидаемого результата. Часто выходит так, что совершенно не важно кто, кого и как вызывает внутри кода, если требования выполняются.
По моему опыту, акцент на интеграционные тесты снижал количество багов и уменьшал time to market. В итоге мы пришли к тому, что сервисы даже не запускались локально для финальной проверки через условный Postman — хватало глубоких интеграционных тестов.
ps: я не утверждаю, что юнит тесты не нужны. Отличное применение им - это проверка маперов, сложной бизнес логики, race condition проверок.
Time to market - да, отлично подходят интеграционные тесты. Проверка на баги? Очень сомнительно. Нужна сильно дисциплинированная команда, но она не всегда есть в наличии. Ну и если уж такой сильный акцент на контракт, то нужен толковый аналитик или управление продуктом, что тоже не всегда в наличии. В общем, должны сойтись звёзды чтобы только на одном интеграционном тестировании вывезти продукт.
70% — юнит-тесты.
25% — slice-тесты.
5% — интеграционные тесты.
Это догма?
Аналогичный вопрос, откуда эти числа, с потолка взяты? Или есть какое-то исследование (и где ссылка на исследование)?
Я вообще считаю, что все эти проценты просто попытка получить какое-то число. Но ведь нужно не число, нужно чтобы тесты реально проверяли то, что нужно проверять. Важно писать тесты, которые проверяют ту часть, которая реально нуждается в проверке. Они должны проверять какую-то фичу, сделанную по задаче, чтобы если разработчик сломал фичу, то у него упал этот тест. Они должны проверять основную логику приложения, чтобы быть уверенным, что приложение ещё работает. Они должны проверять обработку ошибок, чтобы быть уверенным, что ошибки ещё обрабатываются, и их обработка не сломана. И т. д. А просто тест ради покрытия вряд ли принесёт какую-то пользу.
Это совет)
@ExtendWith(MockitoExtension.class), @Mock, @InjectMocks
Я не использую эти аннотации для юнит тестов. Для создания моков использую статический метод mock(MyService.class)
.
Так выглядит типичный класс с юнит тестами
class MyServiceTest {
private final MyRepository mockMyRepo = mock(MyRepository.class);
private final MyService testObject = new MyService(mockMyRepo);
@Test
void test(){
// given
when(mockMyRepo.findById(any(UUID.class))).thenReturn(Optional.Empty());
// when
var result = testObject.findById(UUID.random());
// then
verify(mockMyRepo).findById(any(UUID.com));
assertTrue(result.isEmpty());
}
}
А есть какие нибудь исследования, которые подтверждают не полную бесполезность юнитестов на жаве?
Вот охота статистику глянуть
При использовании SpringBootTest кешируется спринг контекст. Если сделать класс родитель тестов, прописать в нем эту аннотацию и все end to end тесты наследовать от родителя, то контекст будет построен один раз на все тесты. Но есть нюансы, легко сломать, была статья на этой неделе на Хабре.
https://habr.com/ru/companies/spring_aio/articles/905586/
Mockito unit test - это прекрасно, но если заинжектить что-то - пройдись по двадцати тестам, допиши что вернет мок, это бесит. Обычно переписываю mockito unit test на component test, если инжектов больше чем 3. Component test это имплементация родительского теста, с контекстом спринга, но без очистки бд.
Mockmvc и mockjpa ни разу не видел на живом проекте. Один раз попробовал, не удобно, component test наше всё.
Да все эти ваши юниты -- мусор в проекте. Ни один из них не соответствует никакому реальному бизнес кейсу или сценарию. То, что контекст Spring-а поднимается как черепаха -- это проблема самого спринга, а не вашей методологии или организации проекта.
Это смотря что этими юнит-тестами тестировать. Если логику какого-нибудь метода, который что-то рассчитывает, то самое то.
Ну да, в типичном приложении на спринге 70% кода выполняет именно сложную осмысленную вычислительную логику, которая описана как отдельный бизнес кейс ))))
В большинстве случаев приложение -- это тупая надстройка над базой данных, мепящая таблицы в API. Поэтому тестировать что-то там по "слоям" не имеет абсолютно никакого смысла. Берём testcontainers, mockmvc и проверяем вашу CRUD-спеку, абсолютно не заботясь как там оно внутри наговнокожено.
...а через год разработки ни новичка в проект не ввести в сколь либо адекватные сроки, ни новую фичу добавить без боли, ни баг отловить и пофиксить, ни даже рефакторинг не провести, потому что страшно трогать. В целом работа на таком проекте превращается в сплошное страдание
Плавали в таком, больше плавать не хотим
Страшно трогать, когда на при малейшем тривиальнейшем изменении бизнес логики валятся стоптцот абсолютно бесполезных юнит тестов. Или когда логика мокирования становится в десятки раз сложнее самого функционала, и уже сами тесты являются основными источником ошибок.
Если меняется логика каких-то компонентов под изменяющиеся требования, то очевидно, что тесты должны падать. Они для этого и нужны, собственно. Но как со многими вещами в мире, если делать их плохо, то будет и получаться плохо. Если изменяется один компонент, а тестов начинает падать много и разных - то это плохие тесты. Если логика мокирования становится сложной - то это плохой код. А если делать хорошо (ну, хотя бы пытаться), то может и получиться хорошо. Когда на изменение логики падает тест, связанный только с этой логикой. Когда написание теста для класса простое и приятное. Когда изменения в коде подсвечивают, что и где изменилось в логике поведения, и ты можешь верифицировать, то ли это, что тебе нужно, или ты где-то ошибся
На практике утверждения вида "пишите сразу правильно и тогда тесты менять не придется" и "если при изменении логики надо менять много тестов - вы написали плохой код" и подобные им - с точки зрения юнит тестов отдельных методов актуальны либо в (1) простых проектах, либо в (2) медленно меняющихся.
Если проект относительно сложный и живой - это не будет работать. Если вы напишите на каждый метод по 20 тестов на каждый инвариант использования, а потом прилетит требование от бизнеса поменять логику так, что этот метод надо вообще будет удалить / объединить с другим методом, хотя в целом контракт внешне не сильно поменялся - то что делать с этими тестами? Удалить только.
А интеграционный тест - вы поправили пару полей в тестовых данных, пару проверок на границе фичи (внешние вызовы, очередь, БД и др.) и все - тест работает и регресс проверяет. Внутри меняйте и двигайте код как угодно.
Тезис о том, что юнит тесты отдельных методов заставляет писать более правильно декомпозированный код - это правда, но этот плюс почти всегда перекрывается тем, что (1) почти невозможно с 1го раза написать декомпозированный код для сложной фичи. И (2) если неудачную декомпозицию зацементировать юнит тестами, то потом вы уже ее без боли не отрефакторите.
Я пока ни разу не встречал проекты, которым написание unit-тестов вредило бы. Впрочем, не совсем так. Я встречал проекты, на которых люди не писали unit-тесты, и код был в таком состоянии, что попытка их добавить действительно была уже нетривиальной задачей, потому что все было связанным, запутанным, и без пол литры и диалога с психологом туда не то что тесты добавлять, это читать было страшно и больно
на каждый метод по 20 тестов на каждый инвариант использования
Ну, во-первых, не обязательно писать прям на каждый метод. Важно покрывать тестами те методы, которые объявлены в интерфейсе и торчат наружу. Приватные методы и даже саму структуру класса тоже можно покрывать тестами, и у этого тоже есть свои преимущества, но все-таки основная задача - это убедиться, что внешний код, который и вызывает этот класс, получает правильные результаты, а не просто писать тесты ради самого процесса написания тестов. А насчёт 20- и инвариантов - ну, тут дело уже плохо. Даже не просто плохо, это ужасно. Не должно быть у метода 20 результатов, ну просто не должно быть. И, кстати, если исходить как раз из того, что на такой метод должен быть тест, проверяющий все выходы, то и самому человеку, который этот код написал, станет ясно, что это ужас, и, возможно, ему таки придёт в голову, что так оставлять дела нельзя, и надо придумать какое-то более осмысленное решение, лучшую структуру, привнести паттерн какой-нибудь - как раз ради того, чтобы тест не выглядел как разросшееся бесформенное нечто
Внутри меняйте и двигайте код как угодно
Вот внутри и получаются методы с 20 выходами, и прочая хтонь
зацементировать юнит тестами
Unit-тесты - это инструмент. Ребёнок с молотком видит вокруг лишь гвозди, но это не вина молотка, верно? Любой инструмент можно использовать и во благо, и во вред
Внутри меняйте и двигайте код как угодно
Если цель написать код, который просто работает, особенно если это какая-то одноразовая задача, то можно тесты вообще не писать. Ручками проверил, запушил, забыл. Я же предпочитаю, чтобы код был способен к эволюции в будущем, и я не хочу приходить к ситуации, когда любое изменение может привести к падению в продакшане, потому что случилась какая-то редкая ситуация, которую тесты не поймали, потому что ее вообще не воспроизвести интеграционным тестом. Unit-тесты позволяют тестировать любой участок с любымт условиями
Аргументы типа "если вы не любите кошек, то вы не умеете их готовить" -- это фактически единственный аргумент адептов свидетелей ТДД. При всем при том есть стоптцот теоретических изысканий, методик и книг о том, как правильно писать чистый и понятный код. Однако нет ни одной удобоваримой теории как правильно писать тесты. Вся "теория" ограничивается сферическим конем в вакууме ввиде конкретного примера калькулятора, и пачкой наивных абстрактных наставнических соображений, зачастую противоречащих друг другу.
Почему в реальном мире это не работает, неплохо пояснил коллега постом ниже. Из своей практики замечу, что при внедрении метрики обязательного покрытия в 80% разработка проекта замедлилась в 3 раза, кодовая база распухла, а читабельность ухудшилась благодаря излишней фрагментарности. Команда напрочь отказывалась производить всякий рефакторинг из-за сопутствующих проблем с тестами. И главное, это практически никак не повлияло на финальные метрики ошибок.
Любые обязательные метрики, без учёта контекста - это бред. Тем более, что само по себе большое покрытие тестами - это плохая метрика. Тут проблема не в тестах как в инструменте, а в подходе
Однако нет ни одной удобоваримой теории как правильно писать тесты
Написание теста должно быть простым. Вот и вся теория. Если класс содержит в себе 20 филдов, то очевидно тест будет тяжёлым, 20 зависимостей мокать придётся. Если у метода есть 20 возможных результатов, то очевидно тест будет тяжёлым, на один метод 20 тестов. Написание теста на свой код - это моментальный сигнал о том, хороший ли у тебя код. Если написание теста становится болью - код плохой. Все очень просто, какие ещё тут теории нужны?
Любые обязательные метрики, без учёта контекста - это бред.
Благо в этом мире бреда хватает на всех.
Написание теста на свой код - это моментальный сигнал о том, хороший ли у тебя код
Да всем ровным счётом по барабану какой там у вас код и как там он устроен. В проекте важны дедлайны и соответствует ли функционал заявленной бизнес спецификации или нет. Именно это и нужно тестировать. При наличии тестового покрытия функционала, его потом можно отрефакторить без лишнего геморроя.
И как я уже сказал выше, юниты отнюдь не являются метрикой качества кода. В дизайне появляется излишняя фрагментарность, на порядки ухудшающая читабельность, нарушается инкапсуляция и области видимости. С зависимостями все ещё хуже: мокать repository -- глупое и неблагодарное занятие.
по барабану какой там у вас код
Мне не по барабану. Я люблю делать свою работу качественно, а не тяп-ляп, и пушим в прод, лишь бы работало
В проекте важны дедлайны
Через какое-то время разработки тяп-ляп получится ситуация, когда проблемы и плохой код накопятся как снежный ком, и разработка новых фич станет сложной и дорогой, а для ловли багов нужно будет уметь не код читать, а знать кучу особенностей проекта и все его внутренние костыли
В дизайне появляется излишняя фрагментарность, на порядки ухудшающая читабельность, нарушается инкапсуляция и области видимости
Ну, вероятно, можно сделать и так. Вполне допускаю, что многие люди просто не умеют писать хороший код. Но это не проблема инструмента, а проблема плохой квалификации программиста
Если бизнес код все ещё понятен и юзабелен, то поддерживать снежный ком и месиво из тупых и бесполезных юнит тестов уже не представляется возможным. Код не эволюционирует, рефакторинг не производится, новые фичи прикручиваются сбоку, либо вообще игнорятся. Проект а станции, зато метрики покрытия зелёные, QA счастлив!
Никто не умеет писать хороший код. Есть те, кто думают, что пишут хороший код потому что им так сказали в книжке или на презентации люди, которые уже 20 лет ничего не писали.
Тесты как раз обеспечивают безопасный рефакторинг. Вносить изменения, не имея никаких гарантий, что ты случайно что-то где-то не зацепил, и что оно не выстрелит в проде - это, ну, слабоумие и отвага
При этом есть люди, которые стремятся писать хороший код, а есть те, которые делают тяп-ляп, лишь бы работало. Как оно будет функционировать через год их не волнует, они к тому моменту свалят из проекта
Если честно, не согласен и считаю, что всё должно быть наоборот.
Основное поведение системы должно покрываться интеграционными тестами. А юнит-тесты стоит писать выборочно — там, где в одном методе или классе действительно сосредоточено много логики, или же для утилитных методов, какие-то сложно-читаемые штуки с регулярными выражениями и подобными вещами.
Аргументы следующие:
В большинстве проектов мы стараемся писать простые и читаемые методы. Тест на каждый из них — это гонка за покрытием без реальной пользы. Чем меньший объём логики покрывает тест, тем выше риск, что он упустит общий смысл поведения фичи/системы.
Юнит тесты в том виде, что вы предлагаете - "заливают весь проект эпоксидной смолой" / "прибивают все гвоздями". Такие тесты не помогут вам в рефакторинге и проверке регрессионных багов. При значимом рефакторинге тесты придется выкинуть или очень сильно менять. Если проект развивается и нужно корректировать декомпозицию / уровни абстракции / доменную область или контекст - то такое тесты будут только мешать.
TDD в такой интерпретации почти невозможен. Тут почти всегда будет 2 варианта:
Логика фичи настолько простая, что мы заранее можем разбить всё на методы, написать на них тесты, а потом реализовать — но тогда и тесты несут мало пользы - всё и так очевидно.
Логика фичи сложная — и тогда мы не сможем заранее угадать нужную декомпозицию. Придётся писать код, рефакторить его в пару итераций, а потом уже писать тесты. Это уже не TDD, да и такие тесты в любом случае не проверяют фичу в целом.
Медленные интеграционные тесты — почти всегда решаемая задача. Есть способы ускорить их запуск: поднимать только необходимый контекст, переиспользовать контекст / БД / контейнеры, использовать ленивую загрузку и прочее. Данные можно подготовить заранее и один раз, и например, миграции БД не накатывать на каждый тест. Миграции тестировать отдельными тестами, если они очень долгие. В общем, варианты есть.
Сложность подготовки тестовых данных — это тоже сигнал. Если мы не хотим писать интеграционные тесты, т.к не можем под них создать тестовые данные - значит система переусложнена. Тут уже вопрос - что мы вообще собираемся тестировать и какой смысл любых тестов на такой системе? Тут надо архитектурно декомпозировать систему, упрощать и так далее. Все-таки тестовые данные почти всегда создать можно, возможно чуть больше усилий нужно потратить, но это окупиться тем, что теперь у нас будут тесты, которые наконец-то дадут вам возможность использовать их для рефакторинга, регресса и прочее.
Итого, если есть возможность, то я бы рекомендовал делать 90% именно интеграционных тестов.
Согласен со всем сказанным, кроме пункта 4. И здесь виновата сама архитектура спринга, ибо никогда время запуска приложения не считалось критичным. Разработчики подождут -- им за это платят. В итоге вся эта белебердень с юнитами, слайсами, ленивой инициализацией была придумана, чтобы хоть как-то ускорить тесты. Поэтому если разработчики самого спринга рекомендуют вам писать юниты без контекста где это только возможно, значит они где-то крупно прокололись.
Главное уметь выбрать инструмент под ситуацию, иначе все может оказаться бесполезным, неэффективным и даже вредным. Юнит тесты отличный способ точечных проверок, быстро работают и дают гибкость в масштабировании корнер-кейсов, но, как и любой другой вид тестирования, подойдут не везде и не всегда
Пара советов по покрытию тестами проекта на SpringBoot