Komponowanie i testowanie funkcji
Łączenie reducerów i wyczerpujące testowanie.
Komponowanie i testowanie funkcji to bezpłatna lekcja Swift Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Swift Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Swift Academy zawiera 4 lekcji w sumie.
Komponowanie funkcji
Prawdziwe aplikacje składają się z wielu małych funkcji. TCA pozwala funkcji nadrzędnej zawierać funkcje podrzędne, dzięki czemu każda z nich może zachować wyraźnie określony zakres odpowiedzialności, a następnie połączyć je w większą całość. Służą do tego Scope oraz zagnieżdżanie stanu i akcji funkcji podrzędnej.
To właśnie kompozycja jest źródłem nazwy The Composable Architecture.
import ComposableArchitecture
// A parent feature embeds child State and Action,
// then uses Scope to run the child's reducer.Zagnieżdżanie stanu funkcji podrzędnej
State funkcji nadrzędnej przechowuje stan funkcji podrzędnej jako właściwość. Wyliczenie Action funkcji nadrzędnej zawiera przypadek opakowujący akcje funkcji podrzędnej. To zagnieżdżenie odzwierciedla sposób zagnieżdżenia interfejsu użytkownika.
W tym przykładzie funkcja nadrzędna zawiera podrzędny licznik.
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() }
}Ograniczanie zakresu do funkcji podrzędnej
Scope uruchamia reducer funkcji podrzędnej dla fragmentu stanu i akcji funkcji nadrzędnej, które jej dotyczą. Należy przekazać mu ścieżkę kluczową stanu, ścieżkę przypadku akcji, a następnie reducer funkcji podrzędnej.
W ten sposób funkcja podrzędna zostaje podłączona do pętli aktualizacji funkcji nadrzędnej.
import ComposableArchitecture
var body: some ReducerOf<Self> {
Scope(state: \.counter, action: \.counter) {
CounterFeature()
}
Reduce { state, action in
// parent-specific logic here
return .none
}
}Łączenie wielu reducerów
body funkcji może zawierać kilka reducerów; TCA uruchamia je po kolei dla każdej akcji. Pozwala to umieścić jeden lub więcej elementów Scope obok własnego Reduce funkcji nadrzędnej.
W tym przykładzie współistnieją dwie funkcje podrzędne i logika funkcji nadrzędnej.
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
}
}Ograniczanie zakresu store'a dla widoków podrzędnych
Po stronie widoku widok podrzędny potrzebuje store'a wyspecjalizowanego dla funkcji podrzędnej. Widok nadrzędny tworzy go za pomocą store.scope, mapując go na stan i akcję funkcji podrzędnej.
W tym przykładzie widok nadrzędny przekazuje widokowi licznika store o ograniczonym zakresie.
import ComposableArchitecture
import SwiftUI
struct AppView: View {
let store: StoreOf<AppFeature>
var body: some View {
CounterView(
store: store.scope(state: \.counter, action: \.counter)
)
}
}Wprowadzenie do TestStore
TestStore umożliwia dokładne sprawdzanie zachowania funkcji. Wysyła się akcje i opisuje, jak powinien zmienić się stan; jeśli rzeczywiste zachowanie jest inne, test kończy się niepowodzeniem. Takie wyczerpujące sprawdzanie pozwala wykrywać subtelne regresje.
Tworzy się go tak jak zwykły store, podając stan początkowy i reducer.
import ComposableArchitecture
import XCTest
func testIncrement() async {
let store = await TestStore(initialState: CounterFeature.State()) {
CounterFeature()
}
}Sprawdzanie zmian stanu
Podczas wysyłania akcji do TestStore przekazuje się zamknięcie opisujące oczekiwaną zmianę. Test przechodzi tylko wtedy, gdy stan zmieni się dokładnie w określony sposób.
W tym przykładzie zwiększenie musi podnieść count z 0 do 1.
import ComposableArchitecture
import XCTest
func testIncrement() async {
let store = await TestStore(initialState: CounterFeature.State()) {
CounterFeature()
}
await store.send(.incrementTapped) {
$0.count = 1
}
}Wyczerpujące sprawdzanie
Domyślnie TestStore działa wyczerpująco: należy uwzględnić każdą zmianę stanu i każdą akcję przekazaną z powrotem przez efekt. Nieobsłużone zmiany powodują niepowodzenie testu, dzięki czemu asercje pozostają zgodne z rzeczywistym zachowaniem.
W testach ukierunkowanych można wyłączyć ten tryb za pomocą store.exhaustivity = .off.
import ComposableArchitecture
// Exhaustive (default): every change must be asserted.
// Relax when you only care about part of the behavior:
store.exhaustivity = .offOdbieranie akcji efektów
Gdy efekt wysyła akcję z powrotem, test musi ją odebrać za pomocą receive i sprawdzić wynikowy stan. Dowodzi to, że przepływ asynchroniczny działa od początku do końca.
W tym przykładzie przeładowanie wywołuje odpowiedź, na którą test czeka i którą sprawdza.
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
}
}Nadpisywanie zależności w testach
Aby zapewnić deterministyczność efektów, należy nadpisać zależności podczas tworzenia TestStore. Można przekazać atrapę klienta lub zegar testowy, aby wyniki były przewidywalne.
W tym przykładzie klient do obsługi liczb zostaje zastąpiony klientem, który zawsze zwraca wartość 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 }
}Testowanie złożonych funkcji
Ponieważ kompozycja zagnieżdża stan i akcje, funkcję nadrzędną testuje się, wysyłając akcje funkcji podrzędnej opakowane w przypadek funkcji nadrzędnej. Obowiązują te same wyczerpujące asercje, które weryfikują całe drzewo.
W tym przykładzie funkcja nadrzędna otrzymuje akcję zwiększenia licznika za pośrednictwem swojego zakresu.
import ComposableArchitecture
import XCTest
func testChildInParent() async {
let store = await TestStore(initialState: AppFeature.State()) {
AppFeature()
}
await store.send(.counter(.incrementTapped)) {
$0.counter.count = 1
}
}Szybkie sprawdzenie: zachowanie TestStore
Przypomnij sobie, czego domyślnie wymagają asercje w TestStore.
Podsumowanie: komponowanie i testowanie
Ukończono poznawanie narzędzi TCA:
Scopeuruchamia reducer funkcji podrzędnej dla fragmentu stanu i akcji funkcji nadrzędnej, a w jednymbodymożna połączyć kilka reducerów.store.scopeprzekazuje widokowi podrzędnemu jego własny, wyspecjalizowany store.TestStoresprawdza dokładne zmiany stanu podczas wysyłania akcji za pomocąsend.- Domyślnie działa wyczerpująco i używa
receivedo obsługi akcji wywoływanych przez efekty. - Zależności można nadpisywać za pomocą
withDependencies, aby uzyskać deterministyczne testy.
Można już tworzyć, komponować i rygorystycznie testować funkcje za pomocą The Composable Architecture.
import ComposableArchitecture
// Recap: Scope to compose, TestStore to verify.
// Exhaustive assertions + injected fakes = confidence.Często zadawane pytania
Czy lekcja „Komponowanie i testowanie funkcji” jest bezpłatna?
Tak — pełny tekst „Komponowanie i testowanie funkcji” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Swift Academy, przejdź na CoddyKit PRO. Kurs Swift Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Komponowanie i testowanie funkcji”?
Łączenie reducerów i wyczerpujące testowanie. Ćwiczysz Swift Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Swift Academy?
Nie wymagamy żadnego doświadczenia. Swift Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.
Ile czasu zajmuje lekcja „Komponowanie i testowanie funkcji”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Swift Academy?
Tak. Każda lekcja Swift Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Stan, akcja i reducer
- Store i integracja z SwiftUI
- Efekty i zależności
- Komponowanie i testowanie funkcji