А львиную долю кода, написанную для удобства тестирования — нужно переписать. Просто потому что этот код обладает МАГИЕЙ, им невозможно пользоваться не используя магию.
Это что за код «для удобства тестирования»? Arrange/Given? Опять же, если он сложный до магии, это плохой признак. Рабочего кода.
А вот правильный код заклинаниям не подчиняется, и сравнимых результатов вовне не возвращает
Кажется, я вас понял. Сложная логика в void методе. И не разбитая на классы, которые можно протестировать отдельно. Но почему вы считаете, что это правильно?
И на всякий случай повторюсь. Я не говорю о том, сколько нужно покрывать тестами — 20% или 80%. Я говорю о том, что код хорошо бы писать так, чтобы его _можно_ было протестировать. Вот как по-вашему, шаблон фабрика для чего?
Я нигде не упоминал про TDD.
Есть две крайности: либо сложные методы вообще без тестов, либо «открытые» классы без инкапсуляции с хрупкими тестами.
Что делать? Боюсь, буду банален — есть удачные рецепты в шаблонах проектирования.
Я понимаю ваше возмущение.
Вот есть сложный метод, поэтому на него нет теста.
Говорите, его надо разбить на части? Окей, а как?
Шаблоны проектирования? Ну, на калькуляторах и туду-листах все эти фабрики и стратегии понятны. А в реальном проекте как быть?
Много общих слов, и мало конкретики? Так я пытался свои слова конкретикой подкрепить.
1. Нет теста? — сигнал, что плохой метод.
2. В чём причина? — на мой взгляд, в new Date() в теле метода.
3. Как исправить? — не создавать объект внутри, передавать его извне.
4. Что дальше? — продолжаем изучать ООП.
Вот потом можно и подумать о юнит-тесте,
Уже отвечал.
Я нигде не говорил, что нужно написать или не написать тест на этот метод. Но всячески подчёркивал, что кмк полезно задумываться, как же его тестировать.
Я, собственно, на разные лады, пытаюсь сказать вообще другое. Без тестов код получается плохой, архитектура страдает, а багов если сейчас нет, так потом появятся.
На мой взгляд, шаблоны проектирования в том числе и про то, как порезать код по классам так, чтобы и ответственность разделить, и протестировать легко можно было. Даже если и не написать тесты.
Если не возражаете, чтобы не повторяться dou.ua/…ign=reply-comment#1095064
А стремление распространить практику тестирования на всё — убивает все остальные задачи, переводя ресурсы на бюрократию.
На мой взгляд, если практика тестирования «убивает» — это сигнал о плохом коде, а не о бюрократии.
Извините, объясняю на пальцах.
Есть метод, он что-то делает. Например, отправляет поздравления на НГ. Теста на него нет, потому что непонятно, как задачу разбить на классы и методы.
Поэтому она проверяет ручным тестированием с переводом даты на машине разработчика.
Потом проект развивается, например, добавляют новые праздники и поздравления через смс. Код изменяется. Вручную тестируется обычно новый функционал. Регрессию — полное перетестирование всех старых фич — делать долго и дорого. Поэтому не факт, что рассылка на НГ будет перепроверена.
Может через год она и отработает нормально. А может и нет.
Если вы умеете добавлять/изменять/развивать код, гарантируя сохранность старого функционала, так и скажите.
Поэтому я повторюсь.
Заранее усложнять код под будущие изменения — плохо, а если даже не писать тесты, но думать о них — хорошо.
Лучше быть богатым и здоровым, чем бедным и больным. Хороший разработчик, даже работая в команде, просто не делает баги, поэтому и не нуждается в тестах. Извините.
Я ожидал такого ответа.
Во-первых, вы правы — ручное (ну или автоматическое) тестирование действительно превыше всего.
И если юнит- и интеграционных тестов нет, то только оно и остаётся. А оно довольно медленное и дорогое.
Код может сломаться при изменениях, например, другие праздники добавятся.
Я нигде не говорил, что нужно написать или не написать тест на этот метод. Но всячески подчёркивал, что кмк полезно задумываться, как же его тестировать. И таким образом получить сигнал, что в теле метода есть проблема компоновки.
А если метод написан плохо и именно по этой причине на него нет теста, то баги там появятся рано или поздно при дальнейшем развитии проекта.
Окей, а вы что предлагаете: подождать до Нового года и посмотреть сработает или не сработает?
Мне видится, что тесты не пишут не потому, что не хотят, а потому что на первоначальный вариант метода — простой, понятный, читаемый и с багом — тест написать нельзя.
Далее, я считаю, что хорошая архитектура и покрытие тестами — это не противоположные направления, а одно.
юнит-тест — вовсе не обязательно что он проверяет все возможные сценарии поломки
Мне кажется, что цели юнит-теста такие:
1) даже не написанным позитивно влиять на архитектуру. Если ветвлений нет, возможно логгирования будет достаточно;
2) зафиксировать для регрессии/возможного будущего рефакторинга positive flow;
3) в случае рисков и/или багов добавлять negative flows;
Коллега, ну мы же программисты всё таки. Давайте не будем «колбасить», пожалуйста.
Посмотрите в сторону Parameterized, если уж есть необходимость проверять интерфейс на большом количестве значений.
Ну, насчёт «лучше баг» — не могу согласиться. Или можно с НГ и в другой день поздравить, пользователи перебьются? :-) У нас, например, релизное окно — раз в два месяца, поэтому «быстренько подфиксить на проде» не работает.
И, главное, как быть с сопровождением кода?
Пример выше — вообще не про баг, а иллюстрация проблемы,
что в теле метода создаётся новый объект, что практически перечёркивает возможность его тестирования, а не потому что времени не было или заказчик не заплатил.
Извините, я вас плохо понимаю.
Попробую сказать иначе: реализацию крайне выгодно держать доступной не по имени, не по пути к файлам, не по логике вызовов. А просто в том месте, где её будут искать. И ограничить исключительно этим местом.
Есть класс/интерфейс, есть его реализация, одна или несколько. Вы о каком поиске?
То же самое с тестами. Они должны быть написаны и находиться именно в том месте, где они будут понятны.
Опять же, есть конвенция: имя класса Foo, имя теста — FooTest. В том же пакете.
Если же для понимания теста нужно понимать код — это ПЛОХОЙ тест
А может это плохой код? Тест должен писаться просто — AAA или GWT. Если это невозможно, имхо, проблема в сложном методе.
Всё что делает код — должно быть написано в коде, и нигде больше. Если нужно защититься от изменения кода — необходимо делать правила, доступы, защиту версий от человека, бэкапы — но никак не дублирование той же логики в другом коде.
Тут я, кажется, понимаю, что вы хотите сказать. Например, если в методе происходит выбор значения из справочника, то повторять в тесте этот же самый справочник особого смысла нет.
И самый правильный способ это сделать — напрямую. Задача «а как потом это тестировать» потребует абстракции, такой чтобы и точка взятия, и точка куда ложить — была доступна извне, и понятно что это и как.
А вот тут не согласен. И пытался продемонстрировать на примере с датой. Вы, если я правильно понял, призываете писать понятно, читаемо и без багов :-) Но не говорите, как этого достичь. А я предлагаю критерий, следуя которому, как мне кажется, улучшается компоновка. Сравните
public void congrats() { Date today = new Date(); if (today.getMonth() == 1 && today.getDay() == 1) { System.out.println("Happy New Year!"); } }
и
@AllArgsConstructor class Congrats { private final Date date; public String toString() { if (date.getMonth() == 1 && date.getDay() == 1) { return "Happy New Year!"; } return ""; } } ... System.out.println(new Congrats(new Date()));
Да, кода стало на пару строчек больше, и то, спасибо Lombok-у, а то Java не особо лаконична.
Да, баг всё ещё присутствует, но когда на него наткнутся, понятно, как написать тест, исправить, и быть уверенным в исправлении.
Да, на первый взгляд проще «напрямую», но тогда нет ни в чём уверенности, кроме как в полной регрессии на каждый чих.
В местах разрыва или естественных абстракций — сущности легко объяснимы. А вот чтобы объяснить доступ к неочевидным временным частям целого объекта в неочевидной стадии — нужно очень постараться.
Если не затруднит, может пример кода? Не могу понять, о чём идёт речь. Вы про инкапсуляцию, композицию и иммутабельность?
Попробую объяснить. Если для структуры написан «индусский» тест на сеттеры-геттеры, а на «процедуры» тестов нет, потому что слишком сложно, или тесты с копи-пастой сетапа, ЗНАНИЯМИ о коде и прочими страшными вещами, перечислеными выше...
... то не в тестах проблема. Вернее, она, естественно, есть, но это следствие и симптом того, что
Проблема в компоновке кода. Он неправильно разбит по классам, в нём слишком сложные методы, не применены шаблоны проектирования и т.д. Этот код будет, скорее всего, будет сложно переиспользовать, модифицировать при изменяющихся требованиях и т.д. Даже если вдруг реализация вначале была и без багов.
Я выше приводил пример с датой и поздравлением с Новым годом. Даже в таком коротком и простом коде ошибка, на мой взгляд, из-за создания нового объекта — даты — в теле метода.
Вы тоже ужасы рассказываете, хотя, не буду отрицать, я с ними знаком :-)
Код, код нужно писать так, чтобы
Arrange-Act-Assert не особо выходил за рамки трёх строчек.
Если большой/сложный/с ответвлениями/повторяющийся сетап — это в коде проблема, метод слишком сложный. Это всё должно один раз тестироваться, а не повторяться для разных тестов.
Вы сейчас говорите про реализацию. Сравнение со строкой тоже может привести к проблемам, при локализации например. Никто не будет спорить, что код должен быть максимально читаемым. Однако, читаемость не гарантирует хорошей компоновки/архитектуры. Пример выше вовсе не про то, с какого числа нумеруются месяцы.
Главная проблема в сигнатуре метода, компоновке класса, а меньшая — что реализация подкачала.
Дело в том, что в теле метода создаётся новый объект, что практически перечёркивает возможность его тестирования, а не потому что времени не было или заказчик не заплатил.
Поэтому я ещё раз повторюсь
Даже если просто держать в уме «а как я этот метод буду тестировать?» — это уже очень сильно помогает.
Вы какие-то странные вещи говорите.
То есть чтобы протестить, нужно ЗНАТЬ код. А чтобы поменять код — нужно ЗНАТЬ тест,
Если это так — это плохой признак. Сигнал о сложном методе, для тестирования которого нужны mocks == знания о том, что происходит внутри.
Тесты — такой же код. И при их написании говнокод фактически считается «хорошей практикой».
Зачем же так?
С одной стороны, я с вашими мудрыми словами, что тестировать нужно разрывы логики согласен. Только как практически это делать-то?
Вот вы код отрефакторили, а баг остался.
Вы про какую передачу данных? Я про метод, возвращающий значения говорил. В чём тут магия-то?
И давайте c примерами кода, пожалуйста. Иначе слишком абстрактный разговор получается. Повторю вопрос по фабрику. Зачем этот шаблон? Почему нельзя в теле метода просто написать switch?