- PVSM.RU - https://www.pvsm.ru -
Здравствуйте, меня зовут Дмитрий Карловский и это продолжение традиционной рубрики "Почему мы так не любим писать тесты?". Короткий ответ: потому, что получаемые от них бонусы не перевешивают затрачиваемых усилий. Если это так, значит мы делаем что-то не правильно. Давайте разберёмся что же могло пойти не так..
Данная заметка выросла из главы "Заблуждения" лонгрида "Концепции автоматического тестирования" [1], посредством дополнения новыми заблужениями и аргументами.
Да, моки как правило исполняются быстрее, чем реальный код. Однако они прячут некоторые виды ошибок, из-за чего приходится писать больше тестов. Если фреймворк не умеет в ленивость и делает много лишней работы для поднятия дерева компонент (как, например, web-components [2] гвоздями прибитые к DOM или TestBed в Angular [3] создающий всё на свете при инициализации), то тесты существенно замедляются, но не так чтобы фатально. Если же фреймворк не рендерит, пока его об этом не попросят и не создаёт компоненты, пока они не потребуются (как, например, $mol_view [4]), компонентные тесты проходят не медленнеее модульных.
Да, если они исполняются в случайном порядке, то ошибка в логике может уронить кучу тестов от чего может быть не понятно откуда начинать копать. Это, к сожалению, распространённый анти-паттерн — найти все файлы с заданным расширением и выполнить их в случайном порядке, мол тесты же не зависят друг от друга. И это справидливо для модульных тестов.
Однако, исполнять компонентные тесты имеет смысл в порядке от менее зависимых компонент к более зависимым. Тогда первый же упавший тест покажет на источник проблемы. Остальные тесты обычно можно уже и не исполнять, что здорово экономит время прохождения тестов. Опять же, в MAM архитектуре [5] весь код (что продакшен, что тестовый) сериализуется в едином подярке. Это гарантирует, что тесты зависимости будут исполнены до тестов зависимого, а значит тот может смело полагаться на то, что зависимость работает корректно. Если вы используете иные инструменты — подумайте, как с их помощью можно выстраивать тесты в правильном порядке.
Тестировать надо логику. Редкий шаблонизатор (mustache [6], view.tree [7]) запрещает встраивать логику в шаблоны, а значит их тоже надо тестировать. Часто модульные тесты для этого не годятся (enzyme [8] в качестве редкого исключения), так что всё равно приходится прибегать к компонентным.
Да, иногда в тестовом сценари можно выделить эти шаги, но не стоит высасывать их из пальца, когда их нет. Зачастую сценарий имеет более простую (например, только 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
Так же как тест, который никогда не падал — ничего не тестирует. Так и ассерт, который никогда не кидал исключение — ничего не проверяет.
Не редко необходимо проверять правильно ли мы выполнили подготовку состояния поверкой в середине:
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 [9]) даже услужливо остановит отладчик в этом месте, позволяя вам сразу же приступить к исследованию проблемы.
Подводя итог, можно порекомендовать писать тесты не по шаблону "Given/When/Then", а как небольшое приключение, стартующее из абсолютной пустоты и посредством некоторого количества действий, проходящее через некоторое количество состояний, которые мы и проверяем.
Автор: vintage
Источник [10]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/277351
Ссылки в тексте:
[1] "Концепции автоматического тестирования": https://habrahabr.ru/post/351430/
[2] web-components: https://www.webcomponents.org/
[3] TestBed в Angular: https://angular.io/guide/testing#component-with-a-dependency
[4] $mol_view: https://github.com/eigenmethod/mol/tree/master/view
[5] MAM архитектуре: https://github.com/eigenmethod/mam
[6] mustache: https://mustache.github.io/
[7] view.tree: https://github.com/eigenmethod/mol/tree/master/view#viewtree
[8] enzyme: http://airbnb.io/enzyme/
[9] $mol_test: https://github.com/eigenmethod/mol/tree/master/test
[10] Источник: https://habrahabr.ru/post/353080/?utm_campaign=353080
Нажмите здесь для печати.