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

Swift中如何对带有依赖的函数进行单元测试?

嘿,我太懂这种光听概念摸不着头脑的感觉了!刚接触mock和stub的时候,我也是对着名词发呆,直到看了实际代码示例才恍然大悟。结合你提到的私有函数测试场景,我给你拆解一下,保证看完就会用!

先搞懂Mock vs Stub:一句话区分
  • Stub(存根):给你的测试提供「固定输入/返回值」的假实现,核心是隔离依赖。比如你要测试的函数依赖数据库返回的数据,Stub就直接返回你预设好的假数据,不用真的连数据库。
  • Mock(模拟):不仅能提供假实现,还会记录和验证交互行为——比如检查某个依赖方法有没有被调用、调用了几次、传的参数对不对。
结合你的场景写示例(Swift为例)

假设你的setOverviewValues是一个ViewModel里的私有函数,依赖CDPRewards(Core Data实体)和displayMode,内部会调用UI更新方法。我们一步步来改造和测试它。

第一步:抽象依赖,方便替换

首先把CDPRewards的核心属性抽象成协议,这样我们就能用Stub来模拟它:

// 定义协议,提取你需要用到的属性
protocol RewardsProtocol {
    var totalPoints: Int { get }
    var weeklyEarnings: [Int] { get }
}

// 让真实的Core Data实体遵守这个协议
extension CDPRewards: RewardsProtocol {}

第二步:写Stub类,模拟CDPRewards

这个Stub类会返回我们预设的值,完全隔离真实的Core Data依赖:

class StubRewards: RewardsProtocol {
    // 自定义属性,方便测试不同场景
    var totalPoints: Int
    var weeklyEarnings: [Int]
    
    // 给默认值,测试时可以按需修改
    init(totalPoints: Int = 100, weeklyEarnings: [Int] = [20,30,50]) {
        self.totalPoints = totalPoints
        self.weeklyEarnings = weeklyEarnings
    }
}

第三步:用Mock验证交互

如果你的setOverviewValues会调用UI更新方法,我们可以写一个Mock类来记录这些调用,确保逻辑正确:

@testable import YourAppName

// Mock ViewModel,用来跟踪UI更新方法的调用情况
class MockOverviewViewModel: OverviewViewModel {
    // 记录是否调用了对应的方法
    var didCallCompactUpdate = false
    var didCallFullUpdate = false
    
    // 记录传入的参数,方便验证
    var compactUpdateParams: (total: String, points: Int)?
    var fullUpdateParams: (earnings: [Int], total: String)?
    
    // 重写UI更新方法,替换成记录逻辑
    override func updateCompactView(total: String, points: Int) {
        didCallCompactUpdate = true
        compactUpdateParams = (total, points)
    }
    
    override func updateFullView(earnings: [Int], total: String) {
        didCallFullUpdate = true
        fullUpdateParams = (earnings, total)
    }
    
    // 暴露私有函数给测试(解决private无法访问的问题)
    func testableSetOverviewValues(weeklyData: RewardsProtocol, formattedTotal: String, displayMode: DisplayMode) {
        #if DEBUG
        self.setOverviewValues(weeklyData: weeklyData as! CDPRewards, formattedTotal: formattedTotal, displayMode: displayMode)
        #endif
    }
}

第四步:写测试用例

现在我们可以用Stub提供假数据,用Mock验证逻辑:

class OverviewViewModelTests: XCTestCase {
    func testCompactMode_UpdatesCorrectView() {
        // 1. 创建Stub的假数据
        let stubRewards = StubRewards(totalPoints: 150, weeklyEarnings: [30,40,80])
        // 2. 创建Mock ViewModel
        let mockVM = MockOverviewViewModel()
        
        // 3. 调用测试用的函数
        mockVM.testableSetOverviewValues(weeklyData: stubRewards, formattedTotal: "$15.00", displayMode: .compact)
        
        // 4. 验证结果:是否正确调用了compact模式的更新方法
        XCTAssertTrue(mockVM.didCallCompactUpdate)
        XCTAssertEqual(mockVM.compactUpdateParams?.total, "$15.00")
        XCTAssertEqual(mockVM.compactUpdateParams?.points, 150)
        
        // 验证没有调用full模式的方法
        XCTAssertFalse(mockVM.didCallFullUpdate)
    }
    
    func testFullMode_UpdatesCorrectView() {
        let stubRewards = StubRewards(weeklyEarnings: [10,20,30,40])
        let mockVM = MockOverviewViewModel()
        
        mockVM.testableSetOverviewValues(weeklyData: stubRewards, formattedTotal: "$10.00", displayMode: .full)
        
        XCTAssertTrue(mockVM.didCallFullUpdate)
        XCTAssertEqual(mockVM.fullUpdateParams?.earnings, [10,20,30,40])
        XCTAssertEqual(mockVM.fullUpdateParams?.total, "$10.00")
        
        XCTAssertFalse(mockVM.didCallCompactUpdate)
    }
}
关于私有函数测试的小技巧

如果你的函数是private的,Swift的@testable import无法直接访问,这里有两个办法:

  1. 改成internal访问级别:默认就是internal,不用加关键字,测试target通过@testable import就能访问。
  2. 用扩展暴露给测试:在主target里加一个只在Debug模式下编译的扩展:
extension OverviewViewModel {
    #if DEBUG
    func exposeSetOverviewValues(weeklyData: CDPRewards, formattedTotal: String, displayMode: DisplayMode) {
        setOverviewValues(weeklyData: weeklyData, formattedTotal: formattedTotal, displayMode: displayMode)
    }
    #endif
}
最后再划个重点
  • 用Stub解决「依赖不好造」的问题:比如数据库、网络请求、第三方SDK,都可以用Stub替换。
  • 用Mock解决「逻辑是否正确触发了依赖」的问题:比如验证按钮点击后是否调用了API,或者某个状态下是否更新了UI。
  • 核心思路是隔离依赖:把测试目标函数和它的依赖分开,只测试函数本身的逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 04:28:09