학습
Go 모의 객체 (Mocking)
Go 모의 객체 (Mocking) — 중급
모의 객체 (Mocking)
1. 도입 (왜 배워야 하는가)
모의 객체(mocking)는 소프트웨어 테스트에서 매우 중요한 개념입니다. 실제 환경에서 외부 시스템(예: 데이터베이스, API, 메시지 큐 등)과의 연결 없이도 테스트를 수행할 수 있게 해줍니다. 이런 방식은 테스트의 독립성을 높이고, 테스트 환경을 더욱 안정적으로 만들어 줍니다.
만약 모의 객체를 사용하지 않으면, 테스트를 위해 실제 외부 시스템에 의존해야 하며, 이는 다음과 같은 문제를 초래할 수 있습니다:
- 테스트의 속도가 느려진다.
- 외부 시스템의 상태에 따라 테스트 결과가 달라진다.
- 실제 시스템에 영향을 줄 수 있다(예: 데이터 변경 등).
따라서 모의 객체를 사용하면 안정적이고 신뢰할 수 있는 테스트 환경을 구축할 수 있습니다.
2. 핵심 개념 설명
모의 객체는 실제 객체를 대신하여 특정 동작을 시뮬레이션하는 객체입니다. 이를 통해 객체의 동작을 조작하거나, 특정 조건 하에서의 동작을 확인할 수 있습니다.
관련 용어 정리
| 용어 | 설명 |
|---|---|
| Mock | 특정 동작과 기대 결과를 정의한 객체 |
| Stub | 특정 메서드의 결과를 미리 정의한 객체 |
| Spy | 메서드 호출 여부 및 호출된 인자를 추적하는 객체 |
비유: 모의 객체는 마치 연극에서 배우가 아닌 대본을 대신 읽는 사람과 같습니다. 실제 배우가 연기하는 대신 대본을 통해 연극의 흐름을 확인하는 것입니다.
3. 기본 코드 예제
아래는 Go에서 모의 객체를 사용하는 기본 예제입니다.
기본 코드
package main
import (
"fmt"
"testing"
)
// UserService 인터페이스 정의
type UserService interface {
GetUser(id int) string
}
// UserService의 모의 객체
type MockUserService struct {
UserData map[int]string
}
func (m MockUserService) GetUser(id int) string {
return m.UserData[id]
}
// 테스트 함수
func TestGetUser(t testing.T) {
mockService := &MockUserService{
UserData: map[int]string{
1: "Alice",
2: "Bob",
},
}
result := mockService.GetUser(1)
if result != "Alice" {
t.Errorf("Expected Alice, got %s", result)
}
}
실행 결과
--- FAIL: TestGetUser (0.00s)
main_test.go:12: Expected Alice, got Bob
코드 한 줄씩 동작 설명
UserService인터페이스를 정의하여 사용자 정보를 가져오는 메서드GetUser를 선언합니다.MockUserService구조체를 정의하고, 사용자 데이터를 저장할 맵을 포함합니다.GetUser메서드를 구현하여 요청된 ID에 해당하는 사용자 이름을 반환합니다.TestGetUser함수는MockUserService를 사용해 유저 정보를 테스트합니다.- 테스트가 실행되면, 사용자 ID 1에 대해 "Alice"를 기대하지만, 만약 다른 값이 반환되면 에러를 발생시킵니다.
- 주의: 모의 객체는 테스트할 상황에 맞게 세밀하게 정의해야 합니다.
- 모의 객체는 테스트 독립성을 높이고, 외부 시스템의 의존성을 줄인다.
Mock,Stub,Spy와 같은 용어를 이해하고 적절히 사용해야 한다.- 다양한 상황을 시뮬레이션할 수 있도록 모의 객체를 세심하게 정의해야 한다.
- Go의 테스트 패키지 활용법
- 테스트 주도 개발(TDD) 접근법
- Go에서의 인터페이스 활용법
4. 실전 활용 예제
예제 1: 쇼핑몰에서 장바구니를 구현할 때
쇼핑몰에서 장바구니에 상품을 추가하고, 특정 상품의 가격을 조회하는 기능을 테스트할 경우, 실제 데이터베이스에 접근하지 않고 모의 객체를 사용할 수 있습니다.
type ProductService interface {
GetPrice(productId int) float64
}
type MockProductService struct {
Prices map[int]float64
}
func (m MockProductService) GetPrice(productId int) float64 {
return m.Prices[productId]
}
// 장바구니 테스트
func TestAddToCart(t testing.T) {
mockService := &MockProductService{
Prices: map[int]float64{
1: 10.0,
2: 20.0,
},
}
price := mockService.GetPrice(1)
if price != 10.0 {
t.Errorf("Expected price 10.0, got %f", price)
}
}
예제 2: 외부 API와의 통신 테스트
외부 API와 통신하는 코드를 테스트할 때도 모의 객체를 사용할 수 있습니다. API의 응답을 미리 정의하여 다양한 시나리오를 테스트할 수 있습니다.
5. 흔한 실수와 주의사항
실수 1: 모의 객체의 동작을 잘못 정의하기
// 잘못된 코드
func (m MockUserService) GetUser(id int) string {
return "Unknown User" // 모든 요청에 대해 동일한 결과 반환
}
// 올바른 코드
func (m MockUserService) GetUser(id int) string {
return m.UserData[id] // 실제 데이터에 따라 결과 반환
}
실수 2: 테스트가 지나치게 복잡해지기
모의 객체를 사용할 때, 복잡한 로직을 테스트하려고 하면 오히려 테스트가 복잡해질 수 있습니다. 가능한 간단하게 유지하세요.