매니저/폴백 시나리오 테스트 패턴
개요
인증 토큰 삭제처럼 primary 매니저 + fallback 경로 구조를 가진 코드는, 버그가 보통 "정상 경로"가 아니라 폴백이 동작할 때 발생한다. 두 경로를 각각 독립 시나리오로 테스트하는 것이 핵심.
왜 필요한가
- primary 매니저가 존재할 때는 정상 동작 — 테스트 통과
- 매니저가 nil이거나 실패했을 때의 폴백 분기 — 테스트 커버리지 밖인 경우가 많음
- 폴백에서 잘못된 경로 참조·nil 역참조 같은 버그가 남아있어도 기존 테스트는 이를 못 잡는다
예시 (Go)
수정 전 — nil에 대한 방어 없음
func deleteAuthFile(mgr *Manager) error {
return mgr.Delete() // mgr이 nil이면 패닉
}
수정 후 — nil 체크 + 폴백 분기 명시
func deleteAuthFile(mgr *Manager, fallbackPath string) error {
if mgr != nil {
return mgr.Delete()
}
return os.Remove(fallbackPath)
}
테스트 — 시나리오 분리
func TestDeleteAuthFile(t *testing.T) {
t.Run("with manager", func(t *testing.T) {
mgr := newMockManager(t)
err := deleteAuthFile(mgr, "")
assert.NoError(t, err)
mgr.AssertDeleted(t)
})
t.Run("fallback without manager", func(t *testing.T) {
tmpFile := createTempAuthFile(t)
err := deleteAuthFile(nil, tmpFile)
assert.NoError(t, err)
assert.NoFileExists(t, tmpFile)
})
t.Run("fallback file missing", func(t *testing.T) {
err := deleteAuthFile(nil, "/nonexistent/path")
assert.ErrorIs(t, err, os.ErrNotExist)
})
}
핵심 원칙
- 폴백 경로는 항상 별도 테스트 케이스로 분리한다.
- 의존성(매니저·클라이언트)이 nil일 때 어떻게 동작하는지 명시적으로 검증한다.
- 외부 상태(파일 존재 여부 등)가 달라지는 케이스도 각각 커버한다.
- 버그 수정 후에는 해당 버그를 재현하는 회귀 테스트를 같이 커밋한다.