Тестирование
Deno предоставляет встроенный тестовый запускер для написания и запуска тестов как на JavaScript, так и на TypeScript. Это упрощает обеспечение надёжности вашего кода и его ожидаемого функционирования без необходимости установки дополнительных зависимостей или инструментов. Запускер deno test позволяет вам тонко контролировать разрешения для каждого теста, гарантируя, что код не выполняет неожиданных действий.
Помимо встроенного тестового запускаера, вы также можете использовать другие тестовые запускаеры из экосистемы JS, такие как Jest, Mocha или AVA, с Deno. Однако эти инструменты не будут рассматриваться в данном документе.
Написание тестов
Для определения теста в Deno используется функция Deno.test(). Вот несколько примеров:
import { assertEquals } from "jsr:@std/assert";
Deno.test("simple test", () => {
const x = 1 + 2;
assertEquals(x, 3);
});
import { delay } from "jsr:@std/async";
Deno.test("async test", async () => {
const x = 1 + 2;
await delay(100);
assertEquals(x, 3);
});
Deno.test({
name: "read file test",
permissions: { read: true },
fn: () => {
const data = Deno.readTextFileSync("./somefile.txt");
assertEquals(data, "expected content");
},
});
Если вы предпочитаете стиль утверждений, похожий на "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}.
# 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
});
});
Фильтрация в командной строке
Deno позволяет запускать определённые тесты или группы тестов, используя опцию --filter в командной строке. Эта опция принимает либо строку, либо шаблон для сопоставления имён тестов. Фильтрация не влияет на шаги; если имя теста соответствует фильтру, выполняются все его шаги.
Рассмотрим следующие тесты:
Deno.test("my-test", () => {});
Deno.test("test-1", () => {});
Deno.test("test-2", () => {});
Фильтрация по строке
Чтобы запустить все тесты, содержащие слово «my» в их именах, используйте:
deno test --filter "my" tests/
Эта команда выполнит my-test , потому что она содержит слово «my».
Фильтрация по шаблону
Чтобы запустить тесты, соответствующие определённому шаблону, используйте:
deno test --filter "/test-*\d/" tests/
Эта команда запустит test-1 и test-2 , потому что они соответствуют шаблону test-* и последующей цифре.
Для указания использования шаблона (регулярного выражения) оберните значение фильтра в обратные слэши /, подобно синтаксису регулярных выражений в JavaScript.
Включение и исключение файлов тестов в файле конфигурации
Вы также можете фильтровать тесты, указав пути для включения или исключения в файле конфигурации Deno.
Например, если вы хотите протестировать только src/fetch_test.ts и src/signal_test.ts и исключить всё в out/:
{
"test": {
"include": [
"src/fetch_test.ts",
"src/signal_test.ts"
]
}
}
Или, скорее всего:
{
"test": {
"exclude": ["out/"]
}
}
Выбор определения теста
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. Несколько тестов могут иметь эту опцию. Однако, если какой-либо тест помечен как только, общий запуск теста всегда завершится ошибкой, так как это предназначено для временных мер отладки.
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
Это заставит тестовый запускер прекратить выполнение после первой ошибки в тесте.
Отчётчики
Deno включает три встроенных отчётчика для форматирования вывода результатов тестов:
-
pretty(по умолчанию): Предоставляет подробный и читаемый вывод. -
dot: Предлагает краткий вывод, полезный для быстрого просмотра результатов тестов. -
junit: Создаёт вывод в формате JUnit XML, что полезно для интеграции с инструментами CI/CD.
Вы можете указать используемый отчётчик с помощью флага --reporter:
# Use the default pretty reporter
deno test
# Use the dot reporter for concise output
deno test --reporter=dot
# Use the JUnit reporter
deno test --reporter=junit
Кроме того, вы можете записать отчёт JUnit в файл, сохраняя при этом удобочитаемый вывод в терминале, используя флаг --junit-path:
deno test --junit-path=./report.xml
Просмотр, моделирование (тестовые дубликаты), подстановка и имитация времени
Стандартная библиотека Deno предоставляет набор функций, которые помогут вам писать тесты, включающие просмотр, моделирование и подстановку. Подробнее об этих утилитах можно узнать в документации Стандартной библиотеки Deno на сайте @std/testing.
Покрытие
Deno будет собирать данные о покрытии тестами в каталог для вашего кода, если вы укажете флаг --coverage при запуске deno test. Эта информация о покрытии собирается напрямую из движка V8 JavaScript, обеспечивая высокую точность.
Затем эти данные из внутреннего формата можно обработать и привести к широко известным форматам, таким как lcov с помощью инструмента deno coverage.
Разработка, основанная на поведении
С модулем @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);
});
});
Подробнее об этих функциях и хуках можно узнать в документации на JSR.
Тесты документации
Deno позволяет вам оценивать фрагменты кода, написанные в JSDoc или markdown файлах. Это гарантирует, что примеры в вашей документации актуальны и функциональны.
Блоки примеров кода
/**
* # Examples
*
* ```ts
* import { assertEquals } from "jsr:@std/assert/equals";
*
* const sum = add(1, 2);
* assertEquals(sum, 3);
* ```
*/
export function add(a: number, b: number): number {
return a + b;
}
Тройные обратные кавычки обозначают начало и конец блоков кода, язык определяется атрибутом идентификатора языка, который может быть одним из следующих:
jsjavascriptmjscjsjsxtstypescriptmtsctstsx
Если идентификатор языка не указан, язык определяется из типа носителя исходного документа, из которого извлечён блок кода.
deno test --doc example.ts
Приведённая выше команда извлечёт этот пример, преобразует его в псевдотестовый случай, который выглядит как:
import { assertEquals } from "jsr:@std/assert/equals";
import { add } from "file:///path/to/example.ts";
Deno.test("example.ts$4-10.ts", async () => {
const sum = add(1, 2);
assertEquals(sum, 3);
});
а затем запустит его как автономный модуль в той же директории, что и документируемый модуль.
Если вы хотите проверить только типы своих фрагментов кода в JSDoc и файлах markdown без их фактического выполнения, вы можете использовать команду deno check с опцией --doc (для JSDoc) или с опцией --doc-only (для markdown).
Экспортированные элементы автоматически импортируются
Глядя на сгенерированный тестовый код выше, вы заметите, что он включает оператор import для импорта функции add даже если исходный блок кода этого не имеет. При документировании модуля все элементы, экспортированные из модуля, автоматически включаются в сгенерированный тестовый код с тем же именем.
Допустим, у нас есть следующий модуль:
/**
* # Examples
*
* ```ts
* import { assertEquals } from "jsr:@std/assert/equals";
*
* const sum = add(ONE, getTwo());
* assertEquals(sum, 3);
* ```
*/
export function add(a: number, b: number): number {
return a + b;
}
export const ONE = 1;
export default function getTwo() {
return 2;
}
Это будет преобразовано в следующий тестовый случай:
import { assertEquals } from "jsr:@std/assert/equals";
import { add, ONE }, getTwo from "file:///path/to/example.ts";
Deno.test("example.ts$4-10.ts", async () => {
const sum = add(ONE, getTwo());
assertEquals(sum, 3);
});
Пропуск блоков кода
Вы можете пропустить оценку блоков кода, добавив атрибут ignore.
/**
* This code block will not be run.
*
* ```ts ignore
* await sendEmail("deno@example.com");
* ```
*/
export async function sendEmail(to: string) {
// send an email to the given address...
}
Сантилизаторы
Запускер тестов предлагает несколько сантилизаторов, чтобы гарантировать, что тест ведёт себя разумно и ожидаемо.
Сантилизатор ресурсов
Сантилизатор ресурсов гарантирует, что все ресурсы ввода-вывода, созданные во время теста, закрываются, чтобы предотвратить утечки.
Ресурсы ввода-вывода — это такие вещи, как Deno.FsFile дескрипторы, сетевые подключения, fetch тела, таймеры и другие ресурсы, которые не собираются автоматически сборщиком мусора.
Вы всегда должны закрывать ресурсы, когда закончите с ними. Например, для закрытия файла:
const file = await Deno.open("hello.txt");
// Do something with the file
file.close(); // <- Always close the file when you are done with it
Для закрытия сетевого подключения:
const conn = await Deno.connect({ hostname: "example.com", port: 80 });
// Do something with the connection
conn.close(); // <- Always close the connection when you are done with it
Для закрытия fetch тела:
const response = await fetch("https://example.com");
// Do something with the response
await response.body?.cancel(); // <- Always cancel the body when you are done with it, if you didn't consume it otherwise
Этот сантайзер включён по умолчанию, но может быть отключён в этом тесте с помощью sanitizeResources: false:
Deno.test({
name: "leaky resource test",
async fn() {
await Deno.open("hello.txt");
},
sanitizeResources: false,
});
Сантайзер асинхронных операций
Сантайзер асинхронных операций гарантирует, что все асинхронные операции, запущенные в тесте, завершатся до окончания теста. Это важно, потому что если асинхронная операция не ожидает завершения, тест завершится до её завершения, и тест будет отмечен как успешный, даже если операция, возможно, завершилась неудачно.
Вы всегда должны ожидать завершения всех асинхронных операций в своих тестах. Например:
Deno.test({
name: "async operation test",
async fn() {
await new Promise((resolve) => setTimeout(resolve, 1000));
},
});
Этот сантайзер включён по умолчанию, но может быть отключён с помощью sanitizeOps: false:
Deno.test({
name: "leaky operation test",
fn() {
crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode("a".repeat(100000000)),
);
},
sanitizeOps: false,
});
Сантайзер выхода
Сантайзер выхода гарантирует, что тестируемый код не вызывает Deno.exit(), что могло бы привести к ложному успеху теста.
Этот сантайзер включён по умолчанию, но может быть отключён с помощью sanitizeExit: false.
Deno.test({
name: "false success",
fn() {
Deno.exit(0);
},
sanitizeExit: false,
});
// This test never runs, because the process exits during "false success" test
Deno.test({
name: "failing test",
fn() {
throw new Error("this test fails");
},
});
Тестирование снимков
В Стандартной библиотеке Deno есть модуль снимков, который позволяет разработчикам писать тесты, сравнивая значения со снимками-эталонами. Эти снимки — сериализованные представления исходных значений, хранящиеся вместе с файлами тестов.
Тестирование снимков позволяет поймать множество ошибок с минимальным объёмом кода. Это особенно полезно в ситуациях, когда трудно точно сформулировать, что должно быть проверено, без избыточного количества кода, или когда утверждения теста часто меняются.
© 2018–2024 the Deno authors
Licensed under the MIT License.
https://docs.deno.com/runtime/fundamentals/testing