개발/Go

[Gin] GORM으로 단위 테스트 잘하는 법 - 테스트 환경 구성부터 Mock까지

devhooney 2025. 6. 11. 07:41
728x90

실무 백엔드에서는 기능 구현만큼 중요한 게 테스트다.
특히 DB를 다루는 GORM에서는 테스트 코드 작성이 까다로울 수 있지만, 구조만 잘 잡으면 생산성도, 안정성도 높아진다.


 

 

✅ 목차
- GORM 테스트, 왜 어려울까?
- 테스트 환경 구성 전략
- SQLite in-memory DB
- 트랜잭션 롤백
- 인터페이스 분리로 Mocking 가능하게 만들기
- gorm.io/datatypes, faker로 더 현실적인 테스트
- 실전 테스트 예제 정리



 

 

728x90

 

 

 

 


 

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으로도 신뢰도 높은 단위 테스트를 작성할 수 있다.

 

 

 

728x90