FungLeo头像
成为全栈·Taro 小程序篇·小程序质量验证:逻辑测试、真实后端与开发工具各证明什么封面图

成为全栈·Taro 小程序篇·小程序质量验证:逻辑测试、真实后端与开发工具各证明什么

成为全栈·Taro 小程序篇·小程序质量验证:逻辑测试、真实后端与开发工具各证明什么

一份测试全绿,可以非常有用,也可以让人误判。差别在于我们是否知道它没有经过哪一层。

成为全栈·Taro 小程序篇·小程序质量验证:逻辑测试、真实后端与开发工具各证明什么

前言:编译没报错,按钮却不好点

小程序开发中出现过一个很实际的返工:界面文字容器看起来像操作入口,但在开发工具交互中不够稳定。改成原生 Button 后,预览切换、主题选择等动作才得到更明确的操作证据。

TypeScript 不会发现这个问题,构建也不会。另一方面,开发工具点几次又很难制造旧请求在账号切换后返回的精确时序。

所以,这篇不把所有验证都叫“测试通过”。我们按逻辑、真实后端、构建、工具和线上微信链路拆开,说明每份证据究竟保护什么。

先为结论选择对应证据

想证明的结论合适证据本轮情况
缓存旧请求不能回填控制 Promise 顺序的逻辑测试已有测试
他人不能改本人稿件真实后端不同身份请求已有本地集成
当前源码能生成 weapp类型与构建记录已通过
预览按钮与目录可操作开发工具实际点击已观察具体样本
真实微信 code 可登录工具 wx.login 加线上 callback修复后已成功
真机分享与相册完整可用对应设备与平台操作未逐项验收

第一条与第四条没有高低关系,它们只是回答不同问题。把一个逻辑测试叫端到端,会误导;因为没有真机就否认所有逻辑测试的价值,也同样不合理。

把每一层证据拆开看,它的射程与盲区其实很清楚:

证据层代表证据它能证明它到不了的地方
逻辑层Node 单测规则与完成顺序平台控件与真机
集成层临时 Hono 与 SQLite契约与业务路径微信身份与相册
构建层typecheck、build:weapp源码可产出 weapp 包运行期真实行为
工具层开发工具实际点击具体交互样本完整设备矩阵
线上层真实 wx.login平台交换链路全能力与并发压力

这张表也是本篇的目录。后面每一节对应其中一行,先说明这份证据是怎么产生的,再补上它没有覆盖的部分。读者读完应该能自己判断:某个结论需要哪一层证据,而不必笼统地说“测试通过了”。

Node 测试保留真实逻辑,替换平台边界

测试脚本用 esbuild 打包 test/core.test.ts,将 Taro 别名替换成测试模块,并注入 API 常量:

await build({
  entryPoints: ["test/core.test.ts"],
  outfile: ".test-build/core.test.cjs",
  bundle: true,
  define: { __API_BASE__: JSON.stringify("https://api.example.com/api/v1") },
  platform: "node",
  format: "cjs",
  alias: { "@tarojs/taro": resolve("test/taro-mock.ts") },
});

缓存、会话、解析和草稿使用实际模块,平台 request 与 storage 用可控制替身。这样测试关心规则,不需要为每个断言启动微信工具。

现有十一项覆盖读取合并、有界淘汰、失效栅栏、权限失败驱逐、单飞刷新、换账号、写入不重试、历史缓存、草稿、Markdown、评论与退出等重点。一个测试名称可能包含多个断言,不能拿“十一”当完备性比例。

当前没有独立覆盖 reaction store 和乐观回滚组件的专门测试,也没有所有 Hook 的渲染测试。文章不能把旁边相关测试算到不存在的覆盖上。

替身替换的正是那些会真正连到平台的地方。Taro 的 request 与 storage 在测试里换成可控制对象,于是缓存、会话、草稿这些模块仍运行真实代码,只有 IO 被接管。这样安排的好处是断言稳定:不必等待网络,也为每个用例省下一次启动微信工具的开销。

明确写出替身边界还有另一层意义:当某个缺陷只出现在平台侧时,逻辑测试很难提前暴露它。后面第七节的线上 500 正是这种情形,mock 本身没有问题,它只是本来就不覆盖那条平台参数差异。

证据替换边界

一次"全绿"的测试,让我以为并发问题已经解决

写完并发相关代码之后,我跑了一遍测试,全绿。

