Когда вредно тестировать ваши компоненты
Автоматизированные тесты — это хорошо. Проект, который на 100% покрыт тестами, преподносит покрытие как преимущество. Но…
Думаю, что в этом процессе нет осознанности. А она сильно облегчает жизнь. Возможно, что половина тестов в вашем проекте не только бесполезна, более того — несет вред. Ниже расскажу о том, какие тесты писать не нужно.
Рассмотрим компонент на ReactJS:
class FilterForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
frameworks: ['react', 'angular', 'ember', 'backbone']
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
const filteredElements = this.state.frameworks
.filter(e => e.includes(this.state.value))
.map(e => { e } )
return (
{ filteredElements }
);
}
}
Работающий пример
Тесты для этого возможно выглядят как-то так:
test('should work', () => {
const wrapper = shallow( );
expect(wrapper.find('li').length).to.equal(4);
wrapper.find('input').simulate('change', {target: {value: 'react'}});
expect(wrapper.find('li').length).to.equal(1);
});
Давайте подумаем, что этот код тестирует. Это важно. А тестирует он по большому счету эту строку:
.filter(e => e.includes(this.state.value))
Конечно, мы тестируем еще и то, как ReactJS рендерит изменения и как навешены события на DOM, как они обрабатываются и прочее. Но именно эта строка здесь явно главная.
А так ли нам нужно тестировать весь компонент? При этом подходе к написанию кода мы не можем иначе. У нас нет выбора.
В конкретном примере это не вызывает проблем, но почти всегда компоненты с течением времени становятся сильно сложнее. Логика и DOM компоненты перемещаются из одного компонента в другой.
Например, следующим шагом имеет смысл разбить один компонент на два: форма ввода и лист. Каждый элемент листа тоже имеет смысл сделать отдельным компонентом. После таких изменений придется менять тесты. Вернее не менять, а выкидывать и писать заново. Они станут бесполезными, так как завязаны на DOM. Возникает вопрос: «А зачем такие тесты нужны?!»
Тесты, которые нужно менять после каждого изменения в коде, бесполезны и даже вредны, поскольку требуют затрат на сопровождение и не приносят никакой пользы.
Конечно, можно использовать некую абстракцию, типа PageObject (eng), но проблема не будет решена полностью.
Дело в том, что сам компонент написан плохо. Код для фильтрации нельзя использовать повторно. Если нам понадобится фильтрация в другом компоненте, то при таком подходе придется писать и тестировать все заново.Нарушен принцип единственности ответственности.
Помимо логики для вывода в компоненте есть еще и логика для фильтрации. Решение есть, и оно очень простое. Можно перенести код, который отвечает за фильтрацию в другое место и тестировать там.
export function filter(list, value){
return list.filter(e => e.includes(value))
}
expect(filter(list, ‘react’).length).to.equal(1);
Теперь нам не нужно специальных инструментов и рендеринга компонент. Меняйте компоненты сколько угодно, тесты останутся актуальными и помогут вам при каждом коммите.
Если сильно хочется, то можно тестировать и сам компонент. Но что это дает? Вопрос открытый.
Напрашивается вывод: Сложность написания тестов — признак плохого дизайна, повод для того, чтобы еще раз подумать над архитектурой кода. Вместо того, чтобы изворачиваться и писать неподдерживаемые тесты с рендерингом в псевдобраузере, проще навести порядок и писать простые понятные юнит тесты, которые дополнительно документируют ваше приложение.