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

в 9:51, , рубрики: angular 2.0, AngularJS, javascript, jest, React, ReactJS, tdd, автоматизированное тестирование, Блог компании Wrike, Разработка веб-сайтов

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 => <li>{ e }</li>)
           
    return (
      <div>
          <input type="text" value={this.state.value} onChange={this.handleChange} />
          <ul>{ filteredElements }</ul>
      </div>
    );
  }
}

Работающий пример

Тесты для этого возможно выглядят как-то так:

test('should work', () => {
	const wrapper = shallow(<FilterForm />);
	
	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);

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

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

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

Автор: Wrike

Источник

Поделиться новостью

* - обязательные к заполнению поля