编写自动化测试
运行随源码下载的测试,覆盖业务、取消与失败。
本页目录
测试分成三层#
| 层次 | 目标 | 不能替代什么 |
|---|---|---|
| 纯逻辑自动化 | 输入、状态、取消与错误分支正确 | 不能证明真实文件 IPC 和窗口可用 |
| 桌面集成 | 实际宿主中打开、调用、保存和清理 | 不能证明正式包签名和授权登记正确 |
| 正式发行验收 | 用户实际收到的包可导入、更新和执行 | 不能仅用源码运行结果代替 |
先运行随示例提供的测试,再按自己的业务补充。不要只断言“函数存在”,也不要把实现逐行抄成断言来获得通过。
效率插件的业务测试#
在文本整理示例根目录执行:
node --test .tests/text.test.cjsconst test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
function load() {
const context = {};
context.window = context;
vm.runInNewContext(fs.readFileSync(
path.join(__dirname, '../modules/text.js'), 'utf8'), context);
return context.MyExampleTool.normalize;
}
test('合并空格、换行和制表符,保留文本顺序', () => {
assert.equal(load()(' alpha\n\t beta '), 'alpha beta');
});
test('保留中文内容,空输入得到空字符串', () => {
const normalize = load();
assert.equal(normalize(' 题目\n分析 '), '题目 分析');
assert.equal(normalize(' '), '');
});
test('已经整理的结果重复处理不改变内容', () => {
const normalize = load();
const once = normalize(' A B ');
assert.equal(normalize(once), once);
});测试直接加载下载包中的 modules/text.js,覆盖多种空白、中文、空输入和重复处理,防止例子与测试各自维护一份不同实现。
自动化任务的状态测试#
在资料统计示例根目录执行:
node --test .tests/actions.test.cjsconst test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
function load() {
const context = { setTimeout, clearTimeout };
context.window = context;
for (const name of ['state', 'actions']) {
vm.runInNewContext(fs.readFileSync(path.join(__dirname,
'../modules/' + name + '.js'), 'utf8'), context);
}
return context.MyExampleTask;
}
test('正常统计得到真实结果', async () => {
const task = load(), state = task.createState();
await task.run(state, '甲乙\n丙', () => {});
assert.equal(state.status, 'completed');
assert.deepEqual(JSON.parse(JSON.stringify(state.result)),
{ lines: 2, characters: 3 });
assert.equal(state.processed, state.total);
});
test('空输入失败,并且没有伪造结果', async () => {
const task = load(), state = task.createState();
await task.run(state, ' ', () => {});
assert.equal(state.status, 'failed');
assert.match(state.error, /材料/);
assert.equal(state.result, null);
});
test('分批运行中取消,旧任务不写回完成', async () => {
const task = load(), state = task.createState();
await task.run(state, 'a '.repeat(20000), () => {
if (state.processed > 0) task.cancel(state, () => {});
});
assert.equal(state.status, 'cancelled');
assert.equal(state.result, null);
await task.run(state, '新任务', () => {});
assert.equal(state.status, 'completed');
assert.equal(state.result.characters, 3);
});
test('关闭后不让旧运行继续回写', async () => {
const task = load(), state = task.createState();
await task.run(state, 'x'.repeat(20000), () => {
if (state.processed > 0) { state.disposed = true; state.runId++; }
});
assert.equal(state.result, null);
assert.notEqual(state.status, 'completed');
});测试覆盖正确结果、空输入、取消与关闭后旧运行不回写。它使用组件真实的 state 与 actions 模块,但仍是 Node 环境中的逻辑测试,不假装已经验证桌面桥接。
为自己的业务补充边界#
- 输入:空材料、丢失文件、错误类型、长名称、编码和大数据。
- 异步:重复开始、取消后立刻重试、旧请求晚到、窗口关闭。
- 保存:路径不足、只读原件、写入失败、写入时又有新修改。
- 状态:未知总量、不支持的状态版本、检查点缺文件。
- 兼容:缺少接口、旧宿主、资源路径变化和旧项目迁移。
每个失败用例写清预期状态、保留哪些内容,以及应出现什么提示。不要只判断没有抛异常。
如何记录测试结果#
组件:my-example-task 1.0.0
宿主:实际测试版本
环境:开发版 / 正式版,操作系统与运行环境
用例:运行后立即取消,再次开始
预期:旧运行不覆盖新结果,输入可编辑
实际:记录观察与必要的脱敏日志
结论:通过 / 未通过保存真实执行过的结果,不在测试未运行时填写通过。出问题时记录阶段和最小输入,不收集用户密码或 API Key。
宿主项目检查#
在宿主项目根目录执行 npm run check:project,再运行与改动有关的验证。组件独立测试通过,不代表宿主结构检查也通过;反过来也一样。
下一步到桌面集成与性能验收,把逻辑测试中的接口替身替换为真正的桌面操作。