0Pricing
Swift Academy · Lezione

Composizione e test delle funzionalità

Combini i reducer e testi ogni caso in modo esaustivo.

Composizione e test delle funzionalità è una lezione Swift Academy gratuita su CoddyKit. Questa è la lezione 4 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Swift Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Swift Academy include 4 lezioni in totale.

Comporre le funzionalità

Le app reali sono costruite a partire da molte piccole funzionalità. TCA consente a una funzionalità genitore di incorporare funzionalità figlie, così ognuna rimane focalizzata, per poi combinarle in un insieme più ampio. Gli strumenti per farlo sono Scope e l'inclusione dello stato e delle action figlie.

È la composizione a dare il nome The Composable Architecture.

import ComposableArchitecture

// A parent feature embeds child State and Action,
// then uses Scope to run the child's reducer.

Incorporare lo stato figlio

Lo State del genitore contiene lo stato del figlio come proprietà. L'enum Action del genitore ha un case che racchiude le action del figlio. Questa struttura annidata rispecchia il modo in cui è annidata l'interfaccia.

In questo caso, un genitore contiene un figlio contatore.

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() }
}

Definire lo scope per il figlio

Scope esegue il reducer figlio sulla porzione dello stato e dell'action del genitore che gli appartiene. Gli vengono forniti un key path dello stato e un case path dell'action, seguiti dal reducer figlio.

In questo modo il figlio viene integrato nel ciclo di aggiornamento del genitore.

import ComposableArchitecture

var body: some ReducerOf<Self> {
    Scope(state: \.counter, action: \.counter) {
        CounterFeature()
    }
    Reduce { state, action in
        // parent-specific logic here
        return .none
    }
}

Combinare più reducer

Il body di una funzionalità può elencare diversi reducer; TCA li esegue nell'ordine per ogni action. In questo modo è possibile affiancare uno o più Scope al Reduce del genitore.

In questo caso convivono due figli e la logica del genitore.

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
    }
}

Definire lo scope dello store per le view figlie

Dal lato della view, una view figlia ha bisogno di uno store specializzato per il figlio. Il genitore lo ottiene con store.scope, eseguendo il mapping verso lo stato e l'action del figlio.

In questo caso, la view genitore passa uno store con scope alla view del contatore.

import ComposableArchitecture
import SwiftUI

struct AppView: View {
    let store: StoreOf<AppFeature>
    var body: some View {
        CounterView(
            store: store.scope(state: \.counter, action: \.counter)
        )
    }
}

Presentazione di TestStore

TestStore consente di verificare esattamente il comportamento di una funzionalità. Si inviano action e si descrive come deve cambiare lo stato; se il comportamento effettivo è diverso, il test fallisce. Questa verifica esaustiva permette di individuare regressioni non evidenti.

Lo si crea come un normale store, specificando lo stato iniziale e il reducer.

import ComposableArchitecture
import XCTest

func testIncrement() async {
    let store = await TestStore(initialState: CounterFeature.State()) {
        CounterFeature()
    }
}

Verificare le modifiche allo stato

Quando si invia un'action a un TestStore, si passa una closure che descrive la modifica attesa. Il test ha esito positivo solo se lo stato cambia esattamente in quel modo.

In questo caso, l'incremento deve portare il count da 0 a 1.

import ComposableArchitecture
import XCTest

func testIncrement() async {
    let store = await TestStore(initialState: CounterFeature.State()) {
        CounterFeature()
    }
    await store.send(.incrementTapped) {
        $0.count = 1
    }
}

Completezza

Per impostazione predefinita, un TestStore è esaustivo: è necessario tenere conto di ogni modifica dello stato e di ogni action restituita da un effetto. Le modifiche non gestite fanno fallire il test, mantenendo le asserzioni rigorose.

Per test mirati, è possibile disattivare questa impostazione con 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

Ricevere le action degli effetti

Quando un effetto invia un'action, il test deve eseguire receive e verificare lo stato risultante. In questo modo si dimostra che il flusso asincrono funziona dall'inizio alla fine.

In questo caso, un reload attiva una risposta che il test attende e verifica.

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
    }
}

Sovrascrivere le dipendenze nei test

Per rendere deterministici gli effetti, sovrascriva le dipendenze quando crea il TestStore. Fornisca un client fittizio o un orologio di test, così i risultati saranno prevedibili.

In questo caso, il client dei numeri viene sostituito con uno che restituisce sempre 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 }
}

Testare le funzionalità composte

Poiché la composizione annida stato e action, per testare un genitore si inviano action del figlio racchiuse nel case del genitore. Le stesse asserzioni esaustive rimangono valide e verificano l'intero albero.

In questo caso, un genitore riceve un incremento del contatore attraverso il proprio scope.

import ComposableArchitecture
import XCTest

func testChildInParent() async {
    let store = await TestStore(initialState: AppFeature.State()) {
        AppFeature()
    }
    await store.send(.counter(.incrementTapped)) {
        $0.counter.count = 1
    }
}

Verifica rapida: comportamento di TestStore

Ricordi cosa richiede alle Sue asserzioni un TestStore predefinito.

Riepilogo: composizione e test

Ha completato il toolkit di TCA:

  • Scope esegue un reducer figlio su una porzione dello stato e dell'action del genitore, e diversi reducer possono essere combinati in un unico body.
  • store.scope fornisce a una view figlia il proprio store specializzato.
  • TestStore verifica le modifiche esatte dello stato quando si inviano action con send.
  • È esaustivo per impostazione predefinita e usa receive per le action generate dagli effetti.
  • Le dipendenze possono essere sovrascritte con withDependencies per ottenere test deterministici.

Ora può creare, comporre e testare rigorosamente le funzionalità con The Composable Architecture.

import ComposableArchitecture

// Recap: Scope to compose, TestStore to verify.
// Exhaustive assertions + injected fakes = confidence.

Domande Frequenti

La lezione «Composizione e test delle funzionalità» è gratuita?

Sì — il testo completo di «Composizione e test delle funzionalità» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Swift Academy, passa a CoddyKit PRO. Il corso Swift Academy include 4 lezioni in totale.

Cosa imparerò in «Composizione e test delle funzionalità»?

Combini i reducer e testi ogni caso in modo esaustivo. Eserciti Swift Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Swift Academy?

Non è richiesta alcuna esperienza precedente. Swift Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 4 di 4.

Quanto tempo richiede la lezione «Composizione e test delle funzionalità»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Swift Academy?

Sì. Ogni lezione Swift Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Stato, azione e reducer
  2. Lo Store e l’integrazione con SwiftUI
  3. Effetti e dipendenze
  4. Composizione e test delle funzionalità
← Torna a Swift Academy