0Pricing
Swift Academy · Lekcja

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 = .off

Odbieranie 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:

  • Scope uruchamia reducer funkcji podrzędnej dla fragmentu stanu i akcji funkcji nadrzędnej, a w jednym body można połączyć kilka reducerów.
  • store.scope przekazuje widokowi podrzędnemu jego własny, wyspecjalizowany store.
  • TestStore sprawdza dokładne zmiany stanu podczas wysyłania akcji za pomocą send.
  • Domyślnie działa wyczerpująco i używa receive do 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

  1. Stan, akcja i reducer
  2. Store i integracja z SwiftUI
  3. Efekty i zależności
  4. Komponowanie i testowanie funkcji
← Powrót do Swift Academy