而那两个真实 bug(登出后旧数据回填、并发刷新导致被登出),一个都没有被这些测试抓到。

我去查了原因,发现两个测试都长这样:

// ❌ 当时写的:三个请求"同时"发起,但实际是顺序 resolve
const a = api("/me/profile");
const b = api("/me/profile");
await Promise.all([a, b]);   // 看起来测了并发,其实没有

api 的第一个 await 在内部,而 mock 的 resolve 是同步的 —— 于是 a 和 b 实际上一个跑完了另一个才开始。它测的是"两次顺序请求",不是"三个并发请求"。

改成真正控制顺序之后:

// ✅ 现在:显式控制 resolve 时机,让竞态真实发生
const wait = deferred<Envelope<Article>>();
const old = api("/me/profile");     // 发出但挂着
setSession(auth(2));                 // 中途换人
wait.resolve(ok("private-old"));      // 这时才返回
await assert.rejects(old, /账号已切换/);

区别只在于"resolve 发生在什么时候"。 而这一个差别,决定了这个测试是"测了并发"还是"假装测了并发"。

一个测试通过,不等于它验证了你以为它在验证的东西。 而判断的唯一方法是问:“如果我把这个 bug 引入回去,这个测试会红吗?”

这条判断标准现在是我看每个测试的第一问 —— 它比覆盖率数字有用得多。

竞态测试最重要的是完成顺序

缓存测试开始一个 deferred 请求,执行 clear,再释放旧响应,然后读取同键新值。关键断言是新读取必须得到新结果,而不是旧缓存。

const pending = c.read('a', () => wait.promise);
c.clear();
wait.resolve(1);
await pending;
assert.equal(await c.read('a', async () => 2), 2);

如果只测“读两次只调用一次 fetcher”,去重成立,失效仍可能错误。竞态必须把不利顺序明确制造出来。

账号测试同理:A 请求暂停,切到 B,释放 A 的成功响应,断言拒绝且当前会员仍是 B。它不是靠 sleep 等运气,而是控制异步边界。已有刷新测试用短延时形成重叠,断言仅刷新一次。

这些用例的共同之处是人为安排完成顺序,而不是等待一段时间。等待只能碰到“有时发生”的竞态,控制 Promise 才能稳定地把它变成必现路径,让失败可以复现、修复可以被验证。

验证证据分层

真实本地集成跨过 mock,但没有跨过微信平台

integration.mjs 启动真实 Hono 应用,创建临时 SQLite 并执行迁移,监听 127.0.0.1:13997。客户端请求模块仍通过测试 Taro 适配向该服务发送请求。

脚本用临时账号验证注册、资料、固定 ID 投稿、提交撤回、点赞收藏历史、评论回复级联、双账号越权拒绝与密码登录退出。测试发布夹具只在临时进程存在,结束清理入口与数据库。

因此它能检查契约与业务,但不能证明原生相册、输入焦点或分享接收。更不能把本地密码登录当作真实微信平台身份验证。

集成依赖相邻 node-backend 安装好。启动失败、端口占用与测试执行错误都应保留日志,避免一份旧 integration.json 被拿来为新改动背书。

integration 的价值在于它真的跨过了 mock 这一层:请求经过契约、路由、数据库与迁移,返回的也是真实响应。代价是它依赖启动环境,所以脚本在 finally 里清理临时数据库与入口文件,防止一次失败污染下一次运行。

构建证据要与源代码版本配对

交付记录的执行顺序包括 typecheck、test、test:integration、build:weapp。产物扫描确认十四个注册页面、四个 Tab、线上 API 地址、临时夹具未进入包。

758869 字节是文件总和,不是平台上传包体,更不是冷启动性能。正式包体检查、分包和设备测量有自己的口径。

文章写作阶段没有改业务代码,所以引用已有交付记录,同时注明这是历史验证结果。本篇不会因重新写了一次命令,就声称刚刚重跑了所有测试。

如果代码继续变化,应该重新执行对应门禁、更新证据与提交关联。文档更新时间本身不能证明产物更新。

把证据和版本配对,比把证据和时间配对更可靠。同一份命令在不同提交上可能得出不同结论,因此记录里既有命令,也有它当时的实现快照,读者才知道这份绿是对哪一个版本成立的。

工具操作必须记录数据环境

线上只读验收观察首页与 M3-24 正文,包含表格、高亮和目录;写交互使用另一个隔离本机后端,端口 13998,操作登录、输入、预览、保存与提交,再核对同一个 ID。

