Зашел тут как-то спор на работе по поводу юнит-тестов. Спорили-спорили. Интересно, но все остались при своих. Полез смотреть в интернет, оказывается есть два “лагеря”, которые никак не могут примириться.
Проблема простая: тестировать или не тестировать приватные методы.
Сторонники одного лагеря настаивают на том, что юнит-тестами должны быть покрыты только публичные функции и классы. Объяснение этому такое:
- Тестировать нужно интерфейс класса или подсистемы, который не зависит от внутренней реализации.
Любое изменение “внутренностей” этого класса или подсистемы не должно сказываться на функционировании интерфейса. Таким образом, покрыв юнит-тестами интерфейс мы всегда имеем возможность убедиться, что подсистема/класс функционирует именно так, как нам нужно. А что и как у неё делается внутри, с этой точки зрения не важно. - Тестирование приватных функций отнимает много времени, усложняет поддержку и нерационально.
В ходе любого проекта происходит масса рефакторинга. По моим оценкам, на рефакторинг уходит до 20% времени при “нормальной” организации работы и неплохом качестве архитектуры и кода. Как только в проекте появляются юнит-тесты, так сразу возникает необходимость поддержки не только кода продукта, но и юнит-тестов в актуальном состоянии. При этом реализация классов/подсистем меняется гораздо сильнее и чаще, чем интерфейсы. Всяческие там оптимизации и т.д. Поэтому поддержка юнит-тестов на уровне интерфейса – оправдана, так как даёт уверенность при использовании подсистемы/класса. А покрытие ими реализации – не оправдано, так как, во-первых, если “падает” какой-то внутренний функционал, то “падает” и один из “интерфейсных” вариантов использования, а во-вторых существенно увеличивает объем работы. - Подход полностью соответствует TDD.
Создавая публичный метод мы, де-факто, создаём интерфейс, его и тестируем. С этой точки зрения нам, опять же, совершенно не важно, что там делает этот метод внутри и как именно.
Второй лагерь настаивает на обратном:
- Полезно тестировать каждый отдельно взятый функциональный аспект приложения.
В термине “юнит-тестирование” под “юнитом” понимается настолько малый и обособленный функциональный фрагмент, насколько это возможно. Фрагмент этот не обязательно должен быть публичным. Разработчики имеют право быть уверены в том, что каждый маленький, но важный кусок (а неважный никто и писать бы не стал – поленился бы) выполняет свою задачу. Независимо от зоны его видимости, которая, кстати, может меняться в процессе рефакторинга. - Приватная функция может быть использована в нескольких местах.
То же касается и приватного класса. Для того, чтобы использовать эту функцию или класс, неплохо было бы быть уверенным, что этот кусок кода хорошо делает свою работу. - Бόльшая определённость в покрытии юнит-тестами.
Для каждой отдельной конечной приватной функции достаточно легко написать тест, грамотно покрывающий варианты её использования. Другое дело, когда тестируются только публичные функции, вызывающие другие (приватные) функции. Написать тесты для такой сложной функции может оказаться сложнее. Кроме того, никогда точно не знаешь, какой объем вариантов использования покрыт.
В общем, нет согласия в рядах.
А к какому лагерю склоняетесь вы?