On this page
测试
Deno 提供了一个内置的测试运行器,可以用来编写和运行 JavaScript 和 TypeScript 中的测试。这使得确保代码可靠并按预期运行变得简单,无需安装任何额外的依赖项或工具。deno test 运行器允许您对每个测试的权限进行细粒度控制,确保代码没有执行任何意外的操作。
除了内置的测试运行器之外,您还可以在 Deno 中使用 JS 生态系统中的其他测试运行器,例如 Jest、Mocha 或 AVA。要迁移现有的 Jest 测试套件?请参阅 从 Jest 迁移。
编写测试 Jump to heading
要在 Deno 中定义测试,请使用 Deno.test()
函数。以下是一些示例:
import { assertEquals } from "jsr:@std/assert";
import { delay } from "jsr:@std/async";
Deno.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: "读取文件测试",
fn: () => {
const data = Deno.readTextFileSync("./somefile.txt");
assertEquals(data, "预期内容");
},
});
如果您更喜欢类似于“jest”的 expect 风格的断言,Deno 标准库提供了一个 expect 函数,可以替代 assertEquals:
import { expect } from "jsr:@std/expect";
import { add } from "./add.js";
Deno.test("add 函数正确相加两个数字", () => {
const result = add(2, 3);
expect(result).toBe(5);
});
运行测试 Jump to heading
要运行测试,请使用 deno test 子命令。
如果没有传入文件名或目录名,该子命令将自动查找并执行当前目录中(递归地)所有匹配 glob {*_,*.,}test.{ts, tsx, mts, js, mjs, jsx} 的测试。
# 运行当前目录及所有子目录中的所有测试
deno test
# 运行 util 目录中的所有测试
deno test util/
# 仅运行 my_test.ts
deno test my_test.ts
# 并行运行测试模块
deno test --parallel
# 传递额外参数给测试文件,这些参数在 `Deno.args` 中可见
deno test my_test.ts -- -e --foo --bar
# 提供文件系统的读取权限,这对于上面的最终测试是必要的
deno test --allow-read=. my_test.ts
测试步骤 Jump to heading
Deno 还支持测试步骤,这允许您将测试细分为更小、可管理的部分。这在测试中的设置和拆除操作时非常有用:
Deno.test("数据库操作", async (t) => {
using db = await openDatabase();
await t.step("插入用户", async () => {
// 插入用户逻辑
});
await t.step("插入书籍", async () => {
// 插入书籍逻辑
});
});
超时 Jump to heading
您可以使用 timeout 选项为单个测试设置最大持续时间。
如果测试超过其截止时间,它将被标记为失败。无论是异步挂起
(一个永远不会解析的 promise)还是同步死循环(while (true) {}
)都会被捕获。
Deno.test({
name: "在截止时间内完成",
timeout: 5000, // 5 秒
async fn() {
const response = await fetch("https://example.com");
await response.body?.cancel();
},
});
如果某个测试超时,同一文件中的下一个测试仍会正常运行。
将 timeout 设为 0 或省略它,表示测试将在没有截止时间的情况下运行。
测试钩子 Jump to heading
Deno 提供了测试钩子,允许您在测试运行之前和之后运行设置和拆除代码。这些钩子对于初始化资源、测试后清理以及确保测试环境一致非常有用。
可用的钩子 Jump to heading
Deno.test.beforeAll(fn)- 在当前范围内的所有测试之前运行一次Deno.test.beforeEach(fn)- 在每个单独测试之前运行Deno.test.afterEach(fn)- 在每个单独测试之后运行Deno.test.afterAll(fn)- 在当前范围内的所有测试之后运行一次
钩子执行顺序 Jump to heading
- beforeAll/beforeEach: 按先进先出(FIFO)顺序执行
- afterEach/afterAll: 按后进先出(LIFO)顺序执行
如果任何钩子中抛出异常,同类型的剩余钩子将不会被执行,且当前测试将被标记为失败。
示例 Jump to heading
import { DatabaseSync } from "node:sqlite";
import { assertEquals } from "jsr:@std/assert";
let db: DatabaseSync;
Deno.test.beforeAll(() => {
console.log("正在设置测试数据库...");
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("正在清空数据库以保持干净的测试状态...");
db.exec("DELETE FROM users");
});
Deno.test.afterEach(() => {
console.log("测试完成,正在清理资源...");
// 每个测试后的额外清理
});
Deno.test.afterAll(() => {
console.log("正在拆除测试数据库...");
db.close();
});
Deno.test("用户创建", () => {
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("用户删除", () => {
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);
});
多个钩子 Jump to heading
您可以注册多个相同类型的钩子,它们将按照上述顺序依次执行:
Deno.test.beforeEach(() => {
console.log("第一个 beforeEach 钩子");
});
Deno.test.beforeEach(() => {
console.log("第二个 beforeEach 钩子");
});
// 输出:
// 第一个 beforeEach 钩子
// 第二个 beforeEach 钩子
// (测试执行)
过滤测试 Jump to heading
使用 --filter 标志运行部分测试。它接受一个字符串,或者一个
用正斜杠包裹的正则表达式:
# 运行名称包含单词 "my" 的测试
deno test --filter "my" tests/
# 运行名称匹配某种模式的测试
deno test --filter "/test-*\d/" tests/
要控制首先收集哪些测试文件,请在您的
配置文件 中设置 test.include
和 test.exclude。有关完整的
过滤语义,请参阅 deno test 参考。
测试定义选择 Jump to heading
Deno 提供了两种选择测试的方法:忽略测试和专注于特定测试。
忽略/跳过测试 Jump to heading
您可以根据特定条件使用测试定义中的 ignore 布尔值来忽略某些测试。如果 ignore 设置为 true,该测试将被跳过。这在您希望测试仅在特定操作系统上运行时非常有用。
Deno.test({
name: "执行 macOS 功能",
ignore: Deno.build.os !== "darwin", // 如果不在 macOS 上运行,则该测试将被忽略
fn() {
// 在这里执行 MacOS 功能
},
});
如果您想在不传入任何条件的情况下忽略测试,可以使用 Deno.test 对象中的
ignore() 函数:
Deno.test.ignore("我的测试", () => {
// 您的测试代码
});
仅运行特定测试 Jump to heading
如果您想专注于特定测试并忽略其他测试,可以使用 only 选项。这告诉测试运行器仅运行设置为 only 的测试。多个测试可以设置此选项。然而,如果任何测试标记为 only,则整体测试运行将始终失败,因为这被视为临时调试措施。
Deno.test.only("我的测试", () => {
// 一些测试代码
});
或者
Deno.test({
name: "仅关注此测试",
only: true, // 仅此测试将运行
fn() {
// 在这里测试复杂的内容
},
});
快速失败 Jump to heading
如果您有一个长时间运行的测试套件,并希望在第一次失败时停止,可以在运行套件时指定 --fail-fast 标志。
deno test --fail-fast
这将导致测试运行器在第一次测试失败后停止执行。
报告工具 Jump to heading
测试输出默认使用详细的 pretty 报告器。使用
--reporter 标志(dot、junit、tap)切换格式,或者使用
--junit-path 将 JUnit XML 报告写入文件,同时在终端保持可读输出:
deno test --reporter=dot
deno test --junit-path=./report.xml
请参阅 deno test 参考 以查看
报告器的完整列表。
Mock 和测试替身 Jump to heading
通过使用 @std/testing 中的 spy、stub 和 mock 替换其协作者,来隔离被测代码,并使用 FakeTime 控制时钟。请参阅
Mock 和测试替身 以获取包含
可运行示例的完整指南。
覆盖率 Jump to heading
在测试时使用 deno test --coverage 收集覆盖率,然后通过 deno coverage 将其转换为
终端、HTML 或 lcov 报告。数据直接来自 V8。请参阅 测试覆盖率 了解工作流程,
包括 CI 集成。
行为驱动开发 Jump to heading
使用 @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 函数", () => {
it("正确相加两个数字", () => {
const result = add(2, 3);
expect(result).toBe(5);
});
it("处理负数", () => {
const result = add(-2, -3);
expect(result).toBe(-5);
});
});
查看 @std/testing 文档
以了解这些函数和钩子的更多信息。
文档测试 Jump to heading
deno test --doc 会将 JSDoc 注释和 markdown 文件中的代码示例作为测试运行,因此文档不会悄悄过时。请参阅
文档测试 了解如何提取、类型检查和跳过代码片段。
消毒器 Jump to heading
测试运行器可以捕获断言无法发现的错误行为:泄漏的异步
操作、未关闭的资源以及意外的
Deno.exit() 调用。退出消毒器默认开启;由于 Deno 2.8,op 和资源消毒器为可选启用。请参阅
测试消毒器 了解每种消毒器以及全局
启用选项。
快照测试 Jump to heading
将一个值与存储在测试旁边的序列化参考值进行比较,并在行为有意改变时使用一个标志更新参考值。 请参阅 快照测试 了解工作流程,包括 更新和审查快照。
测试和权限 Jump to heading
Deno.test 配置中的 permissions 属性允许您有针对性地拒绝权限,但不会授予权限。权限必须在运行测试命令时提供。在构建健壮的应用程序时,您通常需要处理权限被拒绝的情况(例如,您可能希望编写测试来检查回退方案是否已正确设置)。
考虑一种情况,您正在从文件中读取,您可能希望在函数没有读取权限的情况下提供一个回退值:
import { assertEquals } from "jsr:@std/assert";
import getFileText from "./main.ts";
Deno.test({
name: "File reader gets text with permission",
// 没有 `permissions` 表示“继承”
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");
},
});
# 运行带有读取权限的测试
deno test --allow-read
权限对象支持详细配置:
Deno.test({
name: "permission configuration example",
// permissions: { read: true } // 授予所有读取权限,拒绝其他所有权限
// 或者
permissions: {
read: ["./data", "./config"], // 仅授予对特定路径的读取权限
write: false, // 明确拒绝写权限
net: ["example.com:443"], // 允许特定的 host:port 组合
env: ["API_KEY"], // 允许访问特定的环境变量
run: false, // 拒绝子进程执行权限
ffi: false, // 拒绝加载动态库
hrtime: false, // 拒绝高分辨率时间权限
},
fn() {
// 遵守这些权限边界的测试代码
},
});
请记住,任何未在命令行中明确授予的权限都将被拒绝,无论测试配置中指定了什么。
相关教程 Jump to heading
有关更多上手测试指南,请查看: