Ключом к быстрому продвижению является уверенность в том, что нужно вносить изменения.

Уверенность при внесении изменений зависит от покрытия тестами.

С тех пор, как мы это поняли, автоматические тесты стали необходимы. Это привело к массовому внедрению начальныхсниппетов.

Однако начать работу недостаточно.

Что не так с начальными сниппетами?

Большинство доступных примеров нацелены на то, чтобы быстро начать работу. Это верно для stackoverflow, codewhisperer, copilot, Bard и ChatGPT. В этом виноваты даже учебники по тестовым средам.

Почему?

  1. Они стремятся к наименьшему общему знаменателю читателей. Таким образом, они пропускают профессиональные детали.
  2. Они откладывают крутую часть кривой обучения. Вы встретите его только после того, как уже подсели на их фреймворк.
  3. Существуют противоположные мнения о лучших практиках и стилях. Поэтому учебники либо стараются не высказывать мнения, либо представляют свое мнение как факт.
    Оба способа избегают обсуждения и мыслей, которые оно должно вдохновлять.
  4. Многие оптимизации зависят от возможностей команды, культуры и конкретного случая.

Результаты обычно работают, но в лучшем случае студенческого уровня.

В следующих частях объясняются подводные камни нашей культуры, почемуона не работает и что выводит из него.
Для каки примеров кода— пропустить здесь.

Отсутствующая реальность

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

Реальность, которую мы упускаем, это:

База кода создается один раз, но тестируется непрерывнов течение всего срока службы.

Т.Т.Р. — Время восстановления

Кодовые базы обязательно будут расти, а тесты обречены на провал.
Но сколько времени нужно, чтобы восстановиться после неудачного теста?

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

  • Защищает ли тестовый код допустимую спецификацию, которую необходимо соблюдать?
    — или —
  • Сделала ли новая спецификация старую устаревшей?

Чем меньше расшифровок вам нужно — тем лучше ваш TTR. Есть еще лучшая реальность:

Что, если бы тесты могли точно сообщать, какие требования они защищают?

Что, если результат теста — это все, что вам нужно знать, что нужно исправить, не расшифровывая код?

Что, если бы он мог сделать это для вас с такой же ясностью даже через 6 месяцев?

Ну, вы можете пропустить эту самую разочаровывающую часть восстановления...

Культурный ключ

Наша промышленность находится в состоянии вечной неопытности. Проверьте эту ссылку Дядя Боб объясняет это. Таким образом, культура не может распространяться.

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

Хорошая культура способствует постоянному совершенствованию.

Отношение к неудачному тесту как к сбою — еще один культурный элемент.
Оптимизация TTR — культурный элемент.

Но как туда добраться?

Ведущие факторы

Что общего между уведомлением о сбое службы и уведомлением о неудачной сборке?

  • Оба уведомления нарушают ваш рабочий процесс и требуют обработки.
  • И то, и другое, вероятно, поможет вам начать курс по устранению неполадок, чтобы вывести контекст.
  • Любая минута, которую вы тратите на любую из них, — это тушение пожаров, а не прогресс.

Конечно, не такого размера пламя, но по сути — то же паршивое ощущение расточительства и недопонимания.

Волшебство происходит, когда команда видит сходство между неудачным тестом и простоем из-за простоя.

А уроки такие

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

  1. Стремитесь, чтобы выходные данные теста были достаточно подробными, чтобы избавить вас от чтения тестового кода, т. е. выплевывать весь нужный вам контекст с ошибками.
  2. Не думайте, что разработчик знает. Сделайте дополнительный шаг, чтобы описать вариант использования и контекст.
  3. Отвлеките когнитивную нагрузку от строительных лесов и приборов.
  4. Сосредоточьте когнитивную нагрузку на значимых деталях тестового примера.

Давайте начнем с наихудшего случая и будем улучшать его вверх несколькими маленькими шагами за раз.

Уровень (-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)?

