Чтобы быстро продвигаться в рабочих задачах, необходимо иметь уверенность в том, что можно вносить изменения.
А уверенность в изменениях зависит от тестового покрытия.
С тех пор как мы это поняли, автоматические тесты стали просто необходимы. Это привело к массовому внедрению шаблонов для старта работы.
Однако начать работу недостаточно.
Что плохого в шаблонах для старта работы?
Большинство ресурсов/инструментов можно довольно быстро начать использовать в работе. Это относится и к stackoverflow, и к codewhisperer, и к copilot, и к Bard, и к ChatGPT. Даже руководства по тестовым фреймворкам.
Почему?
-
Они нацелены на наиболее низкий средний уровень пользователей, поэтому опускают сложные детали.
-
Они откладывают на потом крутую часть кривой обучения. Вы встретитесь с ней только после того, как уже подсели на фреймворк.
-
О лучших практиках и стилях существуют противоречивые мнения. Поэтому руководства либо стараются либо вовсе не выражать своего мнения, либо преподносят его как факт. Оба способа позволяют избежать дискуссии и размышлений, на которые она должна вдохновлять.
-
Многие оптимизации зависят от возможностей команды, культуры и конкретного кейса.
Результаты, как правило, рабочие, но в лучшем случае студенческого уровня.
В следующих частях мы расскажем о подводных камнях нашей культуры, почему она сломана и что из этого следует.
Отсутствующая реальность
Мы живем в реальности, в которой спешим на финишную прямую, чтобы поставить какую-то ценность клиенту. Мы забываем, что эта финишная черта — произвольный круг в длинной гонке.
Реальность, которую мы упускаем, такова:
Кодовая база создается один раз, но тестируется постоянно на протяжении всей своей жизни.
T.T.R. (Time to Recovery) — Время на восстановление
Кодовые базы не могут не расти, не могут не завершаться ошибкой.
Но сколько времени требуется для восстановления после теста с ошибкой?
В этом смысле большая часть наших усилий связана не с написанием нового кода, а с работой с существующим кодом. Нам нужно расшифровать как продакшен, так и тестовый код, чтобы ответить на вопросы:
-
Охраняет ли тестовый код действующую спецификацию, которую необходимо соблюдать?
— или —
-
Новая спецификация привела к устареванию старой?
Чем меньше вам нужно расшифровывать, тем лучше ваш TTR. Есть реальность еще лучше:
-
Что, если бы тесты могли точно сообщать, какие требования они защищают?
-
Что, если бы результаты тестов давали полную информацию о том, что нужно исправить, не прибегая к расшифровке кода?
-
Что, если бы они могли сделать тоже самое с такой же ясностью даже 6 месяцев спустя?
Ну, вы могли бы пропустить эту самую угнетающую часть восстановления…
Культурный ключ
Наша индустрия находится в состоянии вечной неопытности. Посмотрите выступление дяди Боба Мартина об этом. Культуре не удается распространиться.
Это означает, что многим из нас приходится изобретать одно и то же колесо снова и снова. И тот факт, что в итоге мы получаем схожие результаты, означает, что это правильный ответ.
Хорошая культура поощряет непрерывное совершенствование.
Отношение к тесту, завершившемуся с ошибкой, как к потере времени — еще один элемент культуры.
Оптимизация TTR — это элемент культуры.
Но как этого добиться?
Ведущие факторы
Что общего между уведомлением о перебое в работе сервиса и уведомлением о неудавшейся сборке?
-
Оба уведомления вторгаются в рабочий процесс, и их нужно обрабатывать.
-
И то, и другое, скорее всего, заставит вас приступить к поиску неисправностей, чтобы определить контекст.
-
Любая минута, которую вы тратите на них, — это время, потраченное на тушение пожара вместо совершения прогресса.
Конечно, пламя не такого размера, но по сути — то же самое паршивое ощущение расточительства и рассогласованности.
Волшебство происходит, когда команда видит, что между неудачным тестом и потерей времени есть сходство.
Какие можно сделать выводы
Когда вы относитесь к тесту с ошибкой как к простою, оптимизация TTR приводит к нескольким выводам:
-
Стремитесь к тому, чтобы вывод теста был достаточно полным, чтобы не приходилось читать код теста, то есть вместе с ошибками выдавайте весь необходимый контекст.
-
Не предполагайте, что разработчик знает. Добавьте дополнительный этап, чтобы описать сценарий использования и контекст.
-
Постарайтесь избавиться от автоматической кодогенерации и инструментария.
-
Сосредоточьте когнитивную нагрузку на значимых деталях тест-кейса.
Давайте начнем с худшего кейса и будем улучшать его понемногу, шаг за шагом.
Уровень (-5) — наивное начало
К сожалению, в роли консультанта я все еще сталкиваюсь с тест-сьютами в таком духе:
const myModule = ... //require or import the System-Under-Test it("should work", async () => { await setup...; await step1(...); expect(…)... . await step2(...); expect(…)... . await step3(...); expect(…)... . // and a load more of those in the same function });
Это тот минимум, который может предотвратить развертывание ошибочного кода.
В мире тестов это эквивалент неорганизованного кода большой вложенности с плохими именованиями и с большим количеством копипасты, без использования изоляции. Многие с отвращением назовут это скриптом, как будто скрипт — это не код (какой неубедительный самообман…)!
И все же многие команды не требуют, чтобы их тестовый код был чем-то бОльшим.
К концу этого поста вы должны быть в состоянии сказать, насколько это ужасно. Конечно, это лучше, чем полное отсутствие тестов, но в темпе нашей индустрии это быстро заблокирует ваш прогресс.
Что не так с уровнем (-5)?
Несколько проблем.
-
Он поддерживает принцип BDD «следовать английской формулировке API». Но он делает это таким образом, что не предоставляет никакой информации о тест-кейсе или сценарии.
-
Когда какой-либо шаг терпит неудачу — весь сценарий ломается, и все последующие шаги не выполняются. Иногда я вижу
try-catchс попыткой очистки. Это не намного лучше, потому что вам все равно придется вернуть ошибки, которые должен выдать тест. Это заставляет вас работать на тест-раннер вместо того, чтобы он работал на вас. -
Когда сценарий терпит неудачу, все, что вы получаете, — это ошибка. Когда ошибка необработанная — она обычно непонятная и общая и не дает много полезной информации.
-
Когда несколько шагов в сценарии могут привести к аналогичной ошибке, это сбивает с толку. Это затрудняет определение места сбоя. Иногда я вижу вызовы
consul.log, которые пытаются помочь определить точку в тестовом потоке. Но это снова работа для тест-раннера и библиотек для проверки утверждений вместо того, чтобы позволить им работать для вас. -
Когда происходит сбой, вы понятия не имеете, где находится виновник. Проблема в тестовом коде? То есть тест не смог организовать, взаимодействовать или убрать за тестируемой системой (SUT, System-Under-Test)? Или это потому, что SUT не сработал, то есть произошло разрушающее изменение в продакшен коде? Иногда я вижу комментарии
//arrangeили//setupи//cleanupили//teardown. Но это комментарии, видимые в тестовом коде, где цель — избавить нас от чтения тестового кода.
В случае неудачи вы, скорее всего, потратите драгоценное время на прыжки между тестовым и продакшен кодом, пытаясь разобраться во всей этой тарабарщине, прежде чем сможете понять, что из них правильно.
Устранение всех «иногда», упомянутых выше, может перевести вас с уровня (-5) на уровень (-2), но все равно оставит далеко позади.
Подобная структура приведет к провалу на любом приличном собеседовании.
Уровень 0 — Использование заголовков
Следующий уровень соответствует такому духу.
describe('my-module', () => { // async api_one(...) it('should do this when called with ...', async () => { ... it('should do that when called after ...', async () => { ... it('should throw that error when ...', async () => { ... // async api_two(...) it('should do this when called with ...', async () => { ... it('should do that when called after ...', async () => { ... it('should throw that error when ...', async () => { ...
В интернете можно найти множество подобных примеров, в руководствах и тестах опенсорсных пакетов, которые часто используются в качестве образца.
Здесь мы находимся в гораздо лучшем положении, чем в предыдущем фрагменте:
-
Он организован.
-
Есть очевидная мысль о матрице примеров.
-
Это основа для изоляции тестов — сбой в одном тесте не помешает выполнению других тестов.
-
При завершении любого теста ошибкой в выводе теста появляется объяснение на английском языке с указанием ошибки.
Что еще не так?
Во-первых — начните с малого. Порядок текста изменен на противоположный.
Если вы посмотрите на матрицу кейсов (или любую таблицу истинности, если на то пошло), то увидите, что сначала идут условия. В естественном порядке вы задаете предусловия, а затем ожидаете поведения. Вы не наблюдаете поведение, а затем подбираете для него предпосылки, при которых оно сработало… По крайней мере, на человеческом уровне — такая форма сбивает с толку.
А что, если в аналогичных условиях вы хотите проверить несколько требований? Должны ли вы повторять условия в каждом из их названий? Будете ли действовать асинхронно, проверяя каждый раз разные условия?
Во-вторых, когда тест завершается ошибкой, вам все равно приходится читать тестовый код.
Популярные библиотеки утверждений, такие как should, expect или chai, пытаются имитировать английский язык. Это позволяет кодерам передавать информацию о тесте в коде. Но это также создает иллюзию, что можно отказаться от хороших заголовков, заставляя вас читать тестовый код при каждой ошибке.
Тестовый код — это все равно код, а код, как правило, имеют плохое соотношение сигнал / шум. Даже то, что вы можете не считать шумом, требует когнитивных усилий.
Третье — секции разделены комментариями, которые недоступны для spec-reporters.
Reporter — это часть, которую использует тест-раннер для выдачи результатов в тестовый вывод. Большинство reporters в конце выдают сводку об ошибках.
spec-reporter — это репортер, который выводит все дерево тест-кейсов, используя описания и заголовки, обычно до сводки об ошибках.
Он помечает каждый тест в дереве записями «pass/fail/skip». Это помогает прочитать историю, которую рассказывает дерево тестов, и место, которое в ней занимают ошибки.
Комментарии недоступны для составителей отчетов о тестировании, что возвращает вас к чтению кода тестов.
При определенной дисциплине этот отчет может выступать в качестве спецификации программного обеспечения. Т.е. читаемая документация, вытекающая прямо из вашего тестового кода.
Spec-reporters хорошо работают в сочетании с отложенными тестами. Использование отложенных тестов — это добавление заголовков спецификаций без предоставления их обработчика. В результате они появляются в дереве как пропущенные (skipped).
Это полезно для того, чтобы записывать непосредственно в тест-сьют все кейсы, которые вы собираетесь реализовать, и добираться до них позже по одному.

Я всегда буду ностальгировать по чистому виду и ощущениям от Mocha.js.
Spec reporter — это стандартный reporter для Mocha, встроенный в tap и поддерживаемый встроенным раннером в Node. Он работает с Jest с помощью пакета плагинов.
Имейте в виду, что вам не нужно сначала писать тесты или работать с TDD/BDD, чтобы использовать spec-reporters. Запускайте spec-reporters, когда захотите посмотреть, о чем рассказывает ваше дерево тестов 🙂
И последнее — вы можете попросить тест-раннер сделать за вас следующее:
-
Выполнить за вас настройку и очистку.
-
Убедиться, что если тест провалился, очистка все равно произойдет.
-
Завершить тест ошибкой, если его настройка или очистка не удалась.
-
Уведомлять вас об ошибке, если тест завершился ошибкой при настройке или во время самого выполнения теста.
Исправив эти четыре проблемы, вы перейдете на уровень (4).
Уровень (4) — базовый профессиональный
describe('my-module', () => { context('when used in cased A…', () => { before(async () => { ... //case setup it('should fulfil requirement 1…', () => { ... it('should fulfil requirement 2…', () => { ... ... after(async () => { ... //cleanup }) context('when used in case B…', () => { before(async () => { ... //case setup it('should fulfil requirement 1…', () => { ... it('should fulfil requirement 2…', () => { ... ... after(async () => { ... //cleanup
Mocha BDD рекомендует использовать api context для описания контекста кейса. Фактически, это псевдоним для describe. Jest поддерживает только describe и позволяет вложить его, как mocha, так что использование describe создает единообразие между ними.
Этапы Arrange и Act выполняются на асинхронных хуках before — например, внести тестовые данные и выполнить HTTP-запрос. Затем все этапы Assert работают с полученным объектом ответа и происходят синхронно.
Итог
Чего мы достигли на данный момент?
-
Тест-раннер обеспечивает выполнение кодов подготовки и очистки даже при неудачном сценарии. Никаких try-catch, никакого console.log. При каждой ошибке тест-раннер сообщит вам, какой именно обработчик не сработал. Он отметит, был ли это хук подготовки/очистки или сам тест.
-
Контекст и кейс передаются с помощью заголовков, которые будут выведены для каждого неудачного теста.
-
Каждый контекст — это отдельное замыкание со своими переменными. Вы можете использовать его для хранения состояния, которое имеет отношение к тест-кейсу.
Mocha позволяет хранить состояние на this. Вам придется писать все свои обработчики как олдскульные функции, а не как стрелочные функции.
Лично мне не нравится использование this в JavaScript, и я предпочитаю хранить состояние в замыканиях, но что поделаешь…
Итак, что дальше?
Есть еще много уровней, на которых можно набрать очки.
Например:
-
Вы можете освоить мокинг с помощью шпионов (spies) и стабов (stubs). Но будьте осторожны, чтобы не заблудиться в области ответственности и не пропустить тестирование работы системы в целом.
-
Вы можете использовать фабрики тест-кейсов. Это позволит вам выразить тесты, сосредоточившись исключительно на входных данных, выходных данных и ожиданиях. Это гораздо лучше, чем копипастить целую структуру и ковыряться в ней.
-
Вы можете подготовить фикстуры с данными. Облегчите их установку и очистку, организуйте их как модули, чтобы можно было импортировать их хуки подготовки/очистки, а также импортировать сами вводимые данные. Это позволяет использовать и делать утверждения, оперирущие логическими сущностями, а не значениями, жестко закодированными в тесте.
-
Вы можете создавать отчеты о покрытии и интегрировать обнаружение проблемного кода. Затем можно использовать прогресс-бар по качеству.
Приглашаем всех тестировщиков на завтрашний открытый урок, на котром познакомимся с основами популярного фреймворков для написания тестов на JavaScript — Mocha и библиотеки утверждений Chai. Напишем пару Unit и API тестов. Записаться на урок можно на странице курса.
ссылка на оригинал статьи https://habr.com/ru/articles/780346/
Добавить комментарий