You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift中非异步函数调用异步函数的单元测试问题

解决方案与设计分析

测试适配方案(不修改协议、无人工等待)

方案1:依赖注入+XCTest期望触发

核心思路是通过替换MyClass依赖的异步方法实现,让测试能精准感知异步任务的完成时机:

  • 先确保MyClass的someAsyncFunc是可注入的(调整初始化逻辑,这不属于修改协议,只是优化实现的可测试性)
  • 在测试用例中创建XCTestExpectation实例
  • 注入自定义的someAsyncFunc实现,在方法完成业务逻辑后调用expectation.fulfill()
  • 调用myClass.doSomething()
  • 调用waitForExpectations(timeout: 1)(timeout仅做兜底,实际会在任务完成后立即触发)
  • 此时再断言didSomethingAsync标志,就能保证时机正确

示例代码:

func testDoSomethingAsyncFlag() {
    let expectation = XCTestExpectation(description: "Async task completes")
    let myClass = MyClass()
    // 替换依赖的异步方法(假设someAsyncFunc是可注入的闭包或协议类型)
    myClass.someAsyncFunc = {
        // 模拟原方法逻辑
        myClass.didSomethingAsync = true
        expectation.fulfill()
    }
    
    myClass.doSomething()
    waitForExpectations(timeout: 1)
    XCTAssertTrue(myClass.didSomethingAsync)
}

方案2:Debug模式下暴露内部Task引用

如果不想调整依赖注入逻辑,可以在MyClass中添加仅Debug模式可见的属性,存储内部启动的Task:

class MyClass: AppStartupService {
    var didSomethingAsync = false
    #if DEBUG
    var internalTask: Task<Void, Error>?
    #endif
    
    func doSomething() {
        #if DEBUG
        internalTask = Task {
            await someAsyncFunc()
        }
        #else
        Task {
            await someAsyncFunc()
        }
        #endif
    }
    
    private func someAsyncFunc() async {
        didSomethingAsync = true
    }
}

测试中直接等待这个Task完成:

func testDoSomethingAsyncFlag() async {
    let myClass = MyClass()
    myClass.doSomething()
    
    // 等待内部Task执行完毕
    if let task = myClass.internalTask {
        await task.value
    }
    
    XCTAssertTrue(myClass.didSomethingAsync)
}

这个方案不会影响Release版本代码,也无需修改协议。

设计层面的潜在问题

你当前的“非异步方法启动异步任务且不等待”属于Fire-and-Forget模式,存在几个隐性问题:

  • 可测试性差:调用者无法感知任务完成时机,也无法捕获异步任务中的错误
  • 错误无处理路径:如果someAsyncFunc执行出错,没有任何地方能捕获并处理这个错误,可能导致隐性故障
  • 生命周期风险:如果MyClass在异步任务完成前被销毁,可能出现内存泄漏或非法访问问题

如果业务场景允许,建议考虑优化方向:

  1. 给AppStartupService协议添加回调方法,在异步任务完成时通知调用者
  2. 使用AsyncStream让调用者能监听任务状态
  3. 若协议可调整,将doSomething()改为异步方法(你当前限制不可修改协议,这条仅做参考)

内容的提问来源于stack exchange,提问作者SimonMoss

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 04:42:35