Ключом к быстрому продвижению является уверенность в том, что нужно вносить изменения.
Уверенность при внесении изменений зависит от покрытия тестами.
С тех пор, как мы это поняли, автоматические тесты стали необходимы. Это привело к массовому внедрению начальныхсниппетов.
Однако начать работу недостаточно.
Что не так с начальными сниппетами?
Большинство доступных примеров нацелены на то, чтобы быстро начать работу. Это верно для stackoverflow, codewhisperer, copilot, Bard и ChatGPT. В этом виноваты даже учебники по тестовым средам.
Почему?
- Они стремятся к наименьшему общему знаменателю читателей. Таким образом, они пропускают профессиональные детали.
- Они откладывают крутую часть кривой обучения. Вы встретите его только после того, как уже подсели на их фреймворк.
- Существуют противоположные мнения о лучших практиках и стилях. Поэтому учебники либо стараются не высказывать мнения, либо представляют свое мнение как факт.
Оба способа избегают обсуждения и мыслей, которые оно должно вдохновлять. - Многие оптимизации зависят от возможностей команды, культуры и конкретного случая.
Результаты обычно работают, но в лучшем случае студенческого уровня.
В следующих частях объясняются подводные камни нашей культуры, почемуона не работает и что выводит из него.
Для каки примеров кода— пропустить здесь.
Отсутствующая реальность
Мы живем в реальности, стремясь к финишной черте какой-то ценности, доставленной покупателю. Мы забываем, что этот финиш — произвольный круг в более длинной гонке.
Реальность, которую мы упускаем, это:
База кода создается один раз, но тестируется непрерывнов течение всего срока службы.
Т.Т.Р. — Время восстановления
Кодовые базы обязательно будут расти, а тесты обречены на провал.
Но сколько времени нужно, чтобы восстановиться после неудачного теста?
В этом смысле большая часть нашей работы заключается не в том, чтобы написать новый код, а в том, чтобы проникнуть в существующий код. Нам нужно расшифровать как производственный, так и тестовый коды, чтобы судить между ними:
- Защищает ли тестовый код допустимую спецификацию, которую необходимо соблюдать?
— или — - Сделала ли новая спецификация старую устаревшей?
Чем меньше расшифровок вам нужно — тем лучше ваш 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, пытаются имитировать английский язык. Это позволяет кодировщикам передавать информацию о тесте в коде. Но это также создает иллюзию, что вы можете отказаться от хороших названий, отправляя вас читать тестовый код при каждой неудаче.
Тестовый код остается кодом, а коды, как правило, имеют плохое отношение сигнал/шум. Даже то, что вы не считаете шумом, требует когнитивных усилий.
Третий — разделы разделены комментариями, недоступными для спецкорреспондентов.
Reporter — это часть, которую средство выполнения тестов использует для передачи результатов в выходные данные теста. Большинство репортеров подводят итоги неудач в конце.
Spec-reporter – это генератор отчетов, который печатает все дерево тестовых наборов, используя ваши описания и заголовки, обычно перед сводкой ошибок.
Он помечает каждый тест в дереве пометкой «годен/не годен/пропущен». Это позволяет прочитать повествование, которое рассказывает ваше тестовое дерево, и место, которое в нем занимают сбои.
Комментарии недоступны для тестовых репортеров — отсылают вас обратно к чтению тестового кода.
При определенной дисциплине этот отчет может выступать в качестве спецификации программного обеспечения. т.е. — удобочитаемая документация, которая появляется прямо из вашего тестового кода.
Отчеты о спецификациях хорошо работают в сочетании с незавершенными тестами. Использование ожидающих тестов — это добавление заголовков спецификаций без предоставления их обработчика тестов. В результате они отображаются в дереве как пропущенные.
Полезно записывать прямо в набор все случаи, которые вы хотите реализовать, и переходить к ним позже один за другим.
Spec reporter — репортер по умолчанию для мокко, встроенный to tap, поддерживаемый встроенным раннером в узле. Он работает с Jest с помощью пакета плагинов.
(ℹ️) Имейте в виду, что вам не нужно сначала писать тесты или работать с TDD/BDD, чтобы использовать отчеты о спецификациях. Запускайте Spec Reporter всякий раз, когда хотите увидеть, что рассказывает ваше тестовое дерево :)
И последнее — есть несколько вещей, которые вы можете попросить исполнителя теста сделать для вас:
- выполнить настройку и очистку за вас.
- убедитесь, что если тест не пройден, очистка все равно произойдет
- провалить тест, установка или очистка которого не удалась
- уведомлять вас об ошибке, если тест не прошел при настройке или во время самого теста.
Устранение этих 4 проблем приведет вас к уровню (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
и позволяет вкладывать его, как мокко, поэтому использование describe
делает их одинаковыми.
Этапы Упорядочитьи Действоватьвыполняются на асинхронных обработчиках before
, например ввести тестовые данные и выполнить HTTP-запрос. Затем все шаги Assert работают с полученным объектом ответа и происходят синхронно.
Резюме
Чего мы достигли на данный момент?
- Test-runner гарантирует, что коды установки и отключения будут работать даже в случае сбоя сценария. Ни try-catch, ни console.log. Тест-раннер сообщит вам о каждом сбое, какой именно обработчик не сработал. Он отметит, является ли это крюком установки/снятия или самим тестом.
- Контекст и случай сообщаются с заголовками, которые будут напечатаны для каждого неудачного теста.
- Каждый контекст — это собственное замыкание со своими переменными. Вы можете использовать его для хранения состояния, относящегося к тестовому примеру.
(ℹ️) мокко позволяет удерживать состояние на
this
. Вам придется писать все ваши обработчики как функции старой школы, а не как стрелочные функции.
Лично я ненавижу использованиеthis
в JavaScript и предпочитаю удерживать свое состояние на членах замыканий, но вы…
Дорога еще длинная. Это далеко не половина пути.
Это не первый пост о тестах (здесь ссылки на один, два и три). В этой серии может быть больше частей.
Ну и что дальше?
Есть еще много уровней, чтобы выиграть.
Например:
- Вы можете освоить издевательство со шпионами и стабами.
(ℹ️) Но будьте внимательны, чтобы не потеряться в объеме блоков и не пропустить проверку работоспособности системы в целом. - Вы можете использовать фабрики тестовых случаев. Это позволяет вам выражать свои тесты, сосредоточив внимание исключительно на входных данных, выходных данных и ожиданиях. Гораздо лучше, чем копипастить целую структуру и халтурить внутри нее.
- Вы можете организовать фикстуры данных. Упростите их настройку и очистку, а также организуйте их как модули, чтобы вы могли импортировать их перехватчики установки/удаления, а также импортировать сами введенные данные. Это позволяет вам использовать и делать утверждения относительно логических сущностей вместо значений, жестко закодированных в вашем тесте.
- Вы можете составлять отчеты о покрытии и интегрировать обнаружение запахов кода. Затем вы можете использовать движущиеся трещотки качественных стержней.
Теперь ваша очередь учить меня:
- ЛМК: какую из них я должен покрыть первой?
- Покажите мне хлопками от 1 до 50, как вам понравилась эта работа.
💙 Я ценю ваше участие и время 💙
Особая благодарность Йонатану Кра, хорошему человеку, с которым я работал в прошлом, чье видео наконец заставило меня сесть и написать все это.