실무 백엔드에서는 기능 구현만큼 중요한 게 테스트다.
특히 DB를 다루는 GORM에서는 테스트 코드 작성이 까다로울 수 있지만, 구조만 잘 잡으면 생산성도, 안정성도 높아진다.
✅ 목차
- GORM 테스트, 왜 어려울까?
- 테스트 환경 구성 전략
- SQLite in-memory DB
- 트랜잭션 롤백
- 인터페이스 분리로 Mocking 가능하게 만들기
- gorm.io/datatypes, faker로 더 현실적인 테스트
- 실전 테스트 예제 정리
1. GORM 테스트, 왜 어려울까?
GORM은 DB 연결을 전제로 하기 때문에, 테스트 시 다음 이슈가 생긴다:
- 실제 DB를 연결하면 테스트 데이터 관리가 어렵다.
- 트랜잭션 롤백 없이 돌리면 더티 데이터가 남는다.
- GORM 객체가 의존성 주입이 안 되면 Mocking 불가능하다.
→ 해결법은: 테스트 전용 DB, 트랜잭션 관리, 인터페이스 분리다.
2. 테스트 환경 구성 전략
✅ SQLite In-Memory DB 활용
GORM은 sqlite 드라이버로 메모리 DB를 쓸 수 있다.
import (
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
func SetupTestDB() *gorm.DB {
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
if err != nil {
panic("failed to connect to test database")
}
db.AutoMigrate(&User{}) // 테스트용 테이블 정의
return db
}
- 테스트마다 새 DB를 쓰므로 상태가 깨끗
- AutoMigrate로 스키마 구성 가능
✅ 트랜잭션으로 테스트 고립시키기
func TestSomething(t *testing.T) {
db := SetupTestDB()
tx := db.Begin() // 트랜잭션 시작
defer tx.Rollback() // 테스트 종료 시 롤백
repo := NewUserRepo(tx)
// 테스트 수행
}
- 테스트가 끝나면 항상 Rollback()
- 다른 테스트와 데이터 충돌 없음
3. 인터페이스로 GORM 추상화하기
GORM은 그대로 쓰면 Mock이 어렵다.
인터페이스로 추상화하면 DB 대신 Fake 객체를 주입 가능!
✅ 예시: Repository 인터페이스 정의
type UserRepository interface {
FindByID(id uint) (*User, error)
Save(user *User) error
}
✅ 실제 GORM Repo
type GormUserRepo struct {
db *gorm.DB
}
func (r *GormUserRepo) FindByID(id uint) (*User, error) {
var user User
err := r.db.First(&user, id).Error
return &user, err
}
✅ 테스트용 Fake Repo
type FakeUserRepo struct {
data map[uint]*User
}
func (f *FakeUserRepo) FindByID(id uint) (*User, error) {
user, ok := f.data[id]
if !ok {
return nil, errors.New("not found")
}
return user, nil
}
→ 실제 코드에서는 UserRepository만 참조하므로, 테스트할 때는 Fake 객체만 바꿔 끼우면 됨!
4. datatypes, faker로 현실적인 테스트 만들기
GORM의 gorm.io/datatypes는 JSON 필드 테스트에 유용하고,
github.com/bxcodec/faker/v4는 더미 데이터를 손쉽게 생성해준다.
✅ JSON 필드 테스트
type User struct {
ID uint
Profile datatypes.JSON
}
user := User{
Profile: datatypes.JSON([]byte(`{"nickname":"Yeri"}`)),
}
✅ Faker로 더미 데이터 만들기
type Product struct {
Name string `faker:"word"`
Price int `faker:"boundary_start=100, boundary_end=999"`
}
faker.FakeData(&product) // 자동으로 필드 채워짐
5. 실전 테스트 예제
func TestUserCreation(t *testing.T) {
db := SetupTestDB()
tx := db.Begin()
defer tx.Rollback()
repo := NewUserRepo(tx)
user := &User{Name: "Yeri", Age: 28}
err := repo.Save(user)
assert.NoError(t, err)
assert.NotZero(t, user.ID)
}
- 테스트 DB 사용
- 트랜잭션으로 격리
- GORM을 추상화한 인터페이스 구조
🔚 마무리
GORM에서 테스트를 잘하기 위해서는 다음 3가지를 기억하자
- 메모리 DB 또는 테스트 전용 DB를 활용하자
- 트랜잭션으로 테스트를 고립시키자
- GORM 로직은 인터페이스로 추상화해서 Mocking 가능하게 하자
이렇게 구조를 잘 잡아두면, GORM으로도 신뢰도 높은 단위 테스트를 작성할 수 있다.
'개발 > Go' 카테고리의 다른 글
[Gin] GORM + Redis 캐싱 전략 - DB 부하 줄이는 실전 캐싱 (75) | 2025.06.16 |
---|---|
[Gin] GORM 디버깅 & 에러 핸들링 완전정복 (59) | 2025.06.13 |
[Gin] GORM에서 안전한 쿼리 작성법 - SQL 인젝션 방지와 Named Parameter 전략 (69) | 2025.06.09 |
[Gin] GORM 마이그레이션 전략 - 자동 vs 수동 관리, 안전하게 스키마 관리하기 (35) | 2025.06.06 |
[Gin] GORM 성능 최적화 팁 - Preload, Select, Index 전략까지! (64) | 2025.06.04 |