Согласен, TDD - не волшебная пилюля. Такой подход не слишком подходит, если мы не знаем в какую сторону идти. Как вы упомянули, могут потребоваться PoC и прочие исследования чтобы определить, где мы хотим оказаться в будущем. После этого в дело вступает TDD, которое как каток помогает прокладывать дорогу к пункту назначения
Чсто встречаю такое утверждение. Мне кажется, мне кажется оно в большей степени касается конкретных реализаций. Сложно представить ситуацию, когда вы уже готовитесь писать решение, но не знаете какая у вас бизнес проблема. На примерах из статьи: Беря задачу в работу я точно знаю, что бизнесу необходимо показывать оценку книги из стороннего сервиса. Исходя из этого я пишу тест вида
Я могу не быть уверенным, какая база данных или ее структура мне нужна. Я могу не знать до конца как взаимодействовать с внешним сервисов рейтингов книг (хотя необходимо проверить заранее, что нужное нам поведение предоставления оценки по ID книги существует). Все это детали реализации, которые могут меняться. Бизнес логика (написанный тест в примере), ради которой и создается любое програмное обеспечение, меняется крайне редко.
Мир не черно-белый, как я говорил в конце статьи, не существуют универсальных подходов. В моей практике, каждый раз, когда я говорил себе "Не стоит тут тратить время на абстракции, можно напрямую работать с конкретными технологиями (зависимостями)", на следующий день появлялась необходимость либо как-то сложно проверять corner case либо поддерживать несколько похожих технологий. Тогда пригодились бы правильные абстракции, и приходилось все равно их внедреть позже чем следовало бы
Мне кажется есть разница между тестами написанными на код и кодом написанным под тесты. Я упомянул это в пункте "Уверенность в том, что должна делать система": тесты написанные после кода всего лишь позволяют проверить что код все еще работает похожим образом после внесенных изменений, но не отвечает на вопрос, делает ли он то, что от него требуется на самом деле.
Спасибо за замеченную опечатку, поправил. Согласен насчет использования ИИ. Хотя я могу доверить написать код по тестам, но все таки тесты еще пишу сам. Процесс написания тестов позволяет сфокусироваться и определить конечный результат, который я хочу видеть
Начните с понимания того, как работает сборщик проектов для вашего языка. Например, для Java посмотрите на Maven (https://maven.apache.org/guides/index.html) и Gradle (https://docs.gradle.org/current/userguide/userguide.html). Почитайте, как вручную скомпилировать несколько файлов с исходным кодом, запаковать в архив. Можно начать писать свой аналог с решения этой проблемы - как автоматизировать сборку артефакта с возможностью конфигурации и кастомизации. Заодно разберетесь, как работают существующие решения и чем они отличаются друг от друга
От себя рекомендую попробовать написать сборщик проектов. Например, для Java аналог Maven или Gradle. Сам сейчас такое пишу в свободное время для развлечения. Много тем затрагивается, например: алгоритмы, динамическая загрузка кода (плагины), последовательность превращения строчек с кодом во что-то живое, возможность применения на реальных проектах и т.д.
Коллеги, поделитесь опытом, часто ли приходиться самостоятельно настраивать таким образом speing-security? Мне кажется это все ненужным, когда есть OAuth2, OIDC, Keycloak и spring-boot-starter-oauth2-resource-server
Интеграционные несомненно нужны, спору нет. Однако они находятся за рамками подхода TDD. Для описанного примера, если его расширить, нужны: тесты ядра (они описаны в статье), тесты различных адаптеров (например, что вызов удаленного ресурса происходит с нужными параметрами и его ответ правильно преобразуется и возвращается в доменный слой), интеграционные тесты (в окруженнии максимально близком в реальному). Как можно заметить, буду тесты и доменного слоя, и слоя приложения (с помощью тестов адаптеров) и слоя инфраструктуры (интеграционные).
TDD ни в коем случае нельзя практиковать при подходе с "белым ящиком". Это убивает одно из его преимуществ: даже минимальный рефакторинг все ломает. Мне кажется: либо сразу понятны граничные случаи - тогда мы обсуждаем их с заказчиком (что будет если name = "") и делаем тесты на них, либо они неочевидны - тогда скорее всего мы с ними столкнемся в виде багов - и снова напишем тест (баг воспроизвелся, тест красный), исправим и проверим (тест зеленый).
"Если же мы хотим уверенности работая с черным ящиком - то нам нужен тест проверяющий все варианты name"
Своему ядру (бизнес логике, домену) нужно доверять. Городить повсюду проверку на null или пустую строку. Для таких случаев, по-моему, хорошо иметь anticorruption layer - на входе в домен мы проверяем входные данные (имя не null, не пусто), после этого внутри ядра мы доверяем тому факту, что имя логически корректно.
Да, согласен, пример кустарный (как и везде в подобных статьях). И все же я надеялся, что основные мысли будут понятны: нужно правильно разграничивать бизнес логику, фреймворки, бибилиотеки, связи между модулями; тестированию при TDD подвергается прежде всего требуемый функционал от системы.
В случае модуля, который зависит от других модулей, я бы тестировал все отдельно друг от друга. При этом взаимосвязи между ними перекрыл портами (выходной порт из одного модуля, входной порт в другом). Таким образом в случае монолита адаптер будет просто перенаправлять вызовы в нужный модуль, если микросервисы - делать http вызовы или что-то другое. Для тестов же это позволит сделать mock или stub модуля, от которого зависит тестируемый и задать ему "правильное" поведение, которое не зависит от реального текущего (вдруг там ошибка и тесты текущего модуля сломаются, так как зависимость вернула неожиданный результат)
Согласен, TDD - не волшебная пилюля. Такой подход не слишком подходит, если мы не знаем в какую сторону идти. Как вы упомянули, могут потребоваться PoC и прочие исследования чтобы определить, где мы хотим оказаться в будущем. После этого в дело вступает TDD, которое как каток помогает прокладывать дорогу к пункту назначения
Чсто встречаю такое утверждение. Мне кажется, мне кажется оно в большей степени касается конкретных реализаций. Сложно представить ситуацию, когда вы уже готовитесь писать решение, но не знаете какая у вас бизнес проблема. На примерах из статьи:
Беря задачу в работу я точно знаю, что бизнесу необходимо показывать оценку книги из стороннего сервиса. Исходя из этого я пишу тест вида
Я могу не быть уверенным, какая база данных или ее структура мне нужна. Я могу не знать до конца как взаимодействовать с внешним сервисов рейтингов книг (хотя необходимо проверить заранее, что нужное нам поведение предоставления оценки по ID книги существует). Все это детали реализации, которые могут меняться. Бизнес логика (написанный тест в примере), ради которой и создается любое програмное обеспечение, меняется крайне редко.
Мир не черно-белый, как я говорил в конце статьи, не существуют универсальных подходов. В моей практике, каждый раз, когда я говорил себе "Не стоит тут тратить время на абстракции, можно напрямую работать с конкретными технологиями (зависимостями)", на следующий день появлялась необходимость либо как-то сложно проверять corner case либо поддерживать несколько похожих технологий. Тогда пригодились бы правильные абстракции, и приходилось все равно их внедреть позже чем следовало бы
Мне кажется есть разница между тестами написанными на код и кодом написанным под тесты. Я упомянул это в пункте "Уверенность в том, что должна делать система": тесты написанные после кода всего лишь позволяют проверить что код все еще работает похожим образом после внесенных изменений, но не отвечает на вопрос, делает ли он то, что от него требуется на самом деле.
Спасибо за замеченную опечатку, поправил.
Согласен насчет использования ИИ. Хотя я могу доверить написать код по тестам, но все таки тесты еще пишу сам. Процесс написания тестов позволяет сфокусироваться и определить конечный результат, который я хочу видеть
Начните с понимания того, как работает сборщик проектов для вашего языка. Например, для Java посмотрите на Maven (https://maven.apache.org/guides/index.html) и Gradle (https://docs.gradle.org/current/userguide/userguide.html). Почитайте, как вручную скомпилировать несколько файлов с исходным кодом, запаковать в архив. Можно начать писать свой аналог с решения этой проблемы - как автоматизировать сборку артефакта с возможностью конфигурации и кастомизации. Заодно разберетесь, как работают существующие решения и чем они отличаются друг от друга
От себя рекомендую попробовать написать сборщик проектов. Например, для Java аналог Maven или Gradle. Сам сейчас такое пишу в свободное время для развлечения. Много тем затрагивается, например: алгоритмы, динамическая загрузка кода (плагины), последовательность превращения строчек с кодом во что-то живое, возможность применения на реальных проектах и т.д.
Коллеги, поделитесь опытом, часто ли приходиться самостоятельно настраивать таким образом speing-security? Мне кажется это все ненужным, когда есть OAuth2, OIDC, Keycloak и spring-boot-starter-oauth2-resource-server
Большое спасибо! Бесценно иметь все в одном месте под рукой
Интеграционные несомненно нужны, спору нет. Однако они находятся за рамками подхода TDD. Для описанного примера, если его расширить, нужны: тесты ядра (они описаны в статье), тесты различных адаптеров (например, что вызов удаленного ресурса происходит с нужными параметрами и его ответ правильно преобразуется и возвращается в доменный слой), интеграционные тесты (в окруженнии максимально близком в реальному). Как можно заметить, буду тесты и доменного слоя, и слоя приложения (с помощью тестов адаптеров) и слоя инфраструктуры (интеграционные).
TDD ни в коем случае нельзя практиковать при подходе с "белым ящиком". Это убивает одно из его преимуществ: даже минимальный рефакторинг все ломает. Мне кажется: либо сразу понятны граничные случаи - тогда мы обсуждаем их с заказчиком (что будет если name = "") и делаем тесты на них, либо они неочевидны - тогда скорее всего мы с ними столкнемся в виде багов - и снова напишем тест (баг воспроизвелся, тест красный), исправим и проверим (тест зеленый).
Своему ядру (бизнес логике, домену) нужно доверять. Городить повсюду проверку на null или пустую строку. Для таких случаев, по-моему, хорошо иметь anticorruption layer - на входе в домен мы проверяем входные данные (имя не null, не пусто), после этого внутри ядра мы доверяем тому факту, что имя логически корректно.
Да, согласен, пример кустарный (как и везде в подобных статьях). И все же я надеялся, что основные мысли будут понятны: нужно правильно разграничивать бизнес логику, фреймворки, бибилиотеки, связи между модулями; тестированию при TDD подвергается прежде всего требуемый функционал от системы.
В случае модуля, который зависит от других модулей, я бы тестировал все отдельно друг от друга. При этом взаимосвязи между ними перекрыл портами (выходной порт из одного модуля, входной порт в другом). Таким образом в случае монолита адаптер будет просто перенаправлять вызовы в нужный модуль, если микросервисы - делать http вызовы или что-то другое. Для тестов же это позволит сделать mock или stub модуля, от которого зависит тестируемый и задать ему "правильное" поведение, которое не зависит от реального текущего (вдруг там ошибка и тесты текущего модуля сломаются, так как зависимость вернула неожиданный результат)