【免费下载链接】App-Store-Connect-CLI
Fast, scriptable CLI for the App Store Connect API. Automate TestFlight, builds, submissions, signing, analytics, screenshots, subscriptions, and more
asc xcode test是 App-Store-Connect-CLI 在asc xcode本地命令组下提供的一等公民测试子命令:它显式运行本机xcodebuild的测试动作,通过 Xcode 命令行工具读取生成的.xcresult结果包,并输出稳定的结构化 JSON、表格、Markdown 或 JUnit 报告。本文以 xcode-test-command.md 设计文档为主体骨架,结合 internal/cli/xcode/test.go、internal/xcode/test.go 等源码实现与测试用例,完整梳理命令的定位边界、全部 Typed 参数、结果解析原理、输出契约、失败行为与质量保障方案,让你能直接把该命令接入本地 CI 与发布流水线。
命令定位与职责边界
asc xcode test位于本地asc xcode命令组的叶子位置(见 internal/cli/xcode/xcode.go 的子命令注册表),它与组内的build、archive、export、install等命令共享同一套本地进程与渲染基础设施,但职责边界被严格限定:
- 只运行本地
xcodebuild测试动作,并通过当前激活的 Xcode 命令行工具读取.xcresult结果包; - 绝不调用 App Store Connect,不上传任何产物,也不修改 Xcode 工程文件;
- Xcode 可能会根据
--destination启动或拉起所选模拟器/真机,但命令本身不做任何宿主相关的隐式选择; - 不需要任何 App Store Connect 凭证,也不需要网络访问。
也就是说,它是纯本地的"编译 + 测试 + 结构化汇总"工具,输出可以直接喂给后续的asc upload、asc publish等发布命令,而测试本身不产生任何远程副作用。
三种测试动作与选择器约束
命令通过一个类型化选项--action支持 Xcode 的三种测试动作:
--action取值 | 对应 xcodebuild 动作 | 选择器要求 |
|---|---|---|
test(默认) | xcodebuild ... test | 必须提供--project或--workspace之一,以及--scheme |
build-for-testing | xcodebuild ... build-for-testing | 同上,仅构建测试产物,不执行测试 |
test-without-building | xcodebuild ... test-without-building | 必须提供已存在的--xctestrun文件,拒绝工程类选择器 |
选择器约束并非只在文档层面承诺,ValidateTestOptions(internal/xcode/test.go)在启动任何子进程前就完成了确定性的校验:test与build-for-testing要求工程/工作区二选一(--project必须后缀.xcodeproj、--workspace必须后缀.xcworkspace)且--scheme必填;test-without-building则禁止--project、--workspace、--scheme、--configuration、--derived-data-path,并要求--xctestrun后缀为.xctestrun且文件真实存在(validateTestInputPaths)。
此外,每种动作都强制要求一个或多个显式--destination,这样一次运行不会依赖 Xcode 基于宿主机的默认目的地选择,保证在 CI 上与在本地行为一致。源码中if len(opts.Destinations) == 0 { return fmt.Errorf("--destination is required") }直接体现了这条硬性约束。
调用方式与全部 Typed 参数
设计文档给出了一条完整的基线命令:
asc xcode test \ --project App.xcodeproj \ --scheme App \ --configuration Debug \ --destination 'platform=iOS Simulator,name=iPhone 17 Pro' \ --result-bundle-path .asc/artifacts/App-tests.xcresult \ --output json在 internal/cli/xcode/test.go 中,这些参数被逐一注册为 Typed flag,完整清单如下:
| 参数 | 类型 | 说明与约束 |
|---|---|---|
--project | 单值 | .xcodeproj路径;与--workspace互斥且必须二选一 |
--workspace | 单值 | .xcworkspace路径;与--project互斥且必须二选一 |
--scheme | 单值 | Xcode scheme 名,除test-without-building外必填 |
--action | 单值 | test(默认)/build-for-testing/test-without-building |
--configuration | 单值 | 构建配置,如Debug、Release;仅对工程类动作有效 |
--destination | 可重复 | Xcode 目的地描述符,必填,可传多次 |
--test-plan | 单值 | Xcode 测试计划名;与--xctestrun互斥 |
--xctestrun | 单值 | 已存在的.xctestrun文件,仅test-without-building使用 |
--only-testing | 可重复 | 只运行指定的测试目标或标识符 |
--skip-testing | 可重复 | 跳过指定的测试目标或标识符 |
--derived-data-path | 单值 | DerivedData 目录;缺省分配在稳定的 asc 缓存路径下 |
--result-bundle-path | 单值 | 新结果包.xcresult的落点;缺省自动分配 |
--clean | 布尔 | 在所选动作前先执行clean |
--no-code-signing | 布尔 | 显式设置CODE_SIGNING_ALLOWED=NO |
--xcodebuild-flag | 可重复 | 原样透传给xcodebuild的原始参数 |
| 标准输出 flag | — | --output、--pretty等通用输出控制 |
命令对参数形状有严格约定:空值一律非法(--%s must not be empty),位置参数一律拒绝(xcode test does not accept positional arguments)。同时有几个重要的互斥/作用域规则:
--test-plan与--xctestrun互斥;--clean、--no-code-signing、--configuration、--derived-data-path只对选择工程/工作区的动作有效,test-without-building全部拒绝;- 可重复的目的地与测试过滤值拒绝空串和控制字符输入,但保留字面空白——这正是
exactTestStringFlag(internal/cli/xcode/test.go)存在的意义:destination 与测试标识符中可能含空格,必须原样到达xcodebuild,不能被通用 flag 解析吞掉。
透传参数的安全约束
--xcodebuild-flag提供了灵活性,但被设计为"可控的逃生舱"。源码中的reservedTestPassthroughArgument与validateTestPassthroughArguments(internal/xcode/test.go)明确禁止透传参数覆盖以下 asc 托管项:
- 选择器类:
-workspace、-project、-scheme、-configuration、-destination、-testPlan、-xctestrun、-only-testing、-skip-testing、-derivedDataPath、-resultBundlePath以及 ASC 托管的签名设置(如CODE_SIGNING_ALLOWED=NO); - 认证类参数值不能为空;带值参数必须跟随一个值,且该值不能是另一个被识别的 xcodebuild 选项(防止 xcodebuild 静默吞掉下一个选项当凭据)。
所有透传值以独立 argv 条目传递,保留用户给定的顺序与空白。这也意味着:命令本身的"动作"永远由--action决定,argv 末尾追加的test/build-for-testing/test-without-building由buildTestCommand统一构造(internal/xcode/test.go),透传层无法篡改动作语义。
结果包分配与产物保护
对于执行测试的动作,asc总是为运行供应一个新的结果包路径:
- 显式提供
--result-bundle-path时,先解析为绝对路径再使用;扩展名必须为.xcresult; - 缺省时在用户缓存目录下分配:
<cache>/asc/xcode-test/<scheme>-<时间戳>-<哈希>.xcresult,其中哈希是对工程/工作区路径、scheme、action、configuration、test-plan 与全部 destinations 的 SHA-256 摘要前 12 位(见resolveTestResultBundlePath,internal/xcode/test.go),保证不同选择器组合不会互相覆盖; - 已存在的路径、目录、符号链接目的地一律拒绝:
validateTestResultBundleDestination在启动子进程前用Lstat检查,validateTestResultBundlePathComponents则逐个组件检查符号链接(仅放行 macOS 系统级/etc、/tmp、/var别名),且该检查在 xcodebuild 前后各执行一次,因为最终路径是由子进程在包外创建的。
DerivedData 路径同理:显式提供时转为绝对路径;缺省时分配在缓存下的稳定路径(按选择器摘要哈希,resolveTestDerivedDataPath),便于build-for-testing之后衔接test-without-building。
build-for-testing不需要结果包,但在恰好能从其 DerivedData 目录下识别出唯一安全的.xctestrun候选文件时会报告该路径(findXctestrunPath,internal/xcode/test.go);候选多于一个时不猜测、不报告。
结构化结果解析:xcresulttool 双读
设计文档明确指出:结果包通过 Xcode 官方工具链汇总,而不是解析人读的xcodebuild日志。当前实现使用两次结构化读取(internal/xcode/test.go):
xcresulttool get test-results summary --path PATH --compact:提供totalTestCount、passedTests、failedTests、skippedTests、testFailures等聚合字段;xcresulttool get test-results tests --path PATH --compact:提供递归的testNodes树。
解析器只把Test Case节点展平(appendTestCases按nodeType过滤),失败文本只从结构化的 failure-message 子节点取有界内容。状态归一化(normalizeTestStatus)只接受passed、failed、skipped与expected-failure四类:期望失败(expected failure)不算失败,但计数会被单独报告。
聚合一致性校验是一道严格的防线(validateTestSummary/ParseTestResultSummary):
- 聚合计数必须非负,且
passed + failed + skipped + expectedFailures必须等于totalTestCount,否则报inconsistent test counts; expectedFailures在 Xcode 提供该字段时做对账,缺失时由剩余计数推导;- 当展平后的用例与聚合描述同一统计单元(用例数等于 total)时,逐用例状态计数会与聚合交叉核对;多目的地或重复运行产生的树即便叶子数与聚合不同也保留原样,不强行篡改;
- 未知字段一律忽略。
另外,结构化输出在解析前就被封顶:maxXcresulttoolOutputBytes为 16 MiB、诊断输出封顶 8 KiB、用例数上限 10000、失败详情上限 100 条、单条失败消息上限 4096 字符(常量定义见 internal/xcode/test.go)。缺必需聚合字段、JSON 畸形、结果包缺失或工具不可用,都是显式的后处理错误——asc 绝不凭空捏造成功计数,只要有任何测试失败就绝不报告成功。
输出契约:JSON 收据、表格/Markdown 与 JUnit
JSON:稳定的 camelCase 收据
JSON 走的是注册的导出输出收据类型(internal/asc/output_xcode.go),字段名稳定为 camelCase。设计文档给出了完整示例:
{ "action": "test", "project": "App.xcodeproj", "scheme": "App", "configuration": "Debug", "destinations": ["platform=iOS Simulator,name=iPhone 17 Pro"], "derivedDataPath": "/path/to/DerivedData", "resultBundlePath": "/path/to/App-tests.xcresult", "tests": { "total": 12, "passed": 10, "failed": 1, "skipped": 1, "expectedFailures": 0, "durationMs": 4812, "failures": [ {"identifier": "AppTests/LoginTests/testInvalidPassword", "message": "assertion failed"} ] }, "success": false, "durationMs": 5120, "exitStatus": 65 }要点:
build-for-testing会省略tests字段,并在安全发现时报告.xctestrun路径;exitStatus保留 xcodebuild 进程的原始退出码(示例中的 65 正是 Xcode 测试失败常见的退出码);取消、预检与结果后处理错误不会冒充子进程退出状态;- 表格(table)与 Markdown 渲染同样的稳定汇总字段,测试失败明细保持有界(
formatXcodeTestFailure截断到 4096 字节); - Xcode 的实时诊断输出留在 stderr;结构化 stdout 永远不会包含完整的原始日志或环境信息。
JUnit:全局--report junit的聚合对账
当提供全局--report junit --report-file PATH且存在结构化测试结果时,命令为每个解析出的测试生成一个 JUnit testcase,并在展平树未能完整代表聚合计数时合成有界的聚合用例(testResultJUnitReport,internal/cli/xcode/test.go):
- 合成顺序刻意优先
aggregate-failed-*,再aggregate-skipped-*、aggregate-passed-*——这样在聚合用例封顶(maxJUnitAggregateCases= 10000)时,失败的签名也不会被吞掉; - 零测试的汇总不产生任何"通过"占位;
- 套件级 duration 直接取汇总的
summary.DurationMS,避免按用例求和时丢失 setup/teardown 与多目的地重复执行的时间; - 当 xcodebuild 异常退出或后处理失败且报告中尚无失败行时,追加一条
xcode test did not complete successfully的基础设施失败行,但已由失败用例代表的普通非零退出不会被重复计数(shouldAddJUnitInfrastructureFailure)。
报告文件沿用仓库既有的 no-overwrite(不覆盖已存在文件)与受限权限写入器行为。使用/预检失败则保留原有的通用命令级报告。
关联子命令:asc xcode test junit与asc xcode test-destinations
在 internal/cli/xcode/test.go 中,test命令还挂载了子命令asc xcode test junit(internal/cli/xcode/test_junit.go):它不运行测试,直接把已存在的.xcresult包转成与asc xcode test --report junit相同形态的 JUnit XML,命令格式为:
asc xcode test junit --xcresult ./Test.xcresult --report-file ./junit.xml --output json若聚合汇总读取成功但逐用例补全失败,命令 fail-closed,不写部分报告。配套的asc xcode test-destinations(internal/cli/xcode/test_destinations.go)则是一个只读的simctl list,输出可直接粘贴进--destination的DestinationString(如platform=iOS Simulator,id=<UDID>),支持--platform iOS|watchOS|tvOS|visionOS|macOS与--available-only过滤,且绝不创建、启动或删除模拟器。
验证与失败行为
命令的确定性校验(选项形状、路径合法性)全部发生在创建目录或启动子进程之前,保证"坏的调用不产生副作用"。本地辅助逻辑复用了仓库既有的 macOS-only Xcode 可用性检查与进程组取消行为(ensureXcodeAvailable、进程组信号)。
失败语义有三个关键分支(对应 internal/xcode/test.go 的Test主流程):
- 动作失败但结果包可读:仍会尽力解析出部分汇总(
readPartialTestResultSummary使用独立的 30 秒后处理超时,并主动丢弃调用方的取消/截止以便抢救可读数据),但命令最终返回原始的 xcodebuild 非零错误; - 动作成功但结果包无法汇总:返回后处理错误,并保留产物供诊断,绝不静默通过;
- 测试失败(
summary.Failed > 0或存在 failed 用例):返回类型化错误ReportedTestFailuresError(internal/xcode/test.go),区分"测试真的失败了"与"基础设施错误",避免 JUnit 层双重计数。
命令在所有平台上都出现在帮助与生成的文档中(跨平台可见),但只在装有可用 Xcode 的 macOS 上真正执行。它不需要 App Store Connect 凭证或网络,因此非常适合作为本地测试闸门。
测试与质量保障计划
设计文档的验证计划在仓库中落地为三层:
- RED 覆盖(CLI 测试):internal/cli/xcode/test_test.go 覆盖命令发现、动作专属 flag 校验、可重复 destination 与过滤器、输出格式、错误流与 usage 退出码;测试名如
TestXcodeTestPassesTypedOptionsAndPrintsJSON、TestXcodeTestValidationErrorsAreUsageErrors、TestXcodeTestRejectsAuthenticationPassthroughUsageErrorsBeforeChild直接对应上述契约; - 核心单测:通过注入的进程与解析器接缝(
runXcodeTestCommand、readTestResultSummaryFn、SetSimulatorListLoaderForTesting、SetXCResultSummaryLoaderForTesting等)覆盖精确 argv 生成、结果包路径分配与碰撞保护、动作失败、取消、汇总解析、有界失败详情与 JUnit 转换——包括TestXcodeTestJUnitSynthesizesAggregateFailuresBeforeFillingCap、TestXcodeTestJUnitKeepsInfrastructureRowWhenReportHasNoFailure这类边界场景; - 完整门禁与实机校验:全量门禁为
make build、make format、make check-docs、make lint、ASC_BYPASS_KEYCHAIN=1 make test;在装有完整 Xcode 的主机上,实机检查覆盖一次通过运行、一次失败运行、build-for-testing 后接 test-without-building、多目的地、测试过滤、JUnit 输出,并确认源码文件保持不变(不修改工程)。
设计取舍与备选方案
设计文档最后给出了被否决的替代路线,解释了为何需要一个专门的类型化命令:
- 只加原始
--xcodebuild-flag逃生舱:虽然能触达测试动作,但无法提供选择器/路径校验、结构化输出、产物保护(结果包路径防覆盖、防符号链接)与稳定的退出契约; - 扩展
asc xcode build去推断测试动作:会让既有 build 结果失真,破坏其"动作不变量"(一次构建只对应一个明确动作); - 专门的类型化命令:让编译与测试语义各自显式,同时复用已有的本地进程管理与渲染基础设施——这正是当前
asc xcode test的定位。
一句话总结:asc xcode test把"本地测试"做成了与远端发布同级的、可脚本化、可机器消费的一等公民能力。想深入理解或二次开发,建议从 xcode-test-command.md 设计文档出发,对照 internal/cli/xcode/test.go 的参数注册与输出渲染、internal/xcode/test.go 的校验与解析内核、internal/cli/xcode/test_test.go 的契约测试逐步阅读。
【免费下载链接】App-Store-Connect-CLI
Fast, scriptable CLI for the App Store Connect API. Automate TestFlight, builds, submissions, signing, analytics, screenshots, subscriptions, and more
相关推荐
App-Store-Connect-CLI 的 Xcode PKG 导出支持:`asc xcode export --pkg-path` 设计解析
App Store Connect CLI 的 Xcode PKG 导出支持: asc xcode export pkg path 设计解析 导读 macOS
App-Store-Connect-CLI 的 `asc xcode install`:向已连接 iOS 真机安装本地 IPA 的完整设计解析
App Store Connect CLI 的 asc xcode install :向已连接 iOS 真机安装本地 IPA 的完整设计解析 asc xcode
App Store Connect CLI 驱动 Xcode:本地 build、archive、export 与 xcode test 结构化结果完整指南
App Store Connect CLI 驱动 Xcode:本地 build、archive、export 与 xcode test 结构化结果完整指南 Ap
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考