## 测试分成三层

| 层次 | 目标 | 不能替代什么 |
| --- | --- | --- |
| 纯逻辑自动化 | 输入、状态、取消与错误分支正确 | 不能证明真实文件 IPC 和窗口可用 |
| 桌面集成 | 实际宿主中打开、调用、保存和清理 | 不能证明正式包签名和授权登记正确 |
| 正式发行验收 | 用户实际收到的包可导入、更新和执行 | 不能仅用源码运行结果代替 |

先运行随示例提供的测试，再按自己的业务补充。不要只断言“函数存在”，也不要把实现逐行抄成断言来获得通过。

## 效率插件的业务测试

在文本整理示例根目录执行：

```powershell
node --test .tests/text.test.cjs
```


```javascript
const 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`，覆盖多种空白、中文、空输入和重复处理，防止例子与测试各自维护一份不同实现。

## 自动化任务的状态测试

在资料统计示例根目录执行：

```powershell
node --test .tests/actions.test.cjs
```


```javascript
const 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 环境中的逻辑测试，不假装已经验证桌面桥接。

## 为自己的业务补充边界

- 输入：空材料、丢失文件、错误类型、长名称、编码和大数据。
- 异步：重复开始、取消后立刻重试、旧请求晚到、窗口关闭。
- 保存：路径不足、只读原件、写入失败、写入时又有新修改。
- 状态：未知总量、不支持的状态版本、检查点缺文件。
- 兼容：缺少接口、旧宿主、资源路径变化和旧项目迁移。

每个失败用例写清预期状态、保留哪些内容，以及应出现什么提示。不要只判断没有抛异常。

## 如何记录测试结果

```text
组件：my-example-task 1.0.0
宿主：实际测试版本
环境：开发版 / 正式版，操作系统与运行环境
用例：运行后立即取消，再次开始
预期：旧运行不覆盖新结果，输入可编辑
实际：记录观察与必要的脱敏日志
结论：通过 / 未通过
```

保存真实执行过的结果，不在测试未运行时填写通过。出问题时记录阶段和最小输入，不收集用户密码或 API Key。

## 宿主项目检查

在宿主项目根目录执行 `npm run check:project`，再运行与改动有关的验证。组件独立测试通过，不代表宿主结构检查也通过；反过来也一样。

下一步到[桌面集成与性能验收](desktop-testing.html)，把逻辑测试中的接口替身替换为真正的桌面操作。
