鸿蒙 PC Markdown 编辑器原生测试:ohosTest 验证文档与大纲服务

Web自动化可以证明 CodeMirror和预览逻辑,却无法证明 HarmonyOS Core File Kit在目标运行时的真实读写语义。BOM、fsync、AtomicFile、应用沙箱和 ArkTS字符串偏移必须在 ohosTest环境执行。否则 Node测试全绿,设备文件仍可能短写或格式变化。

本文基于 OhMarkdown,说明 Hypium测试模块、沙箱隔离、字节读取、故障注入、大纲偏移和设备结果。代码位于 https://gitcode.com/VON-/codex_md_oh,对应提交 3a9146e

测试模块独立于 entry 主模块

entry/src/ohosTest/module.json5

{
  "module": {
    "name": "entry_test",
    "type": "feature",
    "deviceTypes": [
      "phone",
      "2in1"
    ],
    "deliveryWithInstall": true,
    "installationFree": false
  }
}

测试目标同时声明 phone与2in1,当前重点在 MateBook Pro 2in1模拟器。入口文件只注册服务测试:

import documentServiceTest
  from './DocumentService.test';

export default function testsuite() {
  documentServiceTest();
}

保持入口简单,测试分组由具体文件负责。新增 Workspace或 Recovery测试时可独立文件注册,不把所有逻辑堆在 List.test。

使用应用测试上下文

const context = abilityDelegatorRegistry
  .getAbilityDelegator()
  .getAppContext();
const testPath =
  `${context.filesDir}/${TEST_FILE_NAME}`;

测试文件位于测试应用沙箱,不污染用户 Documents,也不依赖系统选择器。使用真实 context.filesDir让 RecoveryService路径与生产一致。

沙箱测试不能替代用户 provider URI,但适合可重复字节和故障场景。选择器授权仍在模拟器手工/自动化层验证。

beforeEach 和 afterEach 双重清理

beforeEach(async () => {
  if (await fileIo.access(testPath)) {
    await fileIo.unlink(testPath);
  }
  if (await fileIo.access(faultPath)) {
    await fileIo.unlink(faultPath);
  }
  if (await fileIo.access(faultDirectory)) {
    await fileIo.rmdir(faultDirectory);
  }
  await clearPendingSaveBackup(context.filesDir);
});

afterEach重复相同清理。before保证上次异常中断不影响当前用例,after保证成功测试不影响下一项。异步文件操作全部 await,不能让清理与测试并发。

删除顺序先文件后目录,避免非空目录 rmdir失败。恢复备份是共享沙箱资源,也必须清理。

测试辅助写入不复用被测函数

async function writeRawText(
  path: string,
  content: string
): Promise<void> {
  const file = await fileIo.open(
    path,
    fileIo.OpenMode.CREATE |
      fileIo.OpenMode.READ_WRITE
  );
  try {
    const writtenBytes = await fileIo.write(
      file.fd,
      content,
      { offset: 0, encoding: 'utf-8' }
    );
    await fileIo.truncate(file.fd, writtenBytes);
    await fileIo.fsync(file.fd);
  } finally {
    await fileIo.close(file);
  }
}

构造输入不调用 writeUtf8Document,否则用被测序列化器生成期望文件,会让同一缺陷同时存在于准备和验证。辅助函数只按给定字符串原样写 UTF-8。

更严格可断言 writtenBytes等于预期,测试辅助自身也要可靠。当前小语料在设备验证中稳定。

字节比较绕过解码

const stat = await fileIo.stat(file.fd);
const data = new ArrayBuffer(stat.size);
const bytesRead = await fileIo.read(
  file.fd,
  data,
  { length: stat.size }
);
const bytes = new Uint8Array(
  data,
  0,
  bytesRead
);
let result = '';
for (let index = 0;
  index < bytes.length;
  index += 1) {
  result += bytes[index]
    .toString(16)
    .padStart(2, '0');
}

返回十六进制用于 assertEqual。BOM与 CRLF不能通过再次调用 readUtf8Document验证,因为读取器会抽象它们;必须比较原始字节。

生产测试若扩展到大文件,应用哈希更节省内存。当前数十字节 fixture用完整 hex能在失败时直接看到差异。

BOM 与 CRLF 用例

it('UTF-8 BOM 与 CRLF 保存后字节一致',
  0,
  async () => {
    const original =
      '\uFEFF# 鸿蒙 PC\r\n\r\n' +
      '第一行\r\n第二行\r\n';
    await writeRawText(testPath, original);
    const beforeHex = await readBytesAsHex(testPath);
    const opened = await readUtf8Document(testPath);
    expect(opened.format.hasUtf8Bom).assertTrue();
    expect(opened.format.lineEnding)
      .assertEqual(LineEnding.CRLF);
    await writeUtf8Document(
      testPath,
      opened.content,
      opened.format
    );
    expect(await readBytesAsHex(testPath))
      .assertEqual(beforeHex);
  }
);

