deno.com
本页内容

测试

Deno 为 JavaScript 和 TypeScript 提供了内置的测试运行器,用于编写和运行测试。这使得您无需安装任何额外的依赖项或工具,即可轻松确保代码可靠并按预期运行。deno test 运行器允许您对每个测试的权限进行细粒度控制,确保代码不会执行任何意外操作。

除了内置的测试运行器,您还可以将 JS 生态系统中的其他测试运行器(如 Jest、Mocha 或 AVA)与 Deno 一起使用。然而,本文档中不会涵盖这些内容。

编写测试 跳转到标题

要在 Deno 中定义测试,请使用 Deno.test() 函数。以下是一些示例

my_test.ts
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",
  fn: () => {
    const data = Deno.readTextFileSync("./somefile.txt");
    assertEquals(data, "expected content");
  },
});

如果您偏好“类似 Jest”的 expect 断言风格,Deno 标准库提供了一个 expect 函数,可以替代 assertEquals 使用。

my_test.ts
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 子命令。

如果运行时不带文件名或目录名,此子命令将自动查找并执行当前目录(递归)中所有匹配 glob {*_,*.,}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-1test-2,因为它们匹配 test-* 后跟数字的模式。

要指示您正在使用模式(正则表达式),请用正斜杠 / 包裹您的过滤值,就像 JavaScript 的正则表达式语法一样。

在配置文件中包含和排除测试文件 跳转到标题

您还可以通过在 Deno 配置文件中指定要包含或排除的路径来过滤测试。

例如,如果您只想测试 src/fetch_test.tssrc/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
  },
});

如果您想在不传递任何条件的情况下忽略测试,可以使用 Deno.test 对象中的 ignore() 函数

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

这将导致测试运行器在第一次测试失败后停止执行。

报告器 跳转到标题

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-path 标志将 JUnit 报告写入文件,同时仍在终端中获取人类可读的输出。

deno test --junit-path=./report.xml

监听、模拟(测试替身)、存根和伪造时间 跳转到标题

Deno 标准库提供了一组函数,可帮助您编写涉及监听、模拟和存根的测试。有关这些实用程序的更多信息,请查阅 JSR 上的 @std/testing 文档,或者我们的使用 Deno 进行测试中模拟和监听的教程

覆盖率 跳转到标题

如果您在启动 deno test 时指定 --coverage 标志,Deno 将为您的代码收集测试覆盖率到一个目录中。此覆盖率信息直接从 V8 JavaScript 引擎获取,确保高准确性。

然后,可以使用 deno coverage 工具将此信息从内部格式进一步处理为诸如 lcov 等已知格式。

行为驱动开发 跳转到标题

使用 @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 文件中编写的代码片段。这确保了文档中的示例是最新的且功能正常。

示例代码块 跳转到标题

example.ts
/**
 * # 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;
}

三个反引号标记代码块的开始和结束,语言由语言标识符属性决定,可以是以下之一

  • js
  • javascript
  • mjs
  • cjs
  • jsx
  • ts
  • typescript
  • mts
  • cts
  • tsx

如果未指定语言标识符,则语言将从提取代码块的源文档的媒体类型推断。

deno test --doc example.ts

上述命令将提取此示例,将其转换为如下所示的伪测试用例

example.ts$4-10.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 文件中对代码片段进行类型检查,而无需实际运行它们,可以改为使用带 --doc 选项(针对 JSDoc)或带 --doc-only 选项(针对 Markdown)的 deno check 命令。

导出的项自动导入 跳转到标题

查看上面生成的测试代码,您会注意到它包含了导入 add 函数的 import 语句,尽管原始代码块中没有。当文档化一个模块时,从该模块导出的任何项都会自动以相同的名称包含在生成的测试代码中。

假设我们有以下模块

example.ts
/**
 * # 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;
}

这将转换为以下测试用例

example.ts$4-10.ts
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...
}

清理器 跳转到标题

测试运行器提供了几个清理器,以确保测试以合理和预期的方式运行。

资源清理器 跳转到标题

资源清理器确保在测试期间创建的所有 I/O 资源都被关闭,以防止泄漏。

I/O 资源包括 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,
});

异步操作清理器 跳转到标题

异步操作清理器确保测试中启动的所有异步操作在测试结束前完成。这很重要,因为如果异步操作未被 await,测试将在操作完成前结束,即使操作可能实际失败,测试也会被标记为成功。

在测试中应始终 await 所有异步操作。例如

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 标准库包含一个快照模块,允许开发人员通过将值与参考快照进行比较来编写测试。这些快照是原始值的序列化表示,并与测试文件一起存储。

快照测试能够以非常少的代码捕获各种错误。在难以精确表达应断言的内容、无需大量代码,或者测试所做的断言预计会经常更改的情况下,它特别有用。

测试与权限 跳转到标题

Deno.test 配置中的 permissions 属性允许您专门拒绝权限,但不会授予它们。权限必须在运行测试命令时提供。在构建健壮的应用程序时,您通常需要处理权限被拒绝的情况(例如,您可能希望编写测试来检查回退是否已正确设置)。

考虑这样一种情况:您正在读取文件,如果函数没有读取权限,您可能希望提供一个回退值。

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
  },
});

请记住,命令行上未明确授予的任何权限都将被拒绝,无论测试配置中指定了什么。

您找到所需内容了吗?

隐私政策