프로젝트 소개

# go-docker-testsuite Docker 컨테이너에서 통합 테스트용 타사 종속성을 실행하기 위한 Go 라이브러리입니다. 데이터베이스, 큐, 캐시, 객체 스토리지 등 모든 Docker 이미지를 실행하고 Go 테스트에서 연결할 수 있습니다. ## 기능 - **Container** — 모든 Docker 이미지를 생성, 실행, 출력 대기, 정리하는 저수준 래퍼 - **Group** — 격리된 Docker 네트워크에서 IP 수준 연결로 여러 컨테이너 실행 - **Applications** — 인기 서비스(MySQL, PostgreSQL, Redis, Kafka 등)용 즉시 사용 가능한 래퍼 - **Hooks** — 컨테이너별 수명주기 콜백(BeforeRun, AfterRun, BeforeClose, AfterClose) - **Exec** — 실행 중인 컨테이너 내부에서 명령 실행 및 stdout, stderr, 종료 코드 캡처 - **수명주기 명령** — exec를 통한 시작/준비 완료 후 명령 실행 - **컨테이너로 파일 복사** — `WithFiles`로 시작 전 파일 시드 - **컨테이너에서 파일 복사** — 실행 중인 컨테이너에서 tar 스트림으로 파일 읽기 - **리소스 제한** — 호스트 및 CI 보호를 위한 CPU/메모리/pids 제한 - **Matchers** — 부분 문자열, 정확히 일치, 정규식 매처로 컨테이너 로그 대기 - **대기 전략** — 구성 가능한 준비 상태 프로브(ForLog, ForHTTPGet, ForCommand, ForTCPConnection) - **환경 빌더** — 타입화된 환경 변수용 유창한 DSL - **포트 바인딩** — 무작위 또는 1:1 할당을 통한 DNAT 포트 매핑 - **네트워크 모드** — 호스트 또는 사용자 정의 네트워크 지원 - **IMAGE_PREFIX** — 프록시/미러를 통한 이미지 라우팅 - **`*testing.T` 바인딩** — 안전한 `t.Parallel()`과 함께 자동 정리 및 로깅 ## 요구 사항 - Go 1.26 이상 - 실행 중인 Docker 데몬(`DOCKER_HOST`를 통한 원격 호스트 지원) ## 설치 멀티 모듈 워크스페이스: 루트는 핵심 모듈이고, `applications/<name>` 아래의 각 애플리케이션은 자체 Go 모듈입니다. 필요한 것만 가져오세요: ```sh go get github.com/teran/go-docker-testsuite # 특정 애플리케이션 래퍼: go get github.com/teran/go-docker-testsuite/applications/redis go get github.com/teran/go-docker-testsuite/applications/postgres ``` ## 애플리케이션 즉시 사용 가능한 래퍼(각각 타입화된 클라이언트 인터페이스를 반환하고 시작, 상태 확인, 정리를 처리): - AWS SDK v2를 사용한 Ceph (RGW) - clickhouse-go를 사용한 ClickHouse - SQLite를 사용한 Forgejo - vtysh를 사용한 FRR 라우팅 스위트 - client-go를 사용한 K3s - Sarama를 사용한 Kafka - gomemcache를 사용한 Memcache - MinIO (S3 호환) - mongo-driver를 사용한 MongoDB - MySQL / MariaDB / Percona Server - PostgreSQL + Redis를 사용한 NetBox - Nginx - opensearch-go를 사용한 OpenSearch - PostgreSQL + Valkey를 사용한 Paperless-ngx - pgx를 사용한 PostgreSQL - prometheus/client_golang을 사용한 Prometheus - RabbitMQ (AMQP + 관리 API) - go-redis를 사용한 Redis - gocql을 사용한 ScyllaDB - Vault - Libvirtd (KVM/QEMU) 많은 패키지에는 pkg.go.dev에서 테스트 가능한 예제가 포함되어 있습니다. ## 사용법 ### 빠른 시작 — MySQL ```go package main import ( "context" "database/sql" "time" _ "github.com/go-sql-driver/mysql" "github.com/teran/go-docker-testsuite/applications/mysql" ) func main() { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() app, err := mysql.New(ctx, "index.docker.io/library/mysql:8.0.4") if err != nil { panic(err) } defer app.Close(ctx) if err := app.CreateDB(ctx, "important_database"); err != nil { panic(err) } db, err := sql.Open("mysql", app.MustDSN("important_database")) if err != nil { panic(err) } defer db.Close() if _, err := db.ExecContext(ctx, "SELECT 1"); err != nil { panic(err) } } ``` ### 다중 컨테이너 그룹 ```go package main import ( "context" "time" "github.com/teran/go-docker-testsuite" "github.com/teran/go-docker-testsuite/wait" ) func main() { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() app := docker.NewApplication( c, docker.HookFunc(func(ctx context.Context, ht docker.HookType, c docker.Container) error { return wait.Wait(ctx, c, wait.ForLog(docker.NewSubstringMatcher("ready"))) }), ) g, err := docker.NewGroup("my-services", app1, app2) if err != nil { panic(err) } if err := g.Run(ctx); err != nil { panic(err) } defer g.Close(ctx) } ``` ### 대기 전략 ```go if err := wait.Wait(ctx, c, wait.ForHTTPGet(8080, wait.WithPath("/health"), wait.WithResponseStatuses(200), )); err != nil { panic(err) } ``` 기타 전략: `ForLog`, `ForCommand`, `ForTCPConnection`. `ForAll` / `ForAny` / `ForAtLeast`와 결합하세요. ### `*testing.T` 바인딩 ```go func TestRedis(t *testing.T) { t.Parallel() c, err := docker.NewContainerWithT( t, "redis", images.Redis, nil, docker.NewEnvironment(), docker.NewPortBindings().PortDNAT(docker.ProtoTCP, 6379), ) if err != nil { t.Fatal(err) } c.RunT(ctx) // 빠른 실패; t.Cleanup 등록 } ``` ### 수명주기 훅 ```go docker.HookTypeBeforeRun // 컨테이너 시작 전 docker.HookTypeAfterRun // 컨테이너 시작 후 docker.HookTypeBeforeClose // 컨테이너 중지 전 docker.HookTypeAfterClose // 컨테이너 중지 후 ``` ### Exec 및 수명주기 명령 ```go res, err := c.Exec(ctx, []string{"echo", "hello"}) if err != nil { panic(err) } if err := res.Error(); err != nil { panic(err) } fmt.Printf("exit code: %d\n", res.ExitCode) fmt.Printf("stdout: %s", res.Stdout) ``` ### 컨테이너 안팎으로 파일 복사 작은 콘텐츠에는 `FileFromBytes`와 함께 `WithFiles`를 사용하고, 큰 파일에는 `io.Reader` + `Size`를 사용하세요. `docker.CopyFromContainer`로 파일을 다시 읽으세요. ### 이미지 접두사 / 프록시 ```sh export IMAGE_PREFIX=registry-mirror.example.com ``` ## 모듈 및 릴리스 단일 버전 번호를 가진 멀티 모듈 워크스페이스로, 별도로 태그됩니다(예: 핵심 `v1.6.0`, 애플리케이션 `applications/redis/v1.6.0`). 릴리스 순서: 핵심 먼저, 그 다음 애플리케이션. 태그에는 Makefile을 사용하세요. ## 라이선스 Apache License, Version 2.0