Заблуждения об автоматическом тестировании

в 8:15, , рубрики: $mol, $mol_test, javascript, testing, TypeScript, unit-testing, Программирование, тестирование, Тестирование IT-систем, Тестирование веб-сервисов, тестирование по, тестирование приложений

Здравствуйте, меня зовут Дмитрий Карловский и это продолжение традиционной рубрики "Почему мы так не любим писать тесты?". Короткий ответ: потому, что получаемые от них бонусы не перевешивают затрачиваемых усилий. Если это так, значит мы делаем что-то не правильно. Давайте разберёмся что же могло пойти не так..

Картинка для привлечения внимания

Данная заметка выросла из главы "Заблуждения" лонгрида "Концепции автоматического тестирования", посредством дополнения новыми заблужениями и аргументами.

Модульные тесты быстрее компонентных

Да, моки как правило исполняются быстрее, чем реальный код. Однако они прячут некоторые виды ошибок, из-за чего приходится писать больше тестов. Если фреймворк не умеет в ленивость и делает много лишней работы для поднятия дерева компонент (как, например, web-components гвоздями прибитые к DOM или TestBed в Angular создающий всё на свете при инициализации), то тесты существенно замедляются, но не так чтобы фатально. Если же фреймворк не рендерит, пока его об этом не попросят и не создаёт компоненты, пока они не потребуются (как, например, $mol_view), компонентные тесты проходят не медленнеее модульных.

С компонентными тестами сложно локализовать ошибку

Да, если они исполняются в случайном порядке, то ошибка в логике может уронить кучу тестов от чего может быть не понятно откуда начинать копать. Это, к сожалению, распространённый анти-паттерн — найти все файлы с заданным расширением и выполнить их в случайном порядке, мол тесты же не зависят друг от друга. И это справидливо для модульных тестов.

Однако, исполнять компонентные тесты имеет смысл в порядке от менее зависимых компонент к более зависимым. Тогда первый же упавший тест покажет на источник проблемы. Остальные тесты обычно можно уже и не исполнять, что здорово экономит время прохождения тестов. Опять же, в MAM архитектуре весь код (что продакшен, что тестовый) сериализуется в едином подярке. Это гарантирует, что тесты зависимости будут исполнены до тестов зависимого, а значит тот может смело полагаться на то, что зависимость работает корректно. Если вы используете иные инструменты — подумайте, как с их помощью можно выстраивать тесты в правильном порядке.

Шаблоны тестировать не надо

Тестировать надо логику. Редкий шаблонизатор (mustache, view.tree) запрещает встраивать логику в шаблоны, а значит их тоже надо тестировать. Часто модульные тесты для этого не годятся (enzyme в качестве редкого исключения), так что всё равно приходится прибегать к компонентным.

Тесты должны соответствовать шаблону Given/When/Then

Да, иногда в тестовом сценари можно выделить эти шаги, но не стоит высасывать их из пальца, когда их нет. Зачастую сценарий имеет более простую (например, только Then блок) или сложную (Given/Check/When/Then) структуру. Несколько примеров:

Чистые функции часто имеют только блок Then:

console.assert( Math.pow( 2 , 3 ) === 8 ) // Then

Не менее часто действие (When) заключается именно в подготовке состояния (Given):

component.setState({ name : 'Jin' }) // Given/When
console.assert( component.greeting === 'Hello, Jin!' ) // Then

А бывает, что и проверка не нужна, ибо сам факт успешного выполнения кода достаточен:

ensurePerson({ name : 'Jin' , age : 33 })

Подобный же код совершенно бессмысленный:

const component = new MyComponent // Given
expect( component ).toBeTruthy() // Then

Так же как тест, который никогда не падал — ничего не тестирует. Так и ассерт, который никогда не кидал исключение — ничего не проверяет.

В правильном тесте должен быть только один assert

Не редко необходимо проверять правильно ли мы выполнили подготовку состояния поверкой в середине:

wizard.nextStep().nextStep() // Given
console.assert( wizard.passport.isVisible === false ) // Check

wizard.toggleRegistration() // When
console.assert( wizard.passport.isVisible === true ) // Then

Разбивать этот тест на два следующих нельзя, так как второй неявно полагается на состояние создаваемое первым:

wizard.nextStep().nextStep() // When
console.assert( wizard.passport.isVisible === false ) // Then

wizard.nextStep().nextStep() // Given
wizard.toggleRegistration() // When
console.assert( wizard.passport.isVisible === true ) // Then

Представье, что требования изменились и теперь форму регистрации мы по умолчанию показываем:

wizard.nextStep().nextStep() // When
console.assert( wizard.passport.isVisible === true ) // Then

Теперь, если toggleRegistration реализован так, что, например, использует своё состояние для ускорения работы, то он будет проходить второй тест, по прежнему возвращая true и получится, что первое применение toggleRegistration не будет ничего менять в форме:

isPassportVisible = false
toggleRegistration() {
     this.passport.isVisible = this.isPassportVisible = !this.isPassportVisible
}

В варианте с дополнительной проверкой дефолтного состояния мы бы словили упавший тест в этом случае. Более того, не стоит бояться писать и более длинные сценарии, если следующий шаг базируется на состоянии предыдущего.

wizard.nextStep().nextStep() // When
console.assert( wizard.passport.isVisible === false ) // Then

wizard.toggleRegistration() // When
console.assert( wizard.passport.isVisible === true ) // Then

wizard.toggleRegistration() // When
console.assert( wizard.passport.isVisible === false ) // Then

Обычно аргументом против такого подхода выступает сложность понимания какой из ассертов упал. Но постойте, никто же не заставляет вас использовать такой инструмент тестирования, который не даёт исчерпывающей информации о месте падения теста. Хороший же инструмент (например, $mol_test) даже услужливо остановит отладчик в этом месте, позволяя вам сразу же приступить к исследованию проблемы.

Подводя итог, можно порекомендовать писать тесты не по шаблону "Given/When/Then", а как небольшое приключение, стартующее из абсолютной пустоты и посредством некоторого количества действий, проходящее через некоторое количество состояний, которые мы и проверяем.

Автор: vintage

Источник

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


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js