Go의 픽스처: Axiom을 사용한 자동화 테스트에서 인프라 추출
Go의 통합 및 E2E 테스트는 종종 비즈니스 로직과 인프라 관리를 섞는 문제를 겪습니다. 표준 testing 패키지는 픽스처 메커니즘을 제공하지 않아 개발자들이 테스트 내부에서 클라이언트 초기화, 데이터 준비, 리소스 정리 코드를 중복해야 합니다. 이로 인해 가독성이 떨어지고 유지보수가 복잡해지며 테스트 스위트 확장이 어려워집니다. 해결책은 Axiom 같은 서드파티 라이브러리를 통해 픽스처를 구현하는 것으로, 관리되는 라이프사이클을 가진 의존성을 선언적으로 기술할 수 있습니다.
테스트 맥락에서의 픽스처란
픽스처는 단순한 도우미 함수가 아닙니다—명확히 정의된 라이프사이클을 가진 리소스입니다: 필요 시 생성, 테스트 실행 후 자동 정리, 다른 픽스처에 의존 가능, 어설션 로직과의 격리. 연결이나 데이터의 수동 관리와 달리 픽스처는 다음을 제공합니다:
- 지연 초기화—리소스는 첫 접근 시에만 생성됩니다.
- 자동 정리—테스트가 실패하더라도 상태가 정리된다는 보장.
- 선언적 의존성—한 픽스처가 다른 픽스처를 수동 매개변수 전달 없이 참조할 수 있습니다.
- 테스트 및 재시도 간 격리—상태가 시도 간에 누출되지 않습니다.
Python(pytest)이나 Java(JUnit) 같은 언어에는 이러한 메커니즘이 내장되어 있습니다. Go에서는 명시적으로 구현하거나 특수 도구를 사용해야 합니다.
픽스처 없이의 문제: 테스트 내부의 인프라
픽스처가 없는 전형적인 Go 테스트는 다음과 같습니다:
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")
}
}
여기서 클라이언트 생성부터 사용자 삭제까지 모든 것이 테스트 본문 내부에 있습니다. 의존성 수가 증가함에 따라 (databases, external APIs, message queues), 이 접근법은 다음으로 이어집니다:
- 높은 코드 중복.
- 변경 어려움 (예: 클라이언트 인증 업데이트).
- 정리 로직 오류 가능성 증가.
- 낮은 가독성: 핵심 어설션이 인프라 세부 사항에 묻힙니다.
Axiom을 사용한 픽스처 구현
Axiom 라이브러리는 값, 정리 함수, 오류를 반환하는 함수로 픽스처를 기술하는 메커니즘을 제공합니다. 라이프사이클은 Runner와 Case를 통해 관리됩니다.
예제 구조:
// 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")
}
})
}
이 접근법의 주요 장점:
- 관심사 명확한 분리: 픽스처가 인프라 처리, 테스트가 동작 처리.
- 재사용성: 단일 클라이언트 픽스처를 Runner를 통해 모든 테스트에서 사용.
- 재시도 안전성: 테스트 재시도 시 모든 픽스처 재생성, 이전 상태 영향 방지.
- 선언적: 테스트 본문에 명령형 코드 없이 의존성 명시적 선언.
픽스처 구현 시기
다음 시나리오에서 픽스처는 특히 유용합니다:
- 여러 외부 의존성을 가진 통합 테스트 (DB, API, file storage).
- 복잡한 환경 설정이 필요한 E2E 테스트.
- 테스트 수가 많고 (>50–100) 유지보수가 중요한 프로젝트.
- 테스트 인프라의 일관성과 예측 가능성을 추구하는 팀.
인터페이스를 통해 의존성을 쉽게 모킹할 수 있는 간단한 단위 테스트에는 픽스처를 사용하지 마세요.
주요 포인트
- Go에 픽스처가 내장되어 있지 않습니다—명시적 구현 또는 Axiom 같은 라이브러리 사용.
- 픽스처의 목표는 테스트 본문에서 인프라 로직 추출.
- Axiom은 지연 초기화, 자동 정리, 테스트 간 격리를 제공.
- 수백 개의 통합 테스트를 가진 대형 프로젝트에서 특히 효과적.
- 픽스처는 모킹을 대체하지 않습니다—실제 환경이 필요한 시나리오에서 보완.
— Editorial Team
아직 댓글이 없습니다.