0Pricing
Swift Academy · 课时

在测试中模拟依赖

用测试替身替换真实实现。

在测试中模拟依赖 是 CoddyKit 上的免费 Swift Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Swift Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Swift Academy 课程共包含 4 节课。

在测试中模拟依赖

mock 是测试中使用的依赖伪实现。由于我们注入的是协议,因此可以提供 mock 来替代真实协作者。

要模拟的协议

从描述该依赖的协议开始。真实类型和 mock 都遵循这个协议。

protocol UserStore {
    func name(for id: Int) -> String
}
struct RealStore: UserStore {
    func name(for id: Int) -> String { "User-\(id)" }
}
print(RealStore().name(for: 5))

编写 mock

mock 遵循该协议,但会返回预设值,从而避免访问真实网络或磁盘。

protocol UserStore { func name(for id: Int) -> String }
struct MockStore: UserStore {
    func name(for id: Int) -> String { "Mock" }
}
print(MockStore().name(for: 99))

注入 mock

通过被测系统的初始化器将 mock 传入其中。被测代码无法分辨两者的差异。

protocol UserStore { func name(for id: Int) -> String }
struct MockStore: UserStore { func name(for id: Int) -> String { "Mock" } }
struct Profile {
    let store: UserStore
    func title(_ id: Int) -> String { "Hello, " + store.name(for: id) }
}
print(Profile(store: MockStore()).title(1))

记录调用

mock 可以记录自身的使用方式,这样测试就能断言依赖是否被正确调用。

protocol Analytics { func track(_ e: String) }
class SpyAnalytics: Analytics {
    var events: [String] = []
    func track(_ e: String) { events.append(e) }
}
let spy = SpyAnalytics()
spy.track("open")
spy.track("buy")
print(spy.events)

断言行为

运行代码后,检查记录的数据。在这里,我们要验证正确的事件是否已触发。

protocol Analytics { func track(_ e: String) }
class Spy: Analytics { var last = ""; func track(_ e: String) { last = e } }
struct Checkout { let analytics: Analytics; func pay() { analytics.track("paid") } }
let spy = Spy()
Checkout(analytics: spy).pay()
print(spy.last == "paid")

设置桩返回值

桩实现会返回预设值,因此您可以测试代码如何响应特定输入。

protocol Clock { func hour() -> Int }
struct StubClock: Clock { let value: Int; func hour() -> Int { value } }
struct Greeter { let clock: Clock; func greet() -> String { clock.hour() < 12 ? "AM" : "PM" } }
print(Greeter(clock: StubClock(value: 9)).greet())
print(Greeter(clock: StubClock(value: 18)).greet())

模拟失败

mock 可以模拟使用真实依赖时难以触发的错误情况,从而提高测试覆盖率。

protocol Loader { func load() -> Int? }
struct FailingLoader: Loader { func load() -> Int? { nil } }
struct VM { let loader: Loader; func status() -> String { loader.load() == nil ? "error" : "ok" } }
print(VM(loader: FailingLoader()).status())

确定性测试

由于 mock 返回固定值,测试既快速又具有确定性,不会受到真实服务不稳定性的影响。

protocol Random { func next() -> Int }
struct FixedRandom: Random { func next() -> Int { 42 } }
struct Game { let rng: Random; func roll() -> Int { rng.next() } }
print(Game(rng: FixedRandom()).roll())

此处不使用测试框架

在真实项目中,这些检查会使用 XCTest 断言完成。注入模式完全相同,只有断言语法不同。

protocol Calc { func add(_ a: Int, _ b: Int) -> Int }
struct RealCalc: Calc { func add(_ a: Int, _ b: Int) -> Int { a + b } }
struct MockCalc: Calc { func add(_ a: Int, _ b: Int) -> Int { 0 } }
print(RealCalc().add(2, 2))
print(MockCalc().add(2, 2))

完整流程

定义协议,编写真实现和 mock,让 mock 注入系统,运行代码,然后进行断言。DI 使每一步都成为可能。

protocol Repo { func count() -> Int }
struct MockRepo: Repo { func count() -> Int { 3 } }
struct Stats { let repo: Repo; func total() -> Int { repo.count() * 2 } }
print(Stats(repo: MockRepo()).total())

快速检查

在这种方法中,是什么让模拟依赖成为可能?

回顾

mock 遵循被注入的协议,并返回预设值、记录调用或模拟失败。注入它可以生成快速且具有确定性的测试。完整流程是:定义协议,编写真实现和 mock,进行注入,运行代码,然后断言。DI 课程到此结束。

常见问题解答

「在测试中模拟依赖」课时是免费的吗?

是的 — 「在测试中模拟依赖」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Swift Academy 课程的其余内容,请升级到 CoddyKit PRO。 Swift Academy 课程共包含 4 节课。

「在测试中模拟依赖」这节课中我会学到什么?

用测试替身替换真实实现。 你通过在浏览器中直接运行的动手代码来练习 Swift Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Swift Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Swift Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「在测试中模拟依赖」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Swift Academy 课中编写并运行代码吗?

能。每节 Swift Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 为什么需要依赖注入
  2. 构造器注入
  3. 基于协议的抽象
  4. 在测试中模拟依赖
← 返回 Swift Academy