Zpět na domů

Fixtures v Go pro automatické testy: praxe s Axiom

Článek vysvětluje, jak používat fixtures v Go k vyvedení infrastrukturní logiky z automatických testů. Je zde probrána knihovna Axiom, její architektura a praktické příklady použití v integračních scénářích.

Go fixtures: jak psát čisté integrační testy
Advertisement 728x90

Fixturní v Go: vynesení infrastruktury z automatizovaných testů s Axiom

Integrační a E2E testy v Go často trpí smísením business logiky a správy infrastruktury. Standardní balíček testing neposkytuje mechanismus fixturní, kvůli čemuž vývojáři musí duplikovat kód inicializace klientů, přípravy dat a čištění zdrojů přímo uvnitř testů. To snižuje čitelnost, ztěžuje údržbu a brání škálování testovací sady. Řešení spočívá ve zavedení fixturní prostřednictvím knihoven třetích stran, jako je Axiom, které umožňují deklarativně popisovat závislosti s řízeným životním cyklem.

Co je fixturní v kontextu testování

Fixturní není jen pomocná funkce, ale zdroj s jasně definovaným životním cyklem: vytvoření na požádání, automatické čištění po dokončení testu, možnost záviset na jiných fixturních a izolace od logiky ověřování. Na rozdíl od ručního řízení spojení nebo dat fixturní zajišťují:

  • Línou inicializaci – zdroj se vytvoří až při prvním přístupu.
  • Automatické čištění – i při selhání testu se stav spolehlivě vyčistí.
  • Deklarativní závislosti – jedna fixturní může odkazovat na jinou bez ruční předávání parametrů.
  • Izolaci mezi testy a opakovanými spuštěními (retry) – stav neprosakuje mezi pokusy.

V jazycích jako Python (pytest) nebo Java (JUnit) jsou takové mechanismy vestavěné. V Go je nutné je implementovat explicitně nebo použít specializované nástroje.

Google AdInline article slot

Problém bez fixturní: infrastruktura uvnitř testu

Typický Go test bez fixturní vypadá takto:

func TestGetUser(t *testing.T) {
    client, err := NewUserClient()
    if err != nil {
        t.Fatalf("failed to create client: %v", err)
    }
    defer client.Close()

    user, err := CreateUser(client)
    if err != nil {
        t.Fatalf("failed to create user: %v", err)
    }
    defer func() {
        _ = DeleteUser(client, user.ID)
    }()

    resp, err := client.GetUser(user.ID)
    if err != nil {
        t.Fatalf("failed to get user: %v", err)
    }

    if resp.ID != user.ID {
        t.Fatalf("unexpected user id")
    }
}

Zde je vše – od vytvoření klienta po smazání uživatele – uvnitř těla testu. Při nárůstu počtu závislostí (databáze, externí API, fronty zpráv) takový přístup vede k:

  • Vysoké míře duplikace kódu.
  • Složitosti při provádění změn (např. aktualizace způsobu autentizace klienta).
  • Zvýšenému riziku chyb v logice čištění.
  • Špatné čitelnosti: podstata ověření se ztrácí mezi infrastrukturními detaily.

Implementace fixturní s Axiom

Knihovna Axiom poskytuje mechanismus pro popis fixturní jako funkcí, které vracejí hodnotu, funkci čištění a chybu. Životní cyklus je řízen prostřednictvím Runner a Case.

Google AdInline article slot

Příklad struktury:

// Fixture client
func UserClientFixture(_ *axiom.Config) (any, func(), error) {
    client, err := NewUserClient()
    if err != nil {
        return nil, nil, err
    }
    return client, client.Close, nil
}

// Fixture polzovatelya, zavisyaschaya from client
func UserFixture(cfg *axiom.Config) (any, func(), error) {
    client := axiom.GetFixture*UserClient
    user, err := client.CreateUser()
    if err != nil {
        return nil, nil, err
    }
    cleanup := func() { _ = client.DeleteUser(user.ID) }
    return user, cleanup, nil
}

// Runner with globalnoy fiksturoy
var runner = axiom.NewRunner(
    axiom.WithRunnerFixture("client", UserClientFixture),
)

// Test
func TestGetUser(t *testing.T) {
    c := axiom.NewCase(
        axiom.WithCaseName("get user by id"),
        axiom.WithCaseFixture("user", UserFixture),
    )

    runner.RunCase(t, c, func(cfg *axiom.Config) {
        client := axiom.GetFixture*UserClient
        user := axiom.GetFixture*User

        resp, err := client.GetUser(user.ID)
        if err != nil {
            t.Fatalf("failed to get user: %v", err)
        }
        if resp.ID != user.ID {
            t.Fatalf("unexpected user id")
        }
    })
}

Klíčové výhody takového přístupu:

  • Jasné oddělení odpovědností: fixturní se starají o infrastrukturu, test o chování.
  • Opakované použití: jedna fixturní klienta lze použít ve všech testech prostřednictvím Runner.
  • Bezpečnost při retry: při opakovaném spuštění testu se všechny fixturní znovu vytvoří, což vylučuje vliv předchozích stavů.
  • Deklarativnost: závislosti se deklarují explicitně, bez imperativního kódu v těle testu.

Kdy zavádět fixturní

Fixturní jsou obzvláště užitečné v následujících scénářích:

Google AdInline article slot
  • Integrační testy s více externími závislostmi (DB, API, úložiště souborů).
  • E2E testy vyžadující přípravu složitého prostředí.
  • Projekty s velkým množstvím testů (>50–100), kde je údržba klíčová.
  • Týmy, které usilují o jednotnost a předvídatelnost v testovací infrastruktuře.

Fixturní se nevyplatí používat pro jednoduché unit testy, kde závislosti lze snadno mockovat přes rozhraní.

Co je důležité

  • Fixturní v Go nejsou vestavěné – je nutné je implementovat explicitně nebo použít knihovny jako Axiom.
  • Cílem fixturní je vynést infrastrukturní logiku mimo tělo testu.
  • Axiom zajišťuje línou inicializaci, automatické čištění a izolaci mezi testy.
  • Přístup je obzvláště efektivný ve velkých projektech se stovkami integračních testů.
  • Fixturní nemohou nahradit mocky – doplňují je v scénářích, kde je potřeba reálné prostředí.

— Editorial Team

Advertisement 728x90

Číst dál