单元测试
test 提供了在 dart 中编写和运行测试的方法
$ dart pub get test --dev
编写测试
测试使用顶级的 test()
函数, 测试断言使用 expect()
import 'package:test/test.dart';
void main() {
test('String.trim() removes surrounding whitespace', () {
var string = ' foo ';
expect(string.trim(), equals('foo'));
});
}
运行结果:
00:00 +0: String.trim() removes surrounding whitespace
00:00 +1: All tests passed!
测试支持分组函数 group()
, 每个分组的描述都会添加到测试描述的开头
import 'package:test/test.dart';
void main() {
group('int', () {
test('.remainder() returns the remainder of division', () {
expect(11.remainder(3), equals(2));
});
test('.toRadixString() returns a hex string', () {
expect(11.toRadixString(16), equals('b'));
});
});
}
运行结果:
00:00 +0: int .remainder() returns the remainder of division
00:00 +1: int .toRadixString() returns a hex string
00:00 +2: All tests passed!
任何来自 matcher 包的匹配器都可以与 expect()
一起使用来进行复杂的验证:
import 'package:test/test.dart';
void main() {
test('.split() splits the string on the delimiter', () {
expect('foo,bar,baz',
allOf([contains('foo'), isNot(startsWith('bar')), endsWith('baz')]));
});
}
运行结果:
00:00 +0: .split() splits the string on the delimiter
00:00 +1: All tests passed!
同时还还可以使用 throwsA()
函数或 throwsFormatException
等匹配器来测试异常:
import 'package:test/test.dart';
void main() {
test('.parse() fails on invalid input', () {
expect(() => int.parse('X'), throwsFormatException);
});
}
运行结果:
00:00 +0: .parse() fails on invalid input
00:00 +1: All tests passed!
可以使用 setUp()
和 tearDown()
函数在测试之间处理/关闭共享代码。 setUp()
回调将在组或测试套件中的每个测试之前运行, tearDown()
将在测试之后运行。即使测试失败, tearDown()
也将运行,以确保正确清理
import 'package:test/test.dart';
void main() {
late HttpServer server;
late Uri url;
setUp(() async {
server = await HttpServer.bind('localhost', 0);
url = Uri.parse('http://${server.address.host}:${server.port}');
});
tearDown(() async {
await server.close(force: true);
server = null;
url = null;
});
// ...
}
运行测试
可以使用 dart test path/to/test.dart
在单个测试文件上运行(Dart 2.10 以上版本 - 以前的 sdk 版本必须使用 pub run test
代替 dart test
)。
可以使用 dart test path/to/dir
同时运行多个测试。
也可以通过使用 dart path/to/test.dart
调用来仅在 Dart VM 上运行测试,但这样不会加载完整的测试运行器,并且会缺少一些功能。
测试运行器将任何以 _test.dart
结尾的文件视为测试文件。如果不传递任何路径,它将运行 test/
目录中的所有测试文件,可以一次性测试整个应用程序
可以使用 dart test -n "test name"
通过名称选择要运行的特定测试用例。将字符串解释为正则表达式,并且只会运行描述(包括任何组描述)与该正则表达式匹配的测试。另外还可以使用 -N
标志来运行名称包含纯文本字符串的测试。
默认情况下,测试在 Dart VM 中运行,但也可以通过传递 dart test -p chrome path/to/test.dart
在浏览器中运行它们. test 会负责启动浏览器并加载测试,所有结果都将在命令行上报告,就像 VM 测试一样。实际上,甚至可以使用单个命令在两个平台上运行测试: dart test -p "chrome,vm" path/to/test.dart
测试路径查询
支持测试路径上的一些查询参数,这些参数允许你在这些路径内仅过滤要运行的测试。这些过滤器与传递的任何全局选项合并,并且所有过滤器都必须匹配才能运行测试。
- name:与 -
-name
- 相同(简单的包含检查)。 这是唯一支持多个条目的选项 - full-name:对测试名称要求完全匹配
- line:匹配测试套件中来自此行的任何测试
- col:匹配测试套件中来自此列的任何测试
示例用法: dart test "path/to/test.dart?line=10&col=2"
行/列匹配语义
line 和 col 过滤器针对从调用测试函数的当前堆栈跟踪进行匹配,如果跟踪中的任何帧都满足以下所有条件,则视为匹配:
- URI 地址 与根测试套件 uri 匹配, 这意味着它不会与导入的库中的行匹配。
- 如果传递了 line 和 col,则必须都与 - 同一帧(WIP) - 匹配
- 要匹配的特定行和列由创建堆栈跟踪的工具定义。这通常意味着它们是基于 1 的而不是基于 0 的,但这个包括不控制确切的语义,它们可能会因平台实现而变化。
分片测试
还可以使用 --total-shards
和 --shard-index
参数将测试分片,从而将测试套件拆分并分开运行。例如,如果想要运行测试套件的 3 个分片,可以如下运行它们:
dart test --total-shards 3 --shard-index 0 path/to/test.dart
dart test --total-shards 3 --shard-index 1 path/to/test.dart
dart test --total-shards 3 --shard-index 2 path/to/test.dart
这将运行测试套件中的所有测试,但每个分片只会运行其中的一部分。如果有一个大型测试套件并想将测试运行并行化以使其运行得更快,则这很有用。
乱序测试
可以使用 --test-randomize-ordering-seed
参数将测试顺序洗牌。可以使用特定的种子(确定性)或每次运行的随机种子来洗牌测试。例如,考虑以下测试运行:
dart test --test-randomize-ordering-seed=12345
dart test --test-randomize-ordering-seed=random
设置 --test-randomize-ordering-seed=0
与不指定它效果相同,意味着测试顺序将保持不变。
选择测试报告
可以使用 --reporter=<option>
命令行选项调整测试结果的输出格式。默认格式是 compact
紧凑输出格式(单行输出) ,随着测试运行而持续更新。但是,当在 GitHub Actions CI 上运行(通过检查 GITHUB_ACTIONS 环境变量是否为 true 检测到)时,默认格式更改为 github 输出格式 - 为该 CI/CD 系统定制的报告者。
-reporter
- 的可用选项包括:- compact:单行,持续更新
- expanded:每次更新一行
- github:GitHub Actions 的自定义报告类型
- json:机器可读格式;参见 https://dart.cn/go/test-docs/json_reporter.md
收集代码覆盖率
要收集代码覆盖率,可以使用 --coverage <directory>
参数运行测试。指定的目录可以是绝对路径或相对路径。如果指定路径处不存在目录,将创建目录。如果存在目录并冲突,则会被覆盖
此选项将在套件级别启用代码覆盖率收集,并在指定的目录中输出结果覆盖率文件。然后可以使用 package:coverage
format_coverage
文件格式化这些文件
目前仅为在 Dart VM 或 Chrome 上运行的测试实现了覆盖率收集
以下是如何运行测试并将收集的覆盖率格式化为 LCOV 的示例:
官方
## Run Dart tests and output them at directory `./coverage`:
dart run test --coverage=./coverage
## Activate package `coverage` (if needed):
dart pub global activate coverage
## Format collected coverage to LCOV (only for directory "lib")
dart pub global run coverage:format_coverage --packages=.dart_tool/package_config.json --report-on=lib --lcov -o ./coverage/lcov.info -i ./coverage
## Generate LCOV report:
genhtml -o ./coverage/report ./coverage/lcov.info
## Open the HTML coverage report:
open ./coverage/report/index.html
另一个
dart test --coverage coverage
format_coverage -i coverage -o lcov.info --lcov
- LCOV 是一个 GNU 工具,它在运行特定测试用例时提供有关程序实际执行(即“覆盖”)的哪些部分的信息。
- genhtml 是 LCOV 工具之一。
- 有关更多信息,请参阅 LCOV 项目:https://github.com/linux-test-project/lcov
- 有关 Homebrew LCOV 公式,请参阅:https://formulae.brew.sh/formula/lcov
ps: 图示
限制在特定平台上测试
有些测试文件只有在特定平台上才有意义。它们可能使用 dart:html
或 dart:io
,可能测试 Windows 的特定文件系统行为,或者可能使用仅在 Chrome 中可用的功能。 @TestOn
注释很容易声明测试文件应在哪些平台上运行。只需在文件顶部放置它,在任何库或导入声明之前:
@TestOn('vm')
import 'dart:io';
import 'package:test/test.dart';
void main() {
// ...
}
@TestOn
所传递的字符串被称为 平台选择器
,它指定测试可在哪些平台上运行。它可以是平台名称,也可以是更复杂的像涉及这些平台名称的 Dart 的布尔表达式
还可以通过在包配置文件中添加 test_on 字段来声明整个包只在特定平台上工作
平台选择器
平台选择器使用 boolean_selector 包定义的布尔选择器(boolean selector syntax)语法,这是 Dart 表达式语法的子集,仅支持布尔运算。定义了以下标识符:
vm
- :测试是否在命令行 Dart VM 上运行。chrome
- :测试是否在 Google Chrome 上运行。firefox
- :测试是否在 Mozilla Firefox 上运行。safari
- :测试是否在 Apple Safari 上运行。ie
- :测试是否在 Microsoft Internet Explorer 上运行。node
- :测试是否在 Node.js 上运行。dart-vm
- :测试是否在任何上下文的 Dart VM 上运行。它与 -!js
- 完全相同。browser
- :测试是否在任何浏览器中运行。js
- :测试是否已编译为 JS。这与 -!dart-vm
- 完全相同。blink
- :测试是否在使用 Blink 渲染引擎的浏览器中运行。windows
- :测试是否在 Windows 上运行。这只有在 vm 或 node 为 true 时才为真。mac-os
- :测试是否在 MacOS 上运行。这只有在 vm 或 node 为 true 时才为真。linux
- :测试是否在 Linux 上运行。这只有在 vm 或 node 为 true 时才为真。android
- :测试是否在 Android 上运行。如果 vm 为 false,则此选项也为 false,这意味着如果测试在 Android 浏览器上运行,则此选项不为真。ios
- :测试是否在 iOS 上运行。如果 vm 为 false,则此选项也为 false,这意味着如果测试在 iOS 浏览器上运行,则此选项不为真。posix
- :测试是否在 POSIX 操作系统上运行。这相当于 -!windows
例如,如果要在每个浏览器上运行测试,但除了 Chrome,可以写 @TestOn('browser && !chrome')
在 Node.js 上运行测试
测试运行器还支持通过传递 --platform node
将测试编译成 JavaScript 并在 Node.js 上运行。请注意,Node 既无法访问 dart:html
也无法访问 dart:io
,因此将使用 js 包调用任何特定于平台的 API。但是,当测试用于 JavaScript 代码的 API 时,它可能很有用。
测试运行器在系统路径上查找名为 node(在 Mac OS 或 Linux 上)或 node.exe(在 Windows 上)的可执行文件。在编译 Node.js 测试时,它传递 -Dnode=true
, 因此测试可以使用 const bool.fromEnvironment('node')
确定是否在 Node 上运行。它还会设置 --server-mode
,这将告诉编译器 dart:html
不可用。