组合和测试功能
组合归约器并进行全面测试。
组合和测试功能 是 CoddyKit 上的免费 Swift Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Swift Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Swift Academy 课程共包含 4 节课。
组合功能
实际应用由许多小型功能构成。TCA 允许父功能嵌入子功能,使每个功能都能专注于自身职责,然后再将它们组合成更大的整体。实现这一点的工具是 Scope,以及嵌入子状态和子操作。
正是组合让 The Composable Architecture 得名。
import ComposableArchitecture
// A parent feature embeds child State and Action,
// then uses Scope to run the child's reducer.嵌入子状态
父级的 State 将子级状态作为一个属性保存。父级的 Action 枚举包含一个包装子级操作的枚举分支。这种嵌套方式反映了用户界面的嵌套结构。
这里的父级持有一个计数器子级。
import ComposableArchitecture
@Reducer
struct AppFeature {
@ObservableState
struct State: Equatable {
var counter = CounterFeature.State()
}
enum Action {
case counter(CounterFeature.Action)
}
var body: some ReducerOf<Self> { EmptyReducer() }
}限定到子级
Scope 在属于子级的父级状态和操作切片上运行子级归约器。您需要向它传入一个状态键路径、一个操作枚举分支路径,然后传入子级归约器。
这样就将子级接入了父级的更新循环。
import ComposableArchitecture
var body: some ReducerOf<Self> {
Scope(state: \.counter, action: \.counter) {
CounterFeature()
}
Reduce { state, action in
// parent-specific logic here
return .none
}
}组合多个归约器
功能的 body 可以列出多个归约器;TCA 会针对每个操作按顺序运行它们。这样,您就可以将一个或多个 Scope 与父级自己的 Reduce 放在一起。
这里有两个子级,它们与父级逻辑共存。
import ComposableArchitecture
var body: some ReducerOf<Self> {
Scope(state: \.profile, action: \.profile) { ProfileFeature() }
Scope(state: \.settings, action: \.settings) { SettingsFeature() }
Reduce { state, action in
return .none
}
}为子级视图限定存储
在视图一侧,子级视图需要一个专门用于子级的存储。父级通过 store.scope 将其映射到子级的状态和操作,从而派生出这个存储。
这里的父级视图将一个限定后的存储传递给计数器视图。
import ComposableArchitecture
import SwiftUI
struct AppView: View {
let store: StoreOf<AppFeature>
var body: some View {
CounterView(
store: store.scope(state: \.counter, action: \.counter)
)
}
}介绍 TestStore
TestStore 允许您准确断言某个功能的行为。您发送操作并描述状态必须如何变化;如果实际结果不同,测试就会失败。这种全面检查可以捕获细微的回归问题。
您可以像创建普通存储一样,使用初始状态和归约器创建它。
import ComposableArchitecture
import XCTest
func testIncrement() async {
let store = await TestStore(initialState: CounterFeature.State()) {
CounterFeature()
}
}断言状态变化
向 TestStore 发送操作时,您需要传入一个描述预期变更的闭包。只有当状态完全按照该方式变化时,测试才会通过。
这里,递增操作必须将计数从 0 增加到 1。
import ComposableArchitecture
import XCTest
func testIncrement() async {
let store = await TestStore(initialState: CounterFeature.State()) {
CounterFeature()
}
await store.send(.incrementTapped) {
$0.count = 1
}
}穷尽性
默认情况下,TestStore 具有穷尽性:您必须处理每个状态变化,以及副作用传回的每个操作。未处理的变化会导致测试失败,从而确保您的断言真实可靠。
对于专注于特定部分的测试,您可以使用 store.exhaustivity = .off 放宽这一要求。
import ComposableArchitecture
// Exhaustive (default): every change must be asserted.
// Relax when you only care about part of the behavior:
store.exhaustivity = .off接收副作用操作
当副作用传回一个操作时,测试必须使用 receive 接收它,并断言由此产生的状态。这可以证明异步流程端到端地正常工作。
这里,重新加载会触发一个响应,测试会等待并检查该响应。
import ComposableArchitecture
import XCTest
func testReload() async {
let store = await TestStore(initialState: NumberFeature.State()) {
NumberFeature()
}
await store.send(.reload)
await store.receive(\.response) {
$0.value = 42
}
}在测试中覆盖依赖
为了让副作用具有确定性,请在构造 TestStore 时覆盖依赖。您可以提供一个虚假客户端或测试时钟,使结果变得可预测。
这里,数字客户端被替换成了一个始终返回 42 的客户端。
import ComposableArchitecture
import XCTest
func testReload() async {
let store = await TestStore(initialState: NumberFeature.State()) {
NumberFeature()
} withDependencies: {
$0.numberClient.fetch = { 42 }
}
await store.send(.reload)
await store.receive(\.response) { $0.value = 42 }
}测试组合功能
由于组合会嵌套状态和操作,测试父级时,您需要发送包装在父级分支中的子级操作。同样的穷尽性断言也适用,从而验证整个树结构。
这里,父级通过其限定范围接收了一个计数器递增操作。
import ComposableArchitecture
import XCTest
func testChildInParent() async {
let store = await TestStore(initialState: AppFeature.State()) {
AppFeature()
}
await store.send(.counter(.incrementTapped)) {
$0.counter.count = 1
}
}快速检查:TestStore 的行为
回顾一下,默认的 TestStore 要求您的断言处理哪些内容。
回顾:组合与测试
您已经完成了 TCA 工具集的学习:
Scope在父级状态和操作的一个切片上运行子级归约器,而且可以在一个body中组合多个归约器。store.scope为子级视图提供专门用于该子级的存储。TestStore会在您使用send发送操作时,断言状态是否发生了准确的变化。- 它默认具有穷尽性,并使用
receive处理由副作用驱动的操作。 - 使用
withDependencies覆盖依赖,以便进行确定性测试。
现在,您可以使用 The Composable Architecture 构建、组合并严格测试功能。
import ComposableArchitecture
// Recap: Scope to compose, TestStore to verify.
// Exhaustive assertions + injected fakes = confidence.常见问题解答
「组合和测试功能」课时是免费的吗?
是的 — 「组合和测试功能」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 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 反馈 — 无需本地设置。