Комментарии 37
Тесты, которые нужно менять после каждого изменения в коде, бесполезны и даже вредны, поскольку требуют затрат на сопровождение и не приносят никакой пользы.Тесты должны падать при изменении системы. Иначе это бесполезные тесты, которые ничего не проверяют.
Тесты должны падать при изменении системы. Иначе это бесполезные тесты, которые ничего не проверяют.
А если точнее, то тысты должны падать при невалидных изменениях системы. В идеале, если мы меняем реализацию компонента без изменения внешнего интерфейса и новая реализация работает, ниодного теста упасть не должно.
Изменение первое: в код API, который возвращает ссылку, случайно добавили очень долго работающий код — например,
Thread.Sleep(100000)
. Реализация поменялась, а интерфейс — нет. Регресс функционала налицо, но если в тесте те указан timeout, то он не упадет.Изменение второе: API возвращает теперь не одну ссылку, а несколько в разном разрешении, и виджет на клиенте показывает наиболее подходящую. Система осталась в согласованном виде, потому что изменения есть и в API, и в клиентском виджете, но промежуточные тесты должны упасть.
Что вы понимаете под «невалидным изменением»?Очевидно то поведение системы, которые не предполагалось и не проектировалось и при обнаружении которого будет заведена задача на его исправление.
Как тут могут помочь тесты с псевдорендерингом? Думаю что примерно никак. В нашем конкретном случае, если вместо листа строчек пришел бы объект или что-то другое, то обычный юнит тест упал бы точно также, как и тест при помощи рендеринга.
Весь смысл статьи в том, что логика рендеринга смешана с логикой для работы с данными, из-за этого нельзя нормально тестировать и нормально сопровождать тесты. И что если так происходит, то время задуматься.
class FaultyList extends React.Component {
render() {
let items = this.state.items;
let filtered = items.filter(x => x.someProperty == 1);
return (
<ul>
{items.map(x => <li>{x}</li>)}
</ul>
)
}
}
Такие досадные ошибки тоже частенько случаются, особенно в ходе рефакторинга. Логику работы с данными можно вынести и наверняка тестировать ее отдельно действительно будет удобно, но полностью от написания интеграционных тестов не спасет.
class FaultyList extends React.Component {
render() {
return (
<ul>
{filter(this.state.items).map(x => <li>{x}</li>)}
</ul>
)
}
}
Я думаю что здесь гораздо сложнее ошибиться. И шаблон как-то почище.
Дело в том, что сам компонент написан плохо. Код для фильтрации нельзя использовать повторно. Если нам понадобится фильтрация в другом компоненте, то при таком подходе придется писать и тестировать все заново.Нарушен принцип единственности ответственности.
Помимо логики для вывода в компоненте есть еще и логика для фильтрации. Решение есть, и оно очень простое. Можно перенести код, который отвечает за фильтрацию в другое место и тестировать там.
1. Если нам понадобится применять ровно эту фильтрацию в другом месте… А если не понадобится?
2. Если мы протестировали код для фильтрации в другом месте — по-хорошему, при тестировании компонента мы должны протестировать, что он вызывает именно эту функцию фильтрации, а не какую-то другую. Так как это внутренняя и неявная зависимость модуля, фактически, для формальной чистоты — мы должны прогнать тесты, которые покроют эту зависимость.
2. Вот в том то и дело, что для формальной чистоты. А насколько и когда это реально нужно?
У вас два пункта противоречат друг другу.
В первом вы говорите, что чистота кода полезна, а во втором — что формальность. В чем же разница?
С одной стороны, вы правы. Если тесты будет писать сложно, то их будет написано мало, и стабильность продукта будет под угрозой.
Но в то же время, если вынести логику в сервисные модули для легкого тестирования, остальное будет непротестировано. В вашем примере компонента тест проверяет не только вызов .filter()
, но и весь компонент в целом. Разработчики могут ошибаться и в коде метода render и в constructor. И тест все это покроет и будет проходить, только если компонент в порядке.
Тем более, что приведенный тест несложный и полностью себя оправдывает.
И ничего больше.
Потому что работа модуля завязана на внешние источники и я пока не научился его тестировать юнит-тестами.
Эти тесты не бесполезны — они как минимум проверяют что модуль в принципе существует и нормально импортируется.
Тесты тестам рознь.
И еще порекомендую хороший доклад с FrontTalks по этой теме
https://youtu.be/ni9Zz1j8fI4?list=PLKaafC45L_SRke8G1qiE0ZTJovI0FYKRw
Имхо, при написании тестов на UI нужно почаще задавать себе вопрос "что именно мы хотим проверить". Для этого компонета я бы назвал самым важным тестом "он показывает все элементы, если поле фильтра пустое". Ключевое слово — "показывает".
Прежде всего компонент должен "работать" — т.е. быть пригодным для использования пользователем, пусть и с багами. Если он падает с ошибкой или не позволяет показать все данные, то на выходе получается принципиально не работающее приложение.
Вовсе не явно. В современном мире View слишком часто бывает важнее, чем Model, чтобы однозначно судить. Именно поэтому фильтрация тут связана с компонентом и не должна быть отдельно от него.
Почему? Все просто, отображение может быть связано непосредственно с этой моделью, и при ее, даже малейшейм, изменении компонент может быть отрисован по другому. Т.е. нам важен не результат этой функции, а именно то, что она принимает и как работает внутри именно этого компонента.
Есть куча примеров, когда проверка вместе с отображением просто необходима. Особенно если все запихать в один метод.
Вот здесь и появляется осознанность. Ведь если вынести код для сортировки, протестировать его отдельно, то необходимость тестирования всего вместе, снижается в разы. Уменьшается сложность и тесты становятся не такими хрупкими.
Когда вредно тестировать ваши компоненты