备注
这是 readme.md 的简体中文翻译。这个链接 用来查看本翻译与 AVA 的 master 分支是否有差别(如果你没有看到 readme.md
发生变化,那就意味着这份翻译文档是最新的)。
未来的测试运行器
虽然 JavaScript 是单线程,但在 Node.js 里由于其异步的特性使得 IO 可以并行。AVA 利用这个优点让你的测试可以并发执行,这对于 IO 繁重的测试特别有用。另外,测试文件可以在不同的进程里并行运行,让每一个测试文件可以获得更好的性能和独立的环境。在 Pageres 项目中从 Mocha 切换 到 AVA 让测试时间从 31 秒下降到 11 秒。测试并发执行强制你写原子测试,意味着测试不需要依赖全局状态或者其他测试的状态,这是一件非常好的事情。
如果你想贡献(问题、PRs 等),请先阅读我们的贡献向导。
关注 AVA 的 Twitter 账号 以获取最新信息。
翻译:Español, Français, Italiano, 日本語, Português, 简体中文
- 轻量和高效
- 简单的测试语法
- 并发运行测试
- 强制编写原子测试
- 没有隐藏的全局变量
- 包含 TypeScript 和 Flow 的类型定义
- Magic assert
- 为每个测试文件隔离环境
- 用 ES2017 编写测试
- 支持 Promise
- 支持 Generator
- 支持 Async
- 支持 Observable
- 强化断言信息
- 可选的 TAP 输出显示
- 简明的堆栈跟踪
import test from 'ava';
test(t => {
t.deepEqual([1, 2], [1, 2]);
});
通过带 --init
参数运行 AVA 全局安装命令,将会添加 AVA 到 package.json
:
$ npm install --global ava
$ ava --init
如果你偏好 Yarn 的话:
$ yarn global add ava
$ ava --init
你的 package.json
会看起来像这样:
{
"name": "awesome-package",
"scripts": {
"test": "ava"
},
"devDependencies": {
"ava": "^0.20.0"
}
}
写在 --init
后面的任何参数都会被添加到 package.json
。
你也可以直接安装 AVA:
$ npm install --save-dev ava
或者使用 Yarn :
$ yarn add --dev ava
你还需要在你的 package.json
中配置 test
脚本来使用 ava
(参照前面)。
在你的工程根目录下创建一个名字为 test.js
的文件:
import test from 'ava';
test('foo', t => {
t.pass();
});
test('bar', async t => {
const bar = Promise.resolve('bar');
t.is(await bar, 'bar');
});
$ npm test
$ npm test -- --watch
AVA 拥有非常智能的观察模式。可以在它的秘方中查看详情。
$ ava --help
Usage
ava [<file|directory|glob> ...]
Options
--init Add AVA to your project
--fail-fast Stop after first test failure
--serial, -s Run tests serially
--tap, -t Generate TAP output
--verbose, -v Enable verbose output
--no-cache Disable the transpiler cache
--no-power-assert Disable Power Assert
--color Force color output
--no-color Disable color output
--match, -m Only run tests with matching title (Can be repeated)
--watch, -w Re-run tests when tests and source files change
--timeout, -T Set global timeout
--concurrency, -c Max number of test files running at the same time (Default: CPU cores)
--update-snapshots, -u Update snapshots
Examples
ava
ava test.js test2.js
ava test-*.js
ava test
ava --init
ava --init foo.js
Default patterns when no arguments:
test.js test-*.js test/**/*.js **/__tests__/**/*.js **/*.test.js
注意:在本地和全局都安装了 AVA 的情况下,本地安装的将会被优先使用。
文件夹会被递归遍历,所有 *.js
文件都会被作为测试文件。名字为 fixtures
,helpers
和 node_modules
的文件夹总会被忽略。所以把 helper 名字以 _
开头命名就可以一起放置在测试文件的目录下。
当使用 npm test
时你可以直接传参数 npm test test2.js
,但标志需要像这样传递 npm test -- --verbose
。
AVA 在子进程中运行测试,因此为了调试测试,需要用如下方法:
$ node --inspect node_modules/ava/profile.js some/test/file.js
这是默认的报告器。
使用 --verbose
参数可以启用详细报告器。这在 CI 环境下始终会被启用,除非已经使用了 TAP 报告器
AVA 支持 TAP 格式,并且与 任何 TAP 报告器 相兼容。使用 --tap
选项来启用 TAP 输出。
$ ava --tap | tap-nyan
注意 TAP 报告器在 监视模式 下不可用。
AVA 加入了代码摘录和干净的 diff 到实际和期望的值。如果断言中的值是 object 或者 array,那么只有 diff 会显示出来,去掉了干扰让人专注问题。同时 diff 是带语法高亮的!如果你正在比较两个 string ,不管是单行还是多行的,AVA 都使用另一种 diff 显示,来高亮出新增和删除的字符。
AVA 会自动去掉调用栈中不相关的行,让你能够更快地定位错误的根源。
所有的 CLI 选项都可以配置在 package.json
的 ava
属性中。你可以修改 ava
命令的默认行为,所以你不需要重复在命令行中输入相同的选项。
{
"ava": {
"files": [
"my-test-folder/*.js",
"!**/not-this-file.js"
],
"source": [
"**/*.{js,jsx}",
"!dist/**/*"
],
"match": [
"*oo",
"!foo"
],
"failFast": true,
"tap": true,
"require": [
"babel-register"
],
"babel": "inherit"
}
}
传递给 CLI 的参数总是比 package.json
中的配置优先级高。
请看 支持 ES2015 章节以了解 babel
选项的更多详情。
测试是并发执行的,你可以选择同步或异步执行测试,在返回一个 promise 或 observable 时你才需要考虑使用同步。
我们强烈推荐使用 async 函数,它让异步代码简洁更具可读性,并且它隐式返回一个 promise 让你无需手动创建。
如果你不能使用 promise 或者 observales,你可以通过这样定义你的测试 test.cb([title], fn)
来启用 “callback 模式”。通过这种方式声明的测试必须手动添加 t.end()
来结束,这种模式的主要目的是为了测试 callback 方式的 API。
你必须同时将所有测试都定义为同步,它们不能定义在 setTimeout
,setImmediate
等里面。
测试文件在当前文件夹中被执行,所以 process.cwd()
和 __dirname
总是相同。你可以使用相对路径代替 path.join(__dirname, 'relative/path')
操作。
你可以通过调用 AVA 中导入的 test
方法来创建一个测试。提供可选的标题和 callback 函数,函数将在你运行测试时被调用。它会传递一个 执行对象 作为其第一个且唯一的一个参数。按照惯例这个参数名字为 t
。
import test from 'ava';
test('my passing test', t => {
t.pass();
});
标题是可选的,意味着你可以这样:
test(t => {
t.pass();
});
如果你有多个测试的话建议写上测试标题。
如果你没有提供测试标题,但 callback 是一个有名字的函数,那这个名字将作为测试标题:
test(function name(t) {
t.pass();
});
断言计划确保测试只能通过指定次数的断言,它们可以帮你捕捉测试太早退出的情况,如果太多断言被执行的话它们也会让测试失败,如果在你的 callback 或循环中有断言的话断言计划会比较有用。
请注意:不像 tap
和 tape
,当断言计划数达到了指定数量 AVA 并不会自动结束。
这些例子将会通过测试:
test(t => {
t.plan(1);
return Promise.resolve(3).then(n => {
t.is(n, 3);
});
});
test.cb(t => {
t.plan(1);
someAsyncFunction(() => {
t.pass();
t.end();
});
});
这些则不会:
test(t => {
t.plan(2);
for (let i = 0; i < 3; i++) {
t.true(i < 3);
}
}); // 失败,3 个断言被执行,但计划只有 2 个
test(t => {
t.plan(1);
someAsyncFunction(() => {
t.pass();
});
}); // 失败,在断言执行之前测试会因为同步而提前结束。
测试默认是并发运行的,这很棒。但有时你必须写不是并发运行的测试。
这种情况比较少见,但你可以使用 .serial
修饰符,它将强制让那些测试在并发的测试前先串行运行。
test.serial(t => {
t.pass();
});
注意这只影响一个特定测试文件里面的测试,AVA 将仍然同时运行多个测试文件,除非你传递 --serial
CLI 标志。
在开发中只运行少量指定的测试非常有用,这可以通过使用 .only
修饰符来完成。
test('will not be run', t => {
t.fail();
});
test.only('will be run', t => {
t.pass();
});
.only
影响所有测试文件,所以你在一个文件里面使用了它,那其他测试文件里的测试将不会运行。
--match
标志允许你只运行包含匹配标题的测试,这可以通过简单的通配模式来做到,模式是大小写敏感的,详情请看 matcher
。
匹配以 foo
结尾的标题:
$ ava --match='*foo'
匹配以 foo
开头的标题:
$ ava --match='foo*'
匹配包含了 foo
的标题:
$ ava --match='*foo*'
匹配精确等于 foo
的标题(尽管大小写敏感):
$ ava --match='foo'
匹配不包含了 foo
的标题:
$ ava --match='!*foo*'
匹配以 foo
开头并且以 bar
结束的标题:
$ ava --match='foo*bar'
匹配以 foo
开头或者以 bar
结束的标题:
$ ava --match='foo*' --match='*bar'
注意,匹配模式优先级高于 .only
修饰符,只有带明确标题的测试可以被匹配,当使用 --match
时,没有标题的或者标题是从 callback 函数名来的测试都会被跳过。
下面是当使用一个匹配模式 *oo*
来匹配测试的结果:
test('foo will run', t => {
t.pass();
});
test('moo will also run', t => {
t.pass();
});
test.only('boo will run but not exclusively', t => {
t.pass();
});
// 不会运行,没有标题
test(function (t) {
t.fail();
});
// 不会运行,没有明确定义的标题
test(function foo(t) {
t.fail();
});
有时候失败的测试一时难以修复,你可以使用 .skip
来告诉 AVA 跳过这些测试。它们仍然会显示在输出结果中(标识为 skipped),但不会被运行。
test.skip('will not be run', t => {
t.fail();
});
你必须指定 callback 函数。
当你计划写一个测试的时候你可以使用 .todo
修饰符,像跳过测试一样这些占位符也会显示在输出结果中,它们只要求一个标题,你不能指定 callback 函数。
test.todo('will think about writing this later');
AVA 让你可以注册在测试之前和之后跑的钩子,这允许你执行 setup 和 teardow 代码。
test.before()
在你的测试文件中注册了一个钩子并在第一个测试前运行,同样地,test.after()
注册了一个钩子在最后一个测试后运行。
test.beforeEach()
在你的测试文件中注册了一个钩子并在每个测试前运行,同样地,test.afterEach()
注册了一个钩子在每个测试后运行。
像 test()
这些方法一样接收一个可选的标题和一个 callback 函数,如果你的钩子失败了标题就会显示出来,callback 函数传递一个 执行对象。
before
在 beforeEach
之前执行,afterEach
在 after
之前执行,同一类的钩子按照它们定义的顺序来执行。
test.before(t => {
// 这个会在所有测试前运行
});
test.before(t => {
// 这个会在上面的方法后面运行,但在测试之前运行
});
test.after('cleanup', t => {
// 这个会在所有测试之后运行
});
test.beforeEach(t => {
// 这个会在每个测试之前运行
});
test.afterEach(t => {
// 这个会在每个测试之后运行
});
test(t => {
// 正常的测试
});
钩子可以同步或异步,就像测试一样。让钩子异步返回一个 promise 或 observable,使用一个 async 函数,或者通过 test.before.cb()
,test.beforeEach.cb()
等来启用 callback 模式。
test.before(async t => {
await promiseFn();
});
test.after(t => {
return new Promise(/* ... */);
});
test.beforeEach.cb(t => {
setTimeout(t.end);
});
test.afterEach.cb(t => {
setTimeout(t.end);
});
请记住,beforeEach
和 afterEach
钩子在一个测试之前和之后运行,并且默认情况下测试是并发运行的,如果你需要为每个测试设置一个全局的状态(比如 console.log
的 spying 例子),你需要确保这些测试是串行运行的。
记住,AVA 运行每个测试文件会有各自单独的进程,你可能不需要在 after
钩子中清理全局状态,因为它只会在进程退出前被调用。
beforeEach
和 afterEach
钩子可以共享测试的上下文:
test.beforeEach(t => {
t.context.data = generateUniqueData();
});
test(t => {
t.is(t.context.data + 'bar', 'foobar');
});
默认情况下 t.context
是一个对象,但你可以重新赋值:
test.beforeEach(t => {
t.context = 'unicorn';
});
test(t => {
t.is(t.context, 'unicorn');
});
上下文共享在 before
和 after
钩子中不可用。
你可以以任何顺序使用 .serial
,.only
和 .skip
,与 test
,before
,after
,beforeEach
和afterEach
一起,比如:
test.before.skip(...);
test.skip.after(...);
test.serial.only(...);
test.only.serial(...);
这意味着你可以在每个测试或钩子的末尾临时添加 .skip
或 .only
,而不用做其他的修改。
给到测试定义的额外参数会被传给测试的实现,这个特性可被用于创建可重用的测试宏。
function macro(t, input, expected) {
t.is(eval(input), expected);
}
test('2 + 2 = 4', macro, '2 + 2', 4);
test('2 * 3 = 6', macro, '2 * 3', 6);
通过实现宏的 title
方法,能够动态的为测试添加标题:
function macro(t, input, expected) {
t.is(eval(input), expected);
}
macro.title = (providedTitle, input, expected) => `${providedTitle} ${input} = ${expected}`.trim();
test(macro, '2 + 2', 4);
test(macro, '2 * 3', 6);
test('providedTitle', macro, '3 * 3', 9);
如果不设置标题,providedTitle
参数默认是空字符串。因此能够安全的进行字符串的连接操作,而不用担心在 null
/ undefined
上调用方法而导致报错。空字符串是假值,if(providedTitle) {...}
这样的条件判断是完全成立的。
还可以向宏方法传入数组:
const safeEval = require('safe-eval');
function evalMacro(t, input, expected) {
t.is(eval(input), expected);
}
function safeEvalMacro(t, input, expected) {
t.is(safeEval(input), expected);
}
test([evalMacro, safeEvalMacro], '2 + 2', 4);
test([evalMacro, safeEvalMacro], '2 * 3', 6);
我们鼓励用宏代替此类测试生成代码。宏的设计初衷是静态分析你的代码,因此有着更好的效率、IDE 集成以及校验规则。
你可以使用其他断言库来替代内置的断言库,当断言失败可以让其抛出异常。
但这样做的话你将得不到内置断言库的良好体验,同时你也将不会用到[断言计划](#断言计划) (『看#25](https://github.com/avajs/ava/issues/25))。
import assert from 'assert';
test(t => {
assert(true);
});
AVA 通过 Babel 6 内置支持 ES2015,只需要用 ES2015 的方式写你测试,不需要额外的配置。你可以在你的工程中使用任何的 Babel 版本,我们使用我们捆绑的 Babel,带有 es2015
和 stage-2
设置,和 espower
和 transform-runtime
插件一样。
类似的 Babel 配置同样适用于 AVA,如下:
{
"presets": [
"es2015",
"stage-2",
],
"plugins": [
"espower",
"transform-runtime"
]
}
你可以自定义 AVA 如何通过 package.json
配置 babel
选项来转换测试文件,例如你可以这样来覆盖配置:
{
"ava": {
"babel": {
"presets": [
"es2015",
"stage-0",
"react"
]
}
}
}
你也可以使用特殊的 "inherit"
关键字,这让 AVA 遵从你的 .babelrc
或 package.json
文件 中的 Babel 配置,这种方式让测试文件的转换方式和与源文件的相同,可以无需在 AVA 中重复配置。
{
"babel": {
"presets": [
"es2015",
"stage-0",
"react"
]
},
"ava": {
"babel": "inherit"
}
}
注意,AVA 总是应用 espower
和 transform-runtime
插件。
AVA 支持 TypeScript,你必须自己配置转换规则,当你在你的 tsconfig.json
文件中把 module
设为 commonjs
,TypeScipt 将自动为 AVA 找到类型定义。你应该把 target
设置为 es2015
来使用 promises 和 async 函数。
AVA 现在只转换需要运行的测试,它不会转换那些在测试中你 import
的模块,这可能不是你所期望的但这就是现在的工作方案。
如果你使用 Babel 你可以使用它的 require 钩子 来实时转换导入模块,运行 AVA 带上 --require babel-register
(请看 CLI) 参数或在你的 package.json
里配置。
你也可以在另外一个进程里转换你的模块,并且参考你的转换文件而不是你的测试源码。
如果你在测试里返回一个 promise,你不需要在测试里明确的结束测试,当 promise resolve 的时候它会自己结束。
test(t => {
return somePromise().then(result => {
t.is(result, 'unicorn');
});
});
AVA 自带对 generator 函数 的内置支持。
test(function * (t) {
const value = yield generatorFn();
t.true(value);
});
AVA 自带对 async functions (async/await) 的内置支持。
test(async function (t) {
const value = await promiseFn();
t.true(value);
});
// 异步箭头函数
test(async t => {
const value = await promiseFn();
t.true(value);
});
AVA 自带对 observables 的内置支持。如果你从测试中返回一个 observable,AVA 会自动消费它使其在测试结束前完成。
你不需要使用 “callback 模式" 或者调用 t.end()
。
test(t => {
t.plan(3);
return Observable.of(1, 2, 3, 4, 5, 6)
.filter(n => {
// 只有奇数
return n % 2 === 0;
})
.map(() => t.pass());
});
当使用 node-style, error-first 等 callback API 时,AVA 支持使用 t.end
作为最后一个 callback 函数。AVA 将把任何为真的值传递给 t.end
其实变成一个 error。注意,t.end
要求 “callback 模式”,这个可以通过使用 test.cb
链来启用。
test.cb(t => {
// t.end 自动检查第一个参数是否为错误
fs.readFile('data.txt', t.end);
});
AVA 可以通过 --tap
选项来生成 TAP 的输出,可以选择任意的 TAP 报告。
$ ava --tap | tap-nyan
AVA 会在堆栈跟踪信息里面自动移除不相关的行,让你更快地找到错误的原因。
类型:string
测试标题
类型:function
应该包含实际的测试。
类型:object
特定测试的执行对象,每个测试的 callback 接收到一个不同的对象,包含断言,.plan(count)
和 .end()
等方法。t.context
可以包含 beforeEach
钩子中的共享状态。
计划有多少断言将在测试中被执行,如果实际断言的数量没有匹配计划数,那么测试将失败,详情请见断言计划。
结束测试,只在 test.cb()
中有效。
断言也被包含在 执行对象 中,可以提供给每个测试 callback:
test(t => {
t.truthy('unicorn'); // 断言
});
如果单个测试中有多个断言同时失败了,那 AVA 只会显示第一个。
测试通过。
断言失败。
断言 value
是否是真值。
断言 value
是否是假值。
断言 value
是否是 true
。
断言 value
是否是 false
。
断言 value
是否和 expected
相等。
断言 value
是否和 expected
不等。
断言 value
是否和 expected
深度相等。
断言 value
是否和 expected
深度不等。
断言 function
抛出一个异常,或者 promise
reject 一个错误。
error
可以是一个构造器,正则,错误信息或者验证函数。
返回由 function
抛出的异常或 promise
的拒绝原因。
断言 function
没有抛出一个异常,或者 promise
resolve。
断言 contents
匹配 regex
。
断言 contents
不匹配 regex
。
断言 error
是假值。
通过 skip
修饰符可以跳过任何断言,跳过的断言仍然会被计数,所以不需要去改变你的断言计划数量。
test(t => {
t.plan(2);
t.skip.is(foo(), 5); // 当跳过时不需要改变你的计划数
t.is(1, 1);
});
AVA 自带内置的 power-assert
,给你更多的描述性断言信息,它阅读你的测试代码并从中试图推断出更多信息。
我们来举个例子,使用 Node 的标准 断言
库。
const a = /foo/;
const b = 'bar';
const c = 'baz';
require('assert').ok(a.test(b) || b === c);
如果你把代码粘贴到 Node REPL 中会返回:
AssertionError: false == true
而在 AVA 中,这个测试:
test(t => {
const a = /foo/;
const b = 'bar';
const c = 'baz';
t.true(a.test(b) || b === c);
});
将会输出:
t.true(a.test(b) || b === c)
| | | |
| "bar" "bar" "baz"
false
每个测试文件都会在一个独立的 Node 进程中运行,这样让你可以在一个测试文件中改变全局状态或将其覆盖一个内置的全局状态,而不会影响其他测试文件。这样更加有效地利用现代的多核处理器,让多个测试可以并发地执行。
并发运行测试面临着许多挑战,文件的 IO 操作就是其中一个。
一般来讲,串行测试在当前文件夹中创建临时文件夹,然后会在测试结束的时候清理它们。但这种做法在并发测试中不起作用,因为测试会一起操作这个文件夹而导致冲突。正确的做法是为每个测试创建一个新的临时文件夹,使用 tempfile
和 temp-write
模块可能会有帮助。
AVA 默认情况下是并发执行测试,这样在调试信息时并不是最理想的,你可以通过设置 --serial
选项来让测试串行执行。
$ ava --serial
你不能使用 istanbul
来做代码覆盖率因为 AVA [处理过这些测试文件](#隔离进程),你可以使用 nyc
来代替,你可以把它看做是支持子进程的 istanbul
。
从版本 5.0.0
开始,在报告覆盖率时为你的代码使用原生映射,而不是转换后的代码。确保你测试的代码包括了一个内联的原生映射或者引用了一个原生映射文件。如果你使用了 babel-register
,你可以在你的 Babel 配置中将 sourceMaps
设置为 inline
。
Mocha 要求你使用隐式全局变量比如 describe
和 it
作为其默认接口(这是大部分人使用的)。这样做不是很好,并且串行执行测试没有进程隔离,使得测试十分缓慢。
Tape 和 tap 是非常好的。AVA 在它们的语法中得到大量启发,但它们也是串行执行测试,它们的默认 TAP 输出不是非常友好,因此你总是需要使用额外的 tap 报告。
与它们不同的是,AVA 可以并发执行测试,为每个测试文件提供独立进程,它的默认报告简单明了,并且 AVA 也支持通过 CLI 标志来输出一个 TAP 报告。
AVA 支持 TAP 格式,所以它兼容任何 TAP 报告,使用 --tap
标志来启用 TAP 输出。
AVA,不是 Ava,也不是 ava,发音 /ˈeɪvə/
ay-və。
它是处女座星系。
- sublime-ava - AVA 测试的代码片段
- atom-ava - AVA 测试的代码片段
- vscode-ava - AVA 测试的代码片段
- eslint-plugin-ava - AVA 测试的代码规则
- gulp-ava - 用 gulp 运行测试
- grunt-ava - 用 grunt 运行测试
- fly-ava - 用 fly 运行测试
- start-ava - 用 start 运行测试
Sindre Sorhus | Vadim Demedes | James Talmage | Mark Wubben |