它同时验证读取格式元数据、内存正文去 BOM、保存序列化和 Core File Kit写入。字节相等是最终门槛。

用例名使用中文,设备报告直接表达目标。第二个参数0是测试过滤/级别参数,异步函数由 Hypium等待。

Mixed 换行归一

输入包含 CRLF、LF和单独 CR,读取应为 MIXED。指定 LF保存后重新读取,正文必须全是 \n,检测为 LF。

该用例验证的是服务显式转换,不涉及 UI策略对话框。UI取消、LF、CRLF三按钮需要模拟器交互测试。测试层必须说明覆盖范围,不能把服务用例冒充完整工作流。

目录删除制造确定故障

await fileIo.mkdir(faultDirectory);
await writeRawText(faultPath, previousContent);
await savePendingSaveBackup(
  context.filesDir,
  {
    version: 1,
    documentUri: faultPath,
    documentName: TEST_FILE_NAME,
    previousContent,
    hasUtf8Bom: false,
    lineEnding: LineEnding.LF,
    updatedAt: Date.now()
  }
);
await fileIo.unlink(faultPath);
await fileIo.rmdir(faultDirectory);

目标父目录不存在,writeUtf8Document必然失败。相比模拟磁盘满,目录删除易重复且不影响设备全局。异常后断言 backup仍能加载、previousContent正确,再重建目录并用备份恢复。

这项用例证明失败不会清理唯一旧版本。它尚未覆盖写到一半失败和 fsync失败,需要可注入文件适配层或平台故障工具。

大纲服务在 ArkTS 运行时验证

describe('Markdown 大纲', () => {
  it('设备端提取标题与 UTF-16 跳转偏移',
    0,
    () => {
      const content =
        '# 鸿蒙 PC\n\n正文\n---\n\n' +
        '```md\n## 代码标题\n```\n\n' +
        '### 目标标题';
      const headings =
        extractMarkdownHeadings(content);
      expect(headings.length).assertEqual(3);
      expect(headings[2].offset).assertEqual(
        content.indexOf('### 目标标题')
      );
    }
  );
});

无需文件 I/O的纯函数也值得在 ArkTS测试,确认目标运行时正则和字符串索引。中文让 UTF-8字节方案无法误通过,代码围栏验证状态机过滤。

UnitTestBuild 与设备执行不同

Hvigor UnitTestBuild验证测试代码能够编译打包,不等于用例已在设备运行。质量记录分别写:UnitTestBuild成功;ohosTest安装并在 MateBook Pro 2in1执行4/4成功。

如果没有连接目标,只能宣称构建成功。测试报告中把“设备执行”与“调用链审查”分开,防止质量数字虚高。

鸿蒙 PC 测试后的应用

下图为设备测试使用的文档格式版本运行在模拟器中。ohosTest本身输出在测试运行器,应用截图证明同一服务进入真实 HAP。

在这里插入图片描述

理想证据还应保存测试运行器结果截图或结构化报告,但每篇文章至少包含应用内部画面。截图不替代4/4日志。

测试结果读取

设备执行结束应记录目标名、应用版本、构建模式、用例数、失败堆栈和时间。HDC连接断开、安装失败与断言失败是不同状态,脚本不能把“没有结果”当成功。

自动提取可解析 Hypium报告并生成 Markdown测试报告。日志中不得写 fixture之外的用户正文和 URI。

可扩展的测试结构

后续应拆分 DocumentFormat.testRecovery.testOutline.testWorkspace.test,List只注册。共享临时目录助手可减少清理重复,但不能隐藏故障步骤。

参数化矩阵适合 BOM×换行×末尾换行;故障用例保持独立名称。每项先构造、执行、按字节/状态断言、清理。

当前边界

现有设备用例只有4项;AtomicFile强杀、用户 picker URI、两千项目录、权限撤销、主题生命周期和多标签关闭尚未自动化。模拟器通过不等于真机 provider行为。

结语

ohosTest把文本保真从算法推断带到 HarmonyOS真实运行时:Core File Kit写入、字节读取、沙箱备份、故障恢复和 UTF-16偏移都有设备断言。测试辅助不复用被测序列化器,前后清理保证隔离。

鸿蒙 PC编辑器的质量不能只有浏览器测试。凡是文件和系统 API参与的承诺,都需要在目标平台产生可重复证据,并诚实区分编译通过与设备执行通过。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