Когда вредно тестировать ваши компоненты

image

Автоматизированные тесты — это хорошо. Проект, который на 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);
    

    Теперь нам не нужно специальных инструментов и рендеринга компонент. Меняйте компоненты сколько угодно, тесты останутся актуальными и помогут вам при каждом коммите.

    Если сильно хочется, то можно тестировать и сам компонент. Но что это дает? Вопрос открытый.

    Напрашивается вывод: Сложность написания тестов — признак плохого дизайна, повод для того, чтобы еще раз подумать над архитектурой кода. Вместо того, чтобы изворачиваться и писать неподдерживаемые тесты с рендерингом в псевдобраузере, проще навести порядок и писать простые понятные юнит тесты, которые дополнительно документируют ваше приложение.

    Комментарии (0)

    © Habrahabr.ru