发版前,应用已经把入场凭证保存成二维码 PNG,界面预览也正常;真正扫码时,却可能因为缩放插值、边缘裁切或前景与背景过于接近而失败。与其只断言“文件存在”,不如在云端 Mac 上取出应用最终导出的图片,用 Vision 再读一遍,并核对读出的内容。这是一道适合放进 iOS 回归流程的文件级检查。
先约定验收边界
本文检查的是应用实际写出的 PNG,不是测试脚本自己生成的二维码。固定测试载荷为 case-123,应用的测试入口应将对应图片写入其沙盒 Documents/qr-sample.png。如果产品只把二维码画在屏幕上,可以先通过现有导出功能取得文件;不要另写一套测试生成器冒充生产输出。
合格条件有三个:文件能从应用容器取出;Vision 恰好识别出一个 QR 码;解出的字符串与预期逐字相同。文件哈希不适合做主要断言:编码参数或 PNG 元数据变化可能改变字节,却不改变二维码内容。
这里的“可识别”只针对导出的数字图片。相机对焦、屏幕亮度和打印尺寸会引入额外变量,文件级验收不能取代实际扫码。
从模拟器取出最终文件
先在已启动的 iOS 模拟器中运行测试入口,确认应用确实写入了约定路径。下列命令要求环境变量 BUNDLE_ID 已设为被测应用的真实 bundle identifier;get_app_container 会返回该应用的数据容器路径。
test -n "$BUNDLE_ID" || exit 2
APP_DATA=$(xcrun simctl get_app_container booted "$BUNDLE_ID" data) || exit 1
mkdir -p artifacts
cp "$APP_DATA/Documents/qr-sample.png" artifacts/qr-sample.png
file artifacts/qr-sample.png
cp 失败时先查应用是否在当前已启动的模拟器上运行,以及导出逻辑是否真的写到了 Documents。不要从开发机上挑一张同名图片补进去,否则回归检查就绕过了应用。若应用每次输出多个文件,应由测试入口使用稳定文件名,并在每轮运行前清理旧文件,避免误读上一次的结果。
用 Vision 解码并核对载荷
在工作目录保存以下 qr-check.swift。脚本接收 PNG 路径和预期文本;输入、识别或比对失败都会以非零状态退出,便于流水线直接判定。
import Foundation
import Vision
guard CommandLine.arguments.count == 3 else {
fputs("usage: swift qr-check.swift IMAGE EXPECTED\n", stderr)
exit(2)
}
let url = URL(fileURLWithPath: CommandLine.arguments[1])
let expected = CommandLine.arguments[2]
let request = VNDetectBarcodesRequest()
request.symbologies = [.qr]
do {
try VNImageRequestHandler(url: url, options: [:]).perform([request])
let codes = (request.results ?? []).filter { $0.symbology == .qr }
guard codes.count == 1,
codes[0].payloadStringValue == expected else {
fputs("QR count or payload mismatch\n", stderr)
exit(1)
}
print("QR payload verified")
} catch {
fputs("QR image could not be analyzed\n", stderr)
exit(1)
}
运行 swift qr-check.swift artifacts/qr-sample.png case-123。不要只检查识别数量:一个成功识别的码仍可能包含旧订单号、错误前缀或被截断的文本。正式测试数据也不应放入真实凭据;如果业务载荷包含敏感字段,用专门的无效测试值覆盖格式与解析逻辑即可。
分层定位失败
先看导出的文件,再看识别结果,最后才回查二维码生成代码。这样能把“根本没导出”和“导出了但不可读”分开。
| 现象 | 优先检查 |
|---|---|
| 找不到 PNG | 模拟器选择、bundle identifier、沙盒路径及旧文件清理 |
| Vision 找不到码 | 图片是否被裁切、缩成小图或经过有损处理 |
| 识别出多个码 | 导出图片是否混入其他二维码 |
| 载荷不一致 | 编码前的文本、字符编码、缓存及导出时机 |
如果应用使用 Core Image 生成二维码,先从生成器的 outputImage 保留完整边界,再做整数倍放大,最后写出 PNG。检查分享或截图路径是否又进行了一次插值缩小;预览看似平滑,并不意味着方块边缘仍清晰。对于深色主题,还要检查最终文件的颜色:界面背景与导出图片背景未必相同,透明区域在另一种底色上可能失去对比度。
别让检查变成误报
固定一张测试图片、一个明确载荷和一个输出路径,但每次都从本轮应用运行结果取图。若要覆盖不同长度、非 ASCII 字符或带分隔符的载荷,分别建立测试用例并逐个比对,不要把多个预期字符串写成“包含其一即可”。二维码解码成功也不代表业务链接可用;需要跳转验收时,应在另一层测试中检查解析与路由。
把结果接入回归流程
在现有模拟器测试之后执行取图命令与 Swift 脚本,让任一步的非零退出码阻断本轮任务。失败时保留本轮 PNG 和脚本的错误输出,便于判断是导出路径变了,还是图像处理引入了退化;通过后则只需记录验收状态,不必长期保存包含业务数据的图片。
最终检查四件事:图片来自应用的最终输出;没有读到上轮残留;Vision 恰好读出一个码;载荷与固定测试值完全一致。完成这些检查,文件级二维码回归就有了清楚、可重复的边界。涉及屏幕展示或打印交付时,再补真实设备上的扫码验收。
常见问题
Vision 能代替真实设备扫码测试吗?
不能。它适合检查导出的图片是否可解码、载荷是否正确;屏幕亮度、相机对焦和实际打印尺寸仍需在真实使用条件下验证。
为什么要检查应用导出的文件,而不是测试脚本自己生成二维码?
只有检查应用最终写出的 PNG,才能覆盖应用的缩放、颜色、裁切和文件导出路径;脚本自生成自识别只能验证脚本本身。
用独享物理 Mac mini 验证下一步
VMCommit 提供 M4 独享物理 Mac mini,支持按天、周、月或季租用。选择机型与节点后,可在控制台查看实时可用性。