两种环境不能混淆。若文章写“线上投稿通过”,读者会以为向生产写过测试文章,实际没有。工具结束后恢复线上基址,临时服务与文件清理。

当时最后收尾遇到锁屏,未能追加最终产物的全部界面复看。已有操作证据保留,最后微调靠类型和构建确认。之后真实微信专项又更新了登录证据。验收记录中的时间先后要读完整,不能只取旧段落里的“未验证”。

环境写清楚,结论才有边界。只读验收与写交互分别记录在案,读者能分辨哪些操作碰过真实数据、哪些只发生在一台本机临时服务上。这份区分对以后复核同样有用:需要复现时,照着环境描述就能搭出接近的现场。

真实微信登录补上了运行时一层

配置 Secret 后线上仍 500,Node mock fetch 无法暴露 Workers 的 redirect 参数差异。workerd 复现、修复 manual 并拒绝 3xx,再使用真实 wx.login 才形成完整专项证据。

这说明 mock 不是错误,而是边界有限。平台交换需要真实运行时样本,逻辑测试负责其可控制的安全规则,双方都应保留。

真实登录成功后,会员中心显示首次设置入口;没有代用户设置密码,真实重复登录与跨 APP 设密矩阵也没有全部执行。这些边界写进专项文档,才不会把一条成功链路扩成全能力声明。

专项文档把这个顺序记完整:先复现、再定位差异、再修复、最后用真实链路确认。任何一步省略,结论都会少一层支撑。这也解释了为什么单看早先的记录会得出“未验证”的印象——它只是还没走到最后那一步。

测试记录也可能让人误读,必须检查断言

一条名为“评论循环保护”的测试,具体断言是分组结果不会因循环丢失所有节点并能终止。它没有证明循环数据会被自动纠正成业务上唯一的树。阅读测试名称之后,还要阅读输入与断言。

同样,“网络失败不重试写请求”统计了一次调用,它保护的是当前请求封装不自动再次写入,不覆盖用户手动第二次点击或另一设备。名词范围越大,越需要解释断言实际到哪里。

新增回归应该从已暴露问题出发

若进一步修复非卡片区域点赞计数,应为对应消费者补渲染断言;若增加上传刷新重放,应模拟认证失败、旧身份与文件任务;若做完整离线阅读,应检查磁盘过期和清理后回填。

这比为每个 getter 增加同构测试更有价值。测试应该捕获一个可能回来的错误,而不是机械照抄实现,让两份同样错误的代码互相通过。

当前文章写作不修改业务代码,也没有为未修复边界补虚假的通过记录。它们作为明确后续场景留下,避免审阅者把事实说明误解成代码变更。

把证据与提交关联起来

报告记录运行环境、命令、目标地址与结果,文章记录开始写作的实现快照。后续代码变化时,可以判断哪些证据需要更新,而不是用一份不知年代的截图作结论。

生产写入尤其要隔离。测试发布夹具绑定本机临时进程,不应进入上线应用;线上只读与真实身份登录分别记录,不发布无意义测试文章,也不代用户设置真实密码来制造完成感。

一份严谨验收还要保留失败与中断。锁屏导致未补最后点击回归,就写中断;平台能力未测,就写范围。可信度来自结论可追溯,不来自报告中只有绿色对勾。

把未验收项写成下一次可以执行的工作

尚缺相册上传、原生分享实际接收、真机键盘、弱网长时间使用、恢复弹窗的独立操作证据。每项都应该有输入条件、动作与预期,而不是模糊写“后续完善”。

例如上传期间继续输入后提交,检查文字不被旧快照覆盖;恢复草稿拒绝覆盖,检查本机文本还在;账号切换释放旧响应,检查私人内容不串。这些能分别补齐行为与时间顺序。

更多发布范围见 演示与正式发布差距,整体判断见 第五批复盘。可信交付,不是声称没有未知,而是让每个结论都有对应证据、每个未知都有清楚位置。

延伸阅读


如果这篇文章对你有帮助,欢迎订阅我的 CSDN 专栏 「成为全栈」:

🔗 专栏地址:https://blog.csdn.net/fungleo/category_13204651.html

📦 本系列配套代码仓库:https://github.com/fengcms/become-a-full-stack-developer

成为全栈专栏订阅

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/FungLeo/article/details/168963894

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--