Skip to content

[iOS][idevice] Unity XCTest 内存图采集方案 #52

Description

@nzcv

[iOS][idevice] Unity 工程 XCTest 内存图采集方案

在 idevice 中为 iOS 真机添加内存图(.memgraph)采集能力。经调研,物理设备无公开 CLI 等价于 Xcode「Debug Memory Graph」;Apple 官方路径为 XCTest + XCTMemoryMetric + xcodebuild -enablePerformanceTestsDiagnostics YES,再从 .xcresult 导出附件。

背景与结论

idevice 现状

  • IOSDevice3 已有 DVT(launch/pkill/proclist), memory / memgraph / footprint API
  • pymobiledevice3 sysmon 可做 physFootprint 快照/流式监控,但不产出 .memgraph
  • leaks --outputGraph 仅适用于本机进程(含 iOS 模拟器),物理 iOS 真机不可用

选定方案

通过 XCTest 性能诊断采集 memgraph:

xcodebuild test \
  -project <path> | -workspace <path> \
  -scheme <scheme> \
  -destination 'platform=iOS,id=<UDID>' \
  -only-testing:<TestTarget>/<testMethod> \
  -enablePerformanceTestsDiagnostics YES \
  -resultBundlePath ./TestResults.xcresult

导出附件:

xcrun xcresulttool export attachments \
  --path ./TestResults.xcresult \
  --output-path ./memgraph_output

每次 measure 迭代通常产生 2 个 memgraphpre_*.memgraph(测量前)和 post_*.memgraph(测量后)。

idevice 计划 API(待实现)

result = device.capture_memory_graph(
    project=Path("Unity-iPhone.xcodeproj"),
    scheme="Unity-iPhone",
    test_filter="UnityMemoryUITests/testGameplayMemory",
)
# result.memgraphs -> pre/post .memgraph 文件列表

前提:调用方工程里已有带 XCTMemoryMetric 的性能测试;idevice 只负责跑测试并导出 memgraph,不生成测试代码。

XCTest 测试示例

func testGameplayMemory() throws {
    let app = XCUIApplication()
    let options = XCTMeasureOptions()
    options.invocationOptions = [.manuallyStart]

    measure(
        metrics: [XCTMemoryMetric(application: app)],
        options: options
    ) {
        app.launch()
        startMeasuring()
        // 在此操作 App
    }
}

要点:

  • 测试内必须使用 XCTMemoryMetric
  • 真机采集 memgraph;模拟器不可靠
  • Scheme Diagnostics 可勾选 Malloc Stack Logging(有助于分配栈)

Unity 导出 Xcode 工程处理

结构

Unity-iPhone.xcodeproj
├── Unity-iPhone      # 主 App(被测进程)
├── UnityFramework
└── ...

步骤

  1. Xcode 中 File → New → Target → UI Testing Bundle(如 UnityMemoryUITests
  2. 写带 XCTMemoryMetricmeasure(...) 测试
  3. Edit Scheme → Test 勾选测试 target
  4. 本地 Product → Test 确认 .xcresult 含 memgraph
  5. 再用 idevice / xcodebuild 自动化

Unity 重导出注意

Unity 重新 Build 可能覆盖手动加的 Test target。建议:PostProcessBuild 自动添加、或独立 workspace、或文档化「导出后需重新配置」。

替代 sleep:HTTP 轮询游戏状态

sleep(10) 可改为 HTTP 轮询,等游戏真正 ready 后再 startMeasuring()

func waitUntilGameReady(timeout: TimeInterval = 60) throws {
    let url = URL(string: "http://127.0.0.1:17890/status")!
    let deadline = Date().addingTimeInterval(timeout)
    while Date() < deadline {
        let (data, response) = try URLSession.shared.syncData(from: url)
        guard let http = response as? HTTPURLResponse, http.statusCode == 200 else {
            Thread.sleep(forTimeInterval: 0.5)
            continue
        }
        let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
        if json?["ready"] as? Bool == true { return }
        Thread.sleep(forTimeInterval: 0.5)
    }
    XCTFail("Game not ready")
}

Unity 侧暴露 GET /status{"ready": true, "scene": "MainMenu"}

方式 优点 缺点
sleep 简单 慢、不稳定
HTTP 轮询 精确表达游戏状态 需游戏内 HTTP 服务;真机 127.0.0.1 需 POC
Accessibility 等待 真机最稳、XCTest 原生 Unity UI 需额外暴露
launchArguments 直达场景 跳过加载 需改启动逻辑

推荐组合:launchArguments 直达场景 + HTTP/Accessibility 确认加载完成 + startMeasuring()

Unity UI 暴露给 XCTest(Accessibility)

XCTest 查询的是 iOS Accessibility 树accessibilityLabel / accessibilityIdentifier),不是 GameObject.name

方案 A:Unity 6 官方 Accessibility 模块(2023.2+)

using UnityEngine.Accessibility;

_hierarchy = new AccessibilityHierarchy();
_node = _hierarchy.AddNode("StartGame");
_node.role = AccessibilityRole.Button;
_node.label = "StartGame";
_node.invoked += () => button.onClick.Invoke();
AssistiveSupport.activeHierarchy = _hierarchy;

文档:Get started with screen reader support

注意:iOS 上可能仅在 VoiceOver 开启时层级才生效,真机 XCTest 需验证。

方案 B:测试专用状态标记(推荐)

不必暴露全部 UI,只暴露少量测试标记:

// Unity:主界面就绪
IOSAccessibility.SetStaticText("test_state", "MainMenuReady");
// XCTest
XCTAssertTrue(app.staticTexts["MainMenuReady"].waitForExistence(timeout: 60))
startMeasuring()

方案 C:原生 UIAccessibility 插件

各 Unity 版本可用,通过 iOS 插件设置 accessibilityIdentifier / accessibilityLabel / accessibilityTraits,坐标需从 RectTransform 转换到屏幕坐标。

XCTest 查询:

app.buttons["btn_battle"].tap()           // identifier
app.staticTexts["MainMenuReady"].exists   // label

推荐落地流程

Unity 导出 Xcode
    ↓
添加 UnityMemoryUITests + XCTMemoryMetric 测试
    ↓
(可选)Unity 暴露 Accessibility 标记 / HTTP 状态接口
    ↓
本地 Product → Test 确认 xcresult 含 memgraph
    ↓
idevice.capture_memory_graph(...) 自动化
    ↓
用 leaks / heap / vmmap 分析导出的 .memgraph

参考

  • Gathering information about memory use
  • WWDC21:Detect and diagnose memory issues(enablePerformanceTestsDiagnostics
  • idevice 计划分支:xctestcapture_memory_graph API 待实现)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions