Тестирование
Deno предоставляет встроенное средство запуска тестов для написания и запуска тестов на JavaScript и TypeScript. Это позволяет легко убедиться, что ваш код надежен и работает так, как ожидается, без необходимости устанавливать дополнительные зависимости или инструменты. Средство запуска deno test позволяет гибко управлять разрешениями для каждого теста, гарантируя, что код не будет выполнять ничего неожиданного.
Помимо встроенного средства запуска тестов, с Deno можно использовать и другие средства запуска тестов из экосистемы JS, например Jest, Mocha или AVA. Переносите существующий набор тестов Jest? См. раздел Перенос с Jest.
Deno.test и node:test
Deno в равной степени поддерживает два API для тестирования: собственный Deno.test и встроенный модуль Node node:test. Оба API имеют полноценную поддержку. deno test обнаруживает, запускает и отображает результаты тестов, написанных с использованием любого из них; основные функции, такие как покрытие кода и фильтрация по имени, работают одинаково независимо от выбранного API, и оба можно использовать в одном проекте. Ни один из них не поддерживается лучше другого.
Разница заключается в API, а не в уровне поддержки:
-
Deno.testне требует импорта и предоставляет специфичные для Deno параметры, например разрешения для каждого теста и переключатели санитайзеров op и ресурсов. -
node:test(import { test } from "node:test") использует API тестирования Node, поэтому написанный с его помощью набор тестов работает в Node.js без изменений.
Используйте node:test, если вам нужен набор тестов, переносимый между Deno и Node, или если вы переносите проект с Node; используйте Deno.test, если вам нужны удобство и параметры, характерные для Deno.
Средства имитации из node:test тоже работают: реализованы и mock.timers (фиктивные таймеры для setTimeout, setInterval, Date и модулей node:timers), и mock.module (замена экспортов модуля на время выполнения теста), поэтому наборы тестов, использующие их, работают без изменений.
Написание тестов
Чтобы определить тест в Deno, используйте функцию Deno.test(). Вот несколько примеров:
import { assertEquals } from "jsr:@std/assert";
import { delay } from "jsr:@std/async";
Deno.test("simple test", () => {
const x = 1 + 2;
assertEquals(x, 3);
});
Deno.test("async test", async () => {
const x = 1 + 2;
await delay(100);
assertEquals(x, 3);
});
Deno.test({
name: "read file test",
fn: () => {
const data = Deno.readTextFileSync("./somefile.txt");
assertEquals(data, "expected content");
},
});
Если вы предпочитаете стиль проверок expect, похожий на "jest", стандартная библиотека Deno предоставляет функцию expect, которую можно использовать вместо assertEquals:
import { expect } from "jsr:@std/expect";
import { add } from "./add.js";
Deno.test("add function adds two numbers correctly", () => {
const result = add(2, 3);
expect(result).toBe(5);
});
Запуск тестов
Для запуска тестов используйте подкоманду deno test.
Если запустить эту подкоманду без имени файла или каталога, она автоматически найдет и выполнит все тесты в текущем каталоге и его подкаталогах, соответствующие шаблону {*_,*.,}test.{ts, tsx, mts, js, mjs, jsx}. Кроме того, любой файл скрипта в каталоге с именем __tests__ считается файлом теста независимо от его имени.
# Run all tests in the current directory and all sub-directories
deno test
# Run all tests in the util directory
deno test util/
# Run just my_test.ts
deno test my_test.ts
# Run test modules in parallel
deno test --parallel
# Pass additional arguments to the test file that are visible in `Deno.args`
deno test my_test.ts -- -e --foo --bar
# Provide permission for deno to read from the filesystem, which is necessary
# for the final test above to pass
deno test --allow-read=. my_test.ts
Шаги теста
Deno также поддерживает шаги теста, которые позволяют разбивать тесты на более мелкие, удобные для управления части. Это полезно для выполнения операций подготовки и очистки в рамках теста:
Deno.test("database operations", async (t) => {
using db = await openDatabase();
await t.step("insert user", async () => {
// Insert user logic
});
await t.step("insert book", async () => {
// Insert book logic
});
});
Тайм-ауты
С помощью параметра timeout можно задать максимальную длительность отдельных тестов. Если тест превысит установленный срок, он будет помечен как неудачный. Обнаруживаются как зависания асинхронного кода (промис, который никогда не разрешается), так и бесконечные синхронные циклы (while (true) {}).
Deno.test({
name: "completes within deadline",
timeout: 5000, // 5 seconds
async fn() {
const response = await fetch("https://example.com");
await response.body?.cancel();
},
});
Если время ожидания теста истечет, следующий тест в том же файле все равно запустится в обычном режиме.
Если задать для timeout значение 0 или не указывать его, тест будет выполняться без ограничения по времени.
Повторные попытки и повторение тестов
Два параметра для каждого теста управляют количеством его запусков. retry повторно запускает неудачный тест и считает его пройденным, если успешна хотя бы одна попытка; это полезно для тестов, которые иногда дают сбой. repeats запускает тест несколько раз и требует успешного завершения каждого запуска; это помогает выявлять нестабильность:
Deno.test({
name: "flaky network call",
retry: 3, // re-run up to 3 times; pass if any attempt passes
async fn() {
const response = await fetch("https://example.com");
await response.body?.cancel();
},
});
Deno.test({
name: "must be deterministic",
repeats: 5, // run 5 times; fail if any run fails
fn() {
// ...
},
});
Эти два параметра можно сочетать: при каждом повторении теста могут выполняться повторные попытки. Каждая попытка заново запускает хуки beforeEach и afterEach и создает новую исходную точку для проверки утечек, поэтому утечка ресурса приводит к повторной попытке так же, как и любая другая ошибка.
Флаги --retry и --repeats задают значения по умолчанию для всего запуска. Параметр, установленный для отдельного теста, имеет приоритет, в том числе явное значение 0, которое отключает заданное флагом значение по умолчанию:
deno test --retry=2
Параметризованные тесты
Deno.test.each запускает одно и то же тело теста для набора вариантов. Для каждого варианта регистрируется отдельный полноценный тест, поэтому результаты каждого варианта отображаются отдельно, и его можно отфильтровать или запустить самостоятельно.
Варианты в виде массивов передаются как позиционные аргументы. Шаблон имени подставляет значения вариантов с помощью токенов в стиле printf (%s, %d/%i, %f, %j, %o/%O), используемых по порядку; для индекса варианта, начинающегося с нуля, предусмотрен токен %#:
import { assertEquals } from "jsr:@std/assert";
Deno.test.each([
[1, 1, 2],
[2, 3, 5],
])("add(%i, %i) = %i", (a, b, expected) => {
assertEquals(a + b, expected);
});
Варианты в виде объектов или примитивных значений передаются как один аргумент. Для вариантов-объектов запись $key (и $key.nested) в шаблоне подставляет соответствующее свойство:
Deno.test.each([
{ a: 1, b: 1, sum: 2 },
{ a: 2, b: 3, sum: 5 },
])("$a + $b = $sum", ({ a, b, sum }) => {
assertEquals(a + b, sum);
});
Deno.test.each принимает обычный объект параметров для каждого теста; сокращенные формы only и ignore сочетаются с ним как .only.each и .ignore.each.
Хуки тестов
Deno предоставляет хуки тестов, позволяющие выполнять код подготовки и очистки до и после тестов. Эти хуки полезны для инициализации ресурсов, очистки после тестов и обеспечения согласованной тестовой среды.
Доступные хуки
-
Deno.test.beforeAll(fn)— выполняется один раз перед всеми тестами в текущей области видимости -
Deno.test.beforeEach(fn)— выполняется перед каждым отдельным тестом -
Deno.test.afterEach(fn)— выполняется после каждого отдельного теста -
Deno.test.afterAll(fn)— выполняется один раз после всех тестов в текущей области видимости
Порядок выполнения хуков
- beforeAll/beforeEach: выполняются в порядке FIFO (первым пришел — первым ушел)
- afterEach/afterAll: выполняются в порядке LIFO (последним пришел — первым ушел)
Если в каком-либо хуке возникает исключение, остальные хуки того же типа не выполняются, а текущий тест помечается как неудачный.
Примеры
import { DatabaseSync } from "node:sqlite";
import { assertEquals } from "jsr:@std/assert";
let db: DatabaseSync;
Deno.test.beforeAll(() => {
console.log("Setting up test database...");
db = new DatabaseSync(":memory:");
db.exec(`
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
) STRICT
`);
});
Deno.test.beforeEach(() => {
console.log("Clearing database for clean test state...");
db.exec("DELETE FROM users");
});
Deno.test.afterEach(() => {
console.log("Test completed, cleaning up resources...");
// Any additional cleanup after each test
});
Deno.test.afterAll(() => {
console.log("Tearing down test database...");
db.close();
});
Deno.test("user creation", () => {
const stmt = db.prepare(
"INSERT INTO users (name, email) VALUES (?, ?) RETURNING *",
);
const user = stmt.get("alice", "alice@example.com");
assertEquals(user!.name, "alice");
});
Deno.test("user deletion", () => {
const insertStmt = db.prepare(
"INSERT INTO users (name, email) VALUES (?, ?) RETURNING *",
);
const user = insertStmt.get("bob", "bob@example.com");
const deleteStmt = db.prepare("DELETE FROM users WHERE id = ?");
deleteStmt.run(user!.id);
const selectStmt = db.prepare("SELECT * FROM users WHERE id = ?");
const deletedUser = selectStmt.get(user!.id);
assertEquals(deletedUser, undefined);
});
Несколько хуков
Можно зарегистрировать несколько хуков одного типа; они будут выполняться в указанном выше порядке:
Deno.test.beforeEach(() => {
console.log("First beforeEach hook");
});
Deno.test.beforeEach(() => {
console.log("Second beforeEach hook");
});
// Output:
// First beforeEach hook
// Second beforeEach hook
// (test runs)
Фильтрация тестов
Запустите часть тестов с помощью флага --filter. Он принимает строку или регулярное выражение, заключенное в прямые косые черты:
# Run tests whose name contains the word "my"
deno test --filter "my" tests/
# Run tests whose name matches a pattern
deno test --filter "/test-*\d/" tests/
Чтобы задать, какие файлы тестов собирать изначально, укажите test.include и test.exclude в файле конфигурации. Полное описание правил фильтрации см. в справочнике по deno test.
Запуск затронутых тестов
Во время работы над изменениями запуск всего набора тестов при каждом сохранении — пустая трата времени. deno test позволяет ограничить запуск только тестами, затронутыми вашими изменениями, выбирая их по истории git или зависимостям. Оба приведенных ниже флага запускают тесты один раз, а не в режиме непрерывного наблюдения.
Тесты, затронутые изменениями в git
--changed запускает только модули тестов, затронутые файлами, которые вы изменили в git. Если значение не указано, учитывается рабочее дерево, включая подготовленные к коммиту, неподготовленные и неотслеживаемые файлы:
deno test --changed
Передайте ссылку git, чтобы также включить все изменения, зафиксированные после ответвления от нее. Deno сравнивает с базовым коммитом слияния (трехточечной формой <ref>...HEAD, которую используют Vitest и Jest), поэтому учитываются все изменения в вашей ветке, а не только последний коммит:
# Every test affected since branching off main
deno test --changed=origin/main
Тесты, зависящие от определенных файлов
--related запускает модули тестов, зависящие от указанных исходных файлов, не обращаясь к git. Используйте этот вариант, если уже знаете, какие модули вы изменили:
# Run the tests that import src/util.ts, directly or transitively
deno test --related=src/util.ts
Как работает выбор тестов
Для обоих флагов Deno строит граф модулей собранных файлов тестов и оставляет только те тесты, которые через импорты достигают измененного или указанного файла. Тест в cart_test.ts запускается, если он импортирует cart.ts и cart.ts изменен, даже если импорт проходит через несколько модулей; тест, который не импортирует ни один из затронутых файлов, пропускается. Флаги сочетаются с остальными способами выбора тестов, поэтому их можно использовать вместе с аргументом каталога, --filter или test.include/test.exclude, чтобы еще сильнее сузить набор запускаемых тестов.
Этот подход хорошо подходит для хука перед коммитом или быстрого локального цикла разработки. В CI, где необходимо проверить все, следует запускать полный набор тестов; при необходимости его можно распределить между машинами с помощью --shard.
Выбор тестов в их определениях
Deno предоставляет два способа выбора тестов непосредственно в их определениях: пропуск тестов и фокусировку на определенных тестах.
Игнорирование и пропуск тестов
Вы можете пропускать определенные тесты при выполнении заданных условий с помощью логического значения ignore в определении теста. Если для ignore задано значение true, тест будет пропущен. Например, это полезно, если тест должен запускаться только в определенной операционной системе.
Deno.test({
name: "do macOS feature",
ignore: Deno.build.os !== "darwin", // This test will be ignored if not running on macOS
fn() {
// do MacOS feature here
},
});
Чтобы пропустить тест без каких-либо условий, можно использовать функцию ignore() из объекта Deno.test:
Deno.test.ignore("my test", () => {
// your test code
});
Запуск только определенных тестов
Чтобы сосредоточиться на конкретном тесте и пропустить остальные, можно использовать параметр only. Он указывает средству запуска тестов выполнять только те тесты, для которых значение only установлено в true. Этот параметр можно задать для нескольких тестов. Однако если хотя бы один тест помечен параметром only, весь запуск тестов всегда завершится ошибкой, поскольку этот параметр предназначен для временного использования при отладке.
Deno.test.only("my test", () => {
// some test code
});
или
Deno.test({
name: "Focus on this test only",
only: true, // Only this test will run
fn() {
// test complicated stuff here
},
});
Немедленная остановка при ошибке
Если набор тестов выполняется долго и вы хотите остановить его при первой же ошибке, укажите флаг --fail-fast при запуске набора.
deno test --fail-fast
Средство запуска тестов остановится после первой ошибки.
Форматы отчетов
По умолчанию результаты тестов выводятся с помощью подробного средства формирования отчетов pretty. Чтобы сменить формат, используйте флаг --reporter (dot, junit, tap) или запишите отчет JUnit XML в файл с помощью --junit-path, сохранив при этом удобочитаемый вывод в терминале:
deno test --reporter=dot
deno test --junit-path=./report.xml
справочнике по deno test.
Имитации и тестовые дублеры
Изолируйте тестируемый код, заменив его зависимости шпионами, заглушками и имитациями из @std/testing, а управление временем поручите FakeTime. Полное руководство с запускаемыми примерами см. в разделе Имитации и тестовые дублеры.
Покрытие кода
Собирайте данные о покрытии во время тестирования с помощью deno test --coverage, а затем преобразуйте их в отчет для терминала, HTML или lcov с помощью deno coverage. Данные поступают напрямую из V8. Описание процесса, включая интеграцию с CI, см. в разделе Покрытие тестами.
Разработка через тестирование поведения
С помощью модуля @std/testing/bdd можно писать тесты в знакомом формате, группировать их и добавлять хуки подготовки и очистки, используемые другими фреймворками тестирования JavaScript, такими как Jasmine, Jest и Mocha.
Функция describe создает блок, объединяющий несколько связанных тестов. Функция it регистрирует отдельный тестовый сценарий. Например:
import { describe, it } from "jsr:@std/testing/bdd";
import { expect } from "jsr:@std/expect";
import { add } from "./add.js";
describe("add function", () => {
it("adds two numbers correctly", () => {
const result = add(2, 3);
expect(result).toBe(5);
});
it("handles negative numbers", () => {
const result = add(-2, -3);
expect(result).toBe(-5);
});
});
Дополнительные сведения об этих функциях и хуках см. в документации @std/testing.
Тесты документации
deno test --doc запускает примеры кода из комментариев JSDoc и файлов Markdown в качестве тестов, поэтому документация не устаревает незаметно. О том, как фрагменты кода извлекаются, проверяются на соответствие типам и пропускаются, читайте в разделе Тесты документации.
Санитайзеры
Средство запуска тестов может выявлять проблемы, незаметные для проверок: утечки асинхронных операций, незакрытые ресурсы и неожиданные вызовы Deno.exit(). Санитайзер выхода включен по умолчанию; санитайзеры op и ресурсов являются необязательными начиная с Deno 2.8. Описание каждого санитайзера и параметров его глобального включения см. в разделе Санитайзеры тестов.
Тестирование снимков
Сравните значение с сериализованным эталоном, хранящимся рядом с тестом, и обновите эталоны с помощью одного флага, если поведение намеренно меняется. Описание процесса, включая обновление и проверку снимков, см. в разделе Тестирование снимков.
Тесты и разрешения
Свойство permissions в конфигурации Deno.test позволяет явно запретить разрешения, но не предоставляет их. Разрешения необходимо указывать при запуске команды тестирования. При создании надежных приложений часто приходится обрабатывать случаи, когда в разрешениях отказано (например, можно написать тесты, проверяющие, правильно ли настроены резервные варианты).
Представьте, что вы читаете файл. Возможно, вы захотите возвращать резервное значение, если у функции нет разрешения на чтение:
import { assertEquals } from "jsr:@std/assert";
import getFileText from "./main.ts";
Deno.test({
name: "File reader gets text with permission",
// no `permissions` means "inherit"
fn: async () => {
const result = await getFileText();
console.log(result);
assertEquals(result, "the content of the file");
},
});
Deno.test({
name: "File reader falls back to error message without permission",
permissions: { read: false },
fn: async () => {
const result = await getFileText();
console.log(result);
assertEquals(result, "oops don't have permission");
},
});
# Run the tests with read permission
deno test --allow-read
Объект разрешений поддерживает детальную настройку:
Deno.test({
name: "permission configuration example",
// permissions: { read: true } // Grant all read permissions and deny all others
// OR
permissions: {
read: ["./data", "./config"], // Grant read to specific paths only
write: false, // Explicitly deny write permissions
net: ["example.com:443"], // Allow specific host:port combinations
env: ["API_KEY"], // Allow access to specific env variables
run: false, // Deny subprocess execution
ffi: false, // Deny loading dynamic libraries
hrtime: false, // Deny high-resolution time
},
fn() {
// Test code that respects these permission boundaries
},
});
Помните, что любое разрешение, явно не предоставленное в командной строке, будет запрещено независимо от указанных в конфигурации теста настроек.
Связанные учебные материалы
Дополнительные практические руководства по тестированию:
Последнее обновление:
© 2018–2025 the Deno authors
Licensed under the MIT License.
https://docs.deno.com/runtime/test