테스트 시나리오
이 가이드는 테스트에서 env 라이브러리를 사용하는 방법을 소개하며, 테스트 환경 격리, 파일 시스템 모킹 및 상태 정리를 다룹니다.
테스트 구성
TestingConfig 사용
TestingConfig 는 기존 환경 변수를 덮어쓰므로 테스트 격리에 적합합니다:
go
func TestWithTestingConfig(t *testing.T) {
cfg := env.TestingConfig()
cfg.Filenames = []string{".env.test"}
loader, err := env.New(cfg)
require.NoError(t, err)
defer loader.Close()
// 테스트 구성 사용
host := loader.GetString("DB_HOST")
}참고
TestingConfig 는 OverwriteExisting: true를 설정하여 테스트 격리를 보장합니다. 기존 변수를 유지해야 하는 경우 cfg.OverwriteExisting = false로 수동 설정할 수 있습니다.
각 테스트마다 독립적인 로더
go
func TestDatabase(t *testing.T) {
loader, err := env.New(env.TestingConfig())
require.NoError(t, err)
defer loader.Close()
// 테스트 값 설정
loader.Set("DB_HOST", "localhost")
loader.Set("DB_PORT", "5432")
// 테스트 실행...
}파일 시스템 모킹
커스텀 FileSystem
FileSystem 인터페이스를 사용하여 파일을 모킹합니다:
go
type MockFileSystem struct {
files map[string]string
env map[string]string
}
func (m *MockFileSystem) Open(name string) (env.File, error) {
content, ok := m.files[name]
if !ok {
return nil, os.ErrNotExist
}
return &MockFile{content: content}, nil
}
func (m *MockFileSystem) OpenFile(name string, flag int, perm os.FileMode) (env.File, error) {
return m.Open(name)
}
func (m *MockFileSystem) Stat(name string) (os.FileInfo, error) {
if _, ok := m.files[name]; !ok {
return nil, os.ErrNotExist
}
return nil, nil
}
func (m *MockFileSystem) MkdirAll(path string, perm os.FileMode) error { return nil }
func (m *MockFileSystem) Remove(name string) error { delete(m.files, name); return nil }
func (m *MockFileSystem) Rename(oldpath, newpath string) error {
m.files[newpath] = m.files[oldpath]
delete(m.files, oldpath)
return nil
}
func (m *MockFileSystem) Getenv(key string) string { return m.env[key] }
func (m *MockFileSystem) Setenv(key, value string) error { m.env[key] = value; return nil }
func (m *MockFileSystem) Unsetenv(key string) error { delete(m.env, key); return nil }
func (m *MockFileSystem) LookupEnv(key string) (string, bool) { val, ok := m.env[key]; return val, ok }테스트에서 사용
go
func TestWithMockFS(t *testing.T) {
mockFS := &MockFileSystem{
files: map[string]string{
".env": "HOST=localhost\nPORT=8080\n",
},
env: make(map[string]string),
}
cfg := env.TestingConfig()
cfg.FileSystem = mockFS
loader, err := env.New(cfg)
require.NoError(t, err)
defer loader.Close()
err = loader.LoadFiles(".env")
require.NoError(t, err)
assert.Equal(t, "localhost", loader.GetString("HOST"))
assert.Equal(t, int64(8080), loader.GetInt("PORT"))
}테스트 상태 정리
기본 로더 재설정
go
func TestMain(m *testing.M) {
// 테스트 실행 전 재설정
env.ResetDefaultLoader()
os.Exit(m.Run())
}
func TestExample(t *testing.T) {
// 각 테스트에서도 재설정 가능
env.ResetDefaultLoader()
// 패키지 수준 함수 사용
env.Load(".env.test")
}환경 변수 정리
go
func TestWithCleanup(t *testing.T) {
// 원래 값 저장
originalHost := os.Getenv("TEST_HOST")
// 테스트 값 설정
os.Setenv("TEST_HOST", "test-value")
// 테스트 종료 후 복원
t.Cleanup(func() {
if originalHost == "" {
os.Unsetenv("TEST_HOST")
} else {
os.Setenv("TEST_HOST", originalHost)
}
})
// 테스트 실행...
}테이블 기반 테스트
구성 테스트
go
func TestTypeConversion(t *testing.T) {
tests := []struct {
name string
envValue string
getInt int64
getBool bool
}{
{"integer", "42", 42, false},
{"zero", "0", 0, false},
{"bool_true", "true", 0, true},
{"bool_yes", "yes", 0, true},
{"bool_1", "1", 1, true},
{"bool_false", "false", 0, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
loader, _ := env.New(env.TestingConfig())
defer loader.Close()
loader.Set("VALUE", tt.envValue)
assert.Equal(t, tt.getInt, loader.GetInt("VALUE"))
assert.Equal(t, tt.getBool, loader.GetBool("VALUE"))
})
}
}검증 테스트
go
func TestValidation(t *testing.T) {
tests := []struct {
name string
required []string
envValues map[string]string
expectError bool
}{
{
name: "all required present",
required: []string{"A", "B"},
envValues: map[string]string{"A": "1", "B": "2"},
expectError: false,
},
{
name: "missing required",
required: []string{"A", "B", "C"},
envValues: map[string]string{"A": "1"},
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := env.TestingConfig()
cfg.RequiredKeys = tt.required
loader, _ := env.New(cfg)
defer loader.Close()
for k, v := range tt.envValues {
loader.Set(k, v)
}
err := loader.Validate()
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}통합 테스트
설정 로딩 테스트
go
func TestConfigLoading(t *testing.T) {
// 임시 .env 파일 생성
tmpDir := t.TempDir()
envFile := filepath.Join(tmpDir, ".env")
content := `
APP_NAME=test-app
APP_VERSION=1.0.0
DEBUG=true
`
err := os.WriteFile(envFile, []byte(content), 0644)
require.NoError(t, err)
// 설정 로딩
cfg := env.TestingConfig()
loader, err := env.New(cfg)
require.NoError(t, err)
defer loader.Close()
err = loader.LoadFiles(envFile)
require.NoError(t, err)
// 검증
assert.Equal(t, "test-app", loader.GetString("APP_NAME"))
assert.Equal(t, "1.0.0", loader.GetString("APP_VERSION"))
assert.True(t, loader.GetBool("DEBUG"))
}구조체 매핑 테스트
go
func TestStructMapping(t *testing.T) {
type Config struct {
Host string `env:"HOST" envDefault:"localhost"`
Port int64 `env:"PORT" envDefault:"8080"`
}
loader, _ := env.New(env.TestingConfig())
defer loader.Close()
loader.Set("HOST", "example.com")
loader.Set("PORT", "443")
var cfg Config
err := loader.ParseInto(&cfg)
require.NoError(t, err)
assert.Equal(t, "example.com", cfg.Host)
assert.Equal(t, int64(443), cfg.Port)
}동시성 테스트
go
func TestConcurrentAccess(t *testing.T) {
loader, _ := env.New(env.TestingConfig())
defer loader.Close()
var wg sync.WaitGroup
// 동시 쓰기
for i := 0; i < 100; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
key := fmt.Sprintf("KEY_%d", n)
loader.Set(key, fmt.Sprintf("value_%d", n))
}(i)
}
// 동시 읽기
for i := 0; i < 100; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
key := fmt.Sprintf("KEY_%d", n)
loader.GetString(key)
}(i)
}
wg.Wait()
}벤치마크 테스트
읽기 성능
go
func BenchmarkGetString(b *testing.B) {
loader, _ := env.New(env.TestingConfig())
defer loader.Close()
loader.Set("KEY", "value")
b.ResetTimer()
for i := 0; i < b.N; i++ {
loader.GetString("KEY")
}
}로딩 성능
go
func BenchmarkLoadFile(b *testing.B) {
// 임시 파일 생성
tmpDir := b.TempDir()
envFile := filepath.Join(tmpDir, ".env")
var content strings.Builder
for i := 0; i < 100; i++ {
content.WriteString(fmt.Sprintf("KEY_%d=value_%d\n", i, i))
}
os.WriteFile(envFile, []byte(content.String()), 0644)
b.ResetTimer()
for i := 0; i < b.N; i++ {
loader, _ := env.New(env.TestingConfig())
loader.LoadFiles(envFile)
loader.Close()
}
}테스트 유틸리티 함수
테스트 보조 함수를 만듭니다:
go
// testutil/env.go
package testutil
import (
"os"
"testing"
"github.com/cybergodev/env"
)
func NewTestLoader(t *testing.T, values map[string]string) *env.Loader {
t.Helper()
loader, err := env.New(env.TestingConfig())
if err != nil {
t.Fatalf("failed to create loader: %v", err)
}
t.Cleanup(func() {
loader.Close()
})
for k, v := range values {
if err := loader.Set(k, v); err != nil {
t.Fatalf("failed to set %s: %v", k, err)
}
}
return loader
}
func SetTestEnv(t *testing.T, key, value string) {
t.Helper()
original := os.Getenv(key)
os.Setenv(key, value)
t.Cleanup(func() {
if original == "" {
os.Unsetenv(key)
} else {
os.Setenv(key, original)
}
})
}사용 예시:
go
func TestWithHelper(t *testing.T) {
loader := testutil.NewTestLoader(t, map[string]string{
"DB_HOST": "localhost",
"DB_PORT": "5432",
})
// 로더는 테스트 종료 후 자동으로 닫힘
assert.Equal(t, "localhost", loader.GetString("DB_HOST"))
}관련 문서
- Config API - TestingConfig - 테스트 구성 참조
- Loader API - Loader 전체 메서드
- 인터페이스 정의 - FileSystem - 커스텀 파일 시스템 인터페이스
- 성능 최적화 - 벤치마크 테스트 데이터