Изоляция, fixtures и асинхронность
Убираем зависимость от порядка, контролируем исходные данные и ждём завершения действий без случайных задержек.
- Создавать независимое состояние для каждого сценария.
- Проверять копирование ответа и отсутствие общей мутации.
- Управлять асинхронным шагом через await и детерминированный барьер.
Зелёный по одному, красный вместе
Если тест публикации меняет общий store, следующий тест может получить 409 вместо ожидаемого 200. По отдельности оба проходят, а в наборе возникает зависимость от порядка. Увеличение тайм-аута не восстановит исходный draft. Причина в общей изменяемой фикстуре, а не в скорости машины. Каждый тест этого курса вызывает createStore(), получая три известных черновика и собственный счётчик id.
Fixture — это договор подготовки и уборки состояния, а не случайный файл с данными. Для модели в памяти достаточно новой фабрики. Для реального API может понадобиться создание уникальной записи и последующее удаление по её id; для БД — изолированная схема или откатываемая транзакция; для браузера — отдельный контекст. Эти механизмы решают разные части проблемы: новый browser context сам по себе не очищает общий серверный объект. В проектном плане назовите, какой именно ресурс принадлежит тесту.
Два независимых состояния
const first=createStore();
const second=createStore();
first.publishTask('t1',anna);
assert.equal(first.readTask('t1',anna).body.state,'published');
assert.equal(second.readTask('t1',anna).body.state,'draft');Копия ответа тоже часть контракта
Метод readTask и успешный createTask возвращают копии. Если тест случайно изменит result.body.title, внутренняя задача не должна измениться. Это можно проверить отдельным чтением по тому же id. Иначе тест или вызывающий код получает скрытый доступ к хранилищу и обходит правила. Snapshot также возвращает копии, чтобы ожидаемое состояние не менялось вместе с действием. Ссылка на тот же объект вместо независимого before сделала бы сравнение до/после бессмысленным.
Не замораживайте все входы без понимания. Object.freeze полезен для маленьких доверенных actor-fixtures, но он не превращает произвольный вложенный объект в глубокую неизменяемую копию. Для сравнения снимков используются независимые структуры. При отказе задача должна остаться точно прежней, включая title, автора и число записей. После успеха изменение ограничено ожидаемым полем. Такой оракул лучше, чем проверка только длины массива.
Асинхронность должна принадлежать тесту
publishLater возвращает Promise. Без await тест может завершиться раньше действия: runner увидит зелёный результат, а ошибка или мутация произойдёт позже. Верните Promise из теста или используйте async/await, затем проверяйте окончательное состояние. Не смешивайте callback-стиль и возвращаемый Promise, если в этом нет необходимости: один понятный способ завершения проще анализировать.
В diagnostics.test.mjs есть управляемый барьер. Пока Promise gate не разрешён, задача остаётся draft. После release() и await pending она становится published. Время не измеряется, фиксированного sleep нет: тест задаёт причинный порядок. Это хороший способ проверить промежуточное состояние без предположения о мощности процессора. Барьер локален тесту, поэтому параллельный сосед не должен его разрешать.
Параллельный запуск полезен, когда ресурсы действительно изолированы. Последовательное выполнение может скрыть конфликт общих данных, а параллельное быстро его проявит. Но один стабильный запуск не доказывает отсутствие всех гонок. В учебном наборе нет реальных сетевых ретраев и конкурентной БД; контролируемый Promise проверяет конкретную очередность доменного действия. Для реального проекта дополните схему ответственностью за данные, очисткой при сбое и уникальными идентификаторами.
Источники и проверка фактов
Node.js · Test lifecycle and asynchronous activityЗавершение асинхронного теста и изоляция подготовки.Playwright · IsolationГраница изоляции браузерных контекстов.Node.js · ECMAScript modulesЯвные файлы модулей и imports.Авторский учебный материал · Редакция QA Orbit · версия 2026-10-28.1