Несколько вещей.

  1. Он отдает дань уважения директиве BDD «следовать английской формулировке API». Но он делает это таким образом, что не предоставляет никакой информации о тестовом примере или сценарии.
  2. Когда какой-либо шаг терпит неудачу — весь сценарий ломается, и любые последующие шаги не запускаются.
    (ℹ️) Иногдая вижу try-catchс попыткой очистки. Это ненамного лучше, потому что вам все равно придется выбрасывать или повторно выдавать ошибки, которые должен выдавать тест. Это заставляет вас работать на тестировщика, а не на вас.
  3. Когда сценарий терпит неудачу, все признаки, которые вы получаете, являются ошибкой. Когда ошибка необработанная — обычно она загадочна и универсальна и не дает много полезной информации.
  4. Когда несколько шагов в сценарии могут вызвать аналогичную ошибку, это сбивает с толку. Это затрудняет определение точки отказа.
    (ℹ️) Иногдая вижу consul.log вызовы, которые пытаются помочь определить точку в потоке тестирования. Но это снова работает для средства запуска тестов и библиотек утверждений, вместо того, чтобы позволить им работать на вас.
  5. Когда происходит сбой — ты понятия не имеешь, где виновник. Проблема в тестовом коде? т.е. тест не смог организовать, взаимодействовать или очистить после 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 всякий раз, когда хотите увидеть, что рассказывает ваше тестовое дерево :)

И последнее — есть несколько вещей, которые вы можете попросить исполнителя теста сделать для вас:

  1. выполнить настройку и очистку за вас.
  2. убедитесь, что если тест не пройден, очистка все равно произойдет
  3. провалить тест, установка или очистка которого не удалась
  4. уведомлять вас об ошибке, если тест не прошел при настройке или во время самого теста.

Устранение этих 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 работают с полученным объектом ответа и происходят синхронно.

Резюме

Чего мы достигли на данный момент?

  1. Test-runner гарантирует, что коды установки и отключения будут работать даже в случае сбоя сценария. Ни try-catch, ни console.log. Тест-раннер сообщит вам о каждом сбое, какой именно обработчик не сработал. Он отметит, является ли это крюком установки/снятия или самим тестом.
  2. Контекст и случай сообщаются с заголовками, которые будут напечатаны для каждого неудачного теста.
  3. Каждый контекст — это собственное замыкание со своими переменными. Вы можете использовать его для хранения состояния, относящегося к тестовому примеру.

(ℹ️) мокко позволяет удерживать состояние на this. Вам придется писать все ваши обработчики как функции старой школы, а не как стрелочные функции.
Лично я ненавижу использование this в JavaScript и предпочитаю удерживать свое состояние на членах замыканий, но вы…

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

Ну и что дальше?

Есть еще много уровней, чтобы выиграть.
Например:

  1. Вы можете освоить издевательство со шпионами и стабами.
    (ℹ️) Но будьте внимательны, чтобы не потеряться в объеме блоков и не пропустить проверку работоспособности системы в целом.
  2. Вы можете использовать фабрики тестовых случаев. Это позволяет вам выражать свои тесты, сосредоточив внимание исключительно на входных данных, выходных данных и ожиданиях. Гораздо лучше, чем копипастить целую структуру и халтурить внутри нее.
  3. Вы можете организовать фикстуры данных. Упростите их настройку и очистку, а также организуйте их как модули, чтобы вы могли импортировать их перехватчики установки/удаления, а также импортировать сами введенные данные. Это позволяет вам использовать и делать утверждения относительно логических сущностей вместо значений, жестко закодированных в вашем тесте.
  4. Вы можете составлять отчеты о покрытии и интегрировать обнаружение запахов кода. Затем вы можете использовать движущиеся трещотки качественных стержней.

Теперь ваша очередь учить меня:
- ЛМК: какую из них я должен покрыть первой?
- Покажите мне хлопками от 1 до 50, как вам понравилась эта работа.
💙 Я ценю ваше участие и время 💙

Особая благодарность Йонатану Кра, хорошему человеку, с которым я работал в прошлом, чье видео наконец заставило меня сесть и написать все это.