Здравствуйте, itslave, Вы писали:
IT>>Типичным индикатором такой ошибки, кстати, является использование моков.
I>хм, использование моков — это первый признак использования юнит тестов. Ну банальное там, операция "перевести деньги со счета на счет" подразумевает мокание базы данных и тестирования логики бизнес рулов перевода денег: там проверок что счета активные, денег достаточно и т.д. С твоей точки зрения — такие тесты делает что нибудь полезное?
Моки и юнит тесты — это две плохо совместимые вещи.
Юнит — это метод, класс, модуль, в идеале максимально независимый и самодостаточный. Идеальным примером юнита является функция, принимающая все входные данные как параметры и возвращающая результат работы как возвращаемые значения без создания каких-либо артефактов. Если сложность такого юнита не ограничивается одной функцией, то это может быть класс или даже целый модуль. Но главная идея остаётся — принимаем входные данные, какими бы сложными они ни были, возвращаем результат, каким бы печальным он ни оказался, не создаём при этом артефактов, даже самых несущественных.
Таким образом, передача в юнит данных с помощью моков говорит об их "неидеальности", а на практике о полной ущербности не только самого юнита, но и в целом дизайна приложения. Передача в юнит с помощью моков поведения, говорит о некоторой уже неюнитости юнита, что тоже по большому счёту как правило косяк.
Последнее следует пояснить. Лично я для себя выделяю несколько, принципиально отличающихся друг от друга, типов кода. Например, всевозможные структуры данных, DTO, AST, ADT и прочие контейнеры — это один тип кода, задача которого заключается в представлении данных. Тестирование такого кода в языках со строгой типизацией имеет исчезающе мало смысла и является признаком весьма бурно прогрессирующей паранойки. Второй тип кода — всё, что вне метода. Классы, сборки, неймспейсы, интерфейсы и т.п. Зачастую именно этот код определяет высокоуровневый дизайн приложения, но как его тестировать пока не придумали. Третий тип — алгоритмы, т.е. код, который делает работу. Как раз его в основном мы и тестируем.
Но есть ещё один тип кода — тот, который управляет кодом, который делает работу. При этом если код, который делает работу, делать максимально независимым и отчуждаемым, а управляющий код делать максимально примитивным, то появляется реальная возможность без проблем жонглировать кодом как вздумается. Передача юниту поведения убивает эту возможность наповал. В общем, coupling and cohesion в полный рост.
Кстати, что такое coupling and cohesion в теории знают все, а вот как с этим бороться на практике объяснить мало кто может. Так вот разделение кода на типы и примитивизация управляющего кода — это самый простой способ. Независимые, самодостаточные, легко отчуждаемые юниты не представляют угрозу увеличения сложности приложения, а вот управляющий код является чуть ли не фундаментальной основой большинства подобных проблем.
Теперь вернёмся к мокам. Мок — это заглушка, имитирующая поведение реального объекта, внешнего по отношению к текущему юниту. Таким образом, использование моков может быть оправдано только в интеграционных тестах. В юнит тестах — это чёткий индикатор кривого дизайна.
Можно ли без моков обойтись всегда? Конечно, нет. Но случаи их применения очень чётко ограничены — зависимость от неподконтрольных систем и длительное время работы внешнего компонента. Всё остальное косяки, особенно это касается моканья базы данных.
Короче, вопрос должен ставиться не как "плохи или хороши моки", а как "простите потомки, виноват, каюсь, но лучшего решения без моков не нашлось".
Если нам не помогут, то мы тоже никого не пощадим.