Comments 11
Текущий проект на 300М пользователей, порядка 50М финансовых транзакций ежедневно, тысячи юнит-тестов, е2e, включая горячо любимые стейкхолдерами executable specifications на Gherkin. Тестеров нет вообще, техническая команда 3.5 землекопа. Каждый раз читая критику тестирования всё пытаюсь понять, что мы делаем не так.
Скорей всего зависит от проекта и подхода. Но в большинстве случаев думается что rtl тестирует лишнее.
Пример: есть много видов карточек с полями формы. Что rtl тестировать ? Заполнение формы и правильный post? Но тогда мы большинство функций и компонентов будем тестировать много много раз . А оно нам надо?
Еще ензим ругает автор за скорость. Вот rtl точно тормоз . Чуть сложный кейс тестирования и даже jest падает с таймаутом. Приходиться ручками ставить в каждом тесте таймауты ожидания. А поиск... это вообще нечто что бы найти инпут он столько время тратит. А если их много?
Еще минус rtl .... это поддержка тестов. Поменялось в зависимостях .. хуках. И все приплыли, что бы найти что поправить в тестах уходит уева куча времени (когда не по тдд а просто сделал задачу закрепил в тестах). Это самое важное в тестах.
Cypress и подобные инструменты не предоставляют встроенных средств для адекватного мокирования серверных данных.
// any request to "/search/*" endpoint will
// automatically receive an array with two book objects
cy.intercept('/search/*', [{ item: 'Book 1' }, { item: 'Book 2' }]).as(
'getSearch'
)
cy.get('[data-testid="autocomplete"]').type('Book')
// this yields us the interception cycle object
// which includes fields for the request and response
cy.wait('@getSearch').its('request.url').should('include', '/search?query=Book')
cy.get('[data-testid="results"]')
.should('contain', 'Book 1')
.and('contain', 'Book 2')
Результаты этих тестов нужно было где-то хранить. Для такой задачи решили использовать Allure.
В cypress хранение "из коробки", при этом можно повторно "проигрывать" тесты, Test Replay
Unit-тесты позволяют выявлять баги кода на ранних этапах + проверка, что код соответствует стандартам, на следующих уровнях будет бОльшая уверенность в коде + экономия времени + легче найти неочевидные баги, unit-тесты нужны, на следующих уровнях будут проводиться иные типы тестирования, которые будут отлавливать иной пласт багов
Обычно проверкой соответствия стандартам всё таки занимается линтер. Вообще, в статье не предлагается же полностью отказываться от юнит тестов, несмотря на кликбейтный заголовок. Идея в том, что юнит-тестирование не очень хорошо подходит для слоя представления.
Да всё оно подходит, если применять так, как действительно нужно
Условный пример: есть у вас компонент. Пусть будет “stepper”. На вход ему приходят массив элементов и, допустим, индекс активного элемента. Индекс активного элемента можно не передавать, в таком случае, по дефолту активный элемент — первый. Есть кнопки «вперёд» и «назад». Если индекс активного передан — при нажатии на соответствующую кнопку будем кидать событие наверх, что индекс меняется с такого-то на такой-то. Если не передан — будем использовать локальную переменную. У активного элемента есть свои плюшки — допустим, какой-то класс, который красит текст этого элемента в другой цвет (это уже сам UI). И т.д. и т.п.
Неужели этот компонент не нуждается в тестировании? Или, хотите сказать «ну то, что ты описал, это ведь логика, а не представление»? И, да, будете правы. Но представление компонента без его логики это ничто (конкретно про приведённый мой пример). Нет смысла тестировать отдельно представление (условно, отрендерили с заранее заданным состоянием, проверили, что все классы на месте и всё). Здесь тест будет иметь смысл только если тестирование будет затрагивать и логику, и представление. Ну и конечно же, юнит-теста недостаточно будет для этого компонента. Так же понадобится интеграционный, чтобы проверить случай с переданным активным индексом, работой событий и изменением этого индекса, а в последствии и изменением представления этого компонента. PS пример лишь условный, первое, что в голову пришло. Думаю, можно было выдумать что-то более подходящее, но не суть
Но частично суть передана в правильном направлении — покрытие, как таковое, не отвечает ни за качество, ни за работоспособность протестированного кода. Всегда и везде будет баланс. И только с балансом есть понимание, что можно и нужно использовать различные инструменты и подходы. У каждого из них своя цель
В вашем примере всё просто можно протестировать на уровне функционального теста. В общем, проблема в том, что во фронтенде компоненты очень редко работают в изоляции, по большому счету это вообще не возможно, если не отделять их в отдельные фреймы. Поэтому проще и эффективнее тестировать уже готовое представление, а не его составные части. Это вообще не значит, что нельзя в принципе тестировать юнитами компоненты, просто зачастую это не очень эффективно.
А так вы правы - баланс и здравый смысл важнее чем абстрактные метрики, типа покрытия. Важно помнить, что любая метрика, которая становится самоцелью - теряет смысл и перестает быть показательной.
Получив мифические 100%, мы фактически покрыли код позитивными сценариями и запушили в продакшен.
Только позитивными?! А чо, так можно было?
Как долго храните результаты выполнения playwright? Просто с почти таким же подходом разрастание логов от него кушает много драгоценной памяти на хранение.
Код в сценарии на Cypress выглядит не очень дружелюбно по отношению к разработчику. Приходится оперировать большим количеством строковых аргументов, а это легко может привести к путанице и семантическим ошибкам.
Мы выносим селекторы в объект - это позволяет их переиспользовать и при изменении вносить правки в одном месте, а не бегать по всем тестам.
export const selectors = {
tabsPanel: '.filters-tabs',
tab: '.filters-tabs .ant-tabs-tab',
tabActive: '.filters-tabs .ant-tabs-tab.ant-tabs-tab-active',
}
cy.get(locators.tabsPanel).should('be.visible');
События также удобно переиспользовать
export class FiltersEvents {
clickOnDrawerButtonSubmit() {
cy.get(locators.drawerButtonSubmit).click();
}
clickOnDrawerButtonSaveAsNew() {
cy.get(locators.drawerButtonSaveAsNew).click();
}
clickOnDrawerButtonReset() {
cy.get(locators.drawerButtonReset).click();
}
//...
openDrawerAndCreateProjectFilter(numProjects: number = 1) {
this.clickOnOpenFiltersDrawer();
this.clickAtFormFieldProjects();
this.selectProject(numProjects);
this.clickOnDrawerButtonSubmit();
}
}
Тесты: 100% покрытия и юниты не нужны