소프트웨어 엔지니어로서 여러분은 아마도 외부 HTTP 서비스와 상호 작용하는 코드 작성에 익숙할 것입니다. 결국 그것은 우리가 하는 가장 일반적인 일 중 하나입니다! 데이터 가져오기, 공급자를 통한 결제 처리, 소셜 미디어 게시물 자동화 등 우리 애플리케이션에는 거의 항상 외부 HTTP 요청이 포함됩니다. 우리 소프트웨어가 안정적이고 유지 관리 가능하도록 하려면 이러한 요청을 실행하고 발생할 수 있는 오류를 처리하는 코드를 테스트하는 방법이 필요합니다. 그러면 몇 가지 옵션이 남습니다.
이러한 옵션은 모두 함께 사용할 수 있다면 나쁘지 않습니다. 하지만 VCR 테스트라는 더 나은 옵션이 있습니다.
비디오카세트 레코더의 이름을 딴 VCR 테스트는 실제 요청에서 테스트 픽스처를 생성하는 모의 테스트 유형입니다. 픽스처는 요청과 응답을 기록하여 향후 테스트에서 자동으로 재사용합니다. 동적 시간 기반 입력을 처리하거나 자격 증명을 제거하기 위해 나중에 픽스처를 수정해야 할 수도 있지만 처음부터 모의를 만드는 것보다 훨씬 간단합니다. VCR 테스트에는 몇 가지 추가 이점이 있습니다.
이제 VCR 테스트의 동기를 확인했으므로 dnaeon/go-vcr을 사용하여 Go에서 이를 구현하는 방법을 더 자세히 살펴보겠습니다.
이 라이브러리는 모든 HTTP 클라이언트 코드에 완벽하게 통합됩니다. 클라이언트 라이브러리 코드가 아직 *http.Client 또는 클라이언트의 http.Transport 설정을 허용하지 않는 경우 지금 추가해야 합니다.
익숙하지 않은 분들을 위해 설명하자면 http.Transport는 기본적으로 요청/응답에 액세스할 수 있는 클라이언트측 미들웨어인 http.RoundTripper의 구현입니다. 500 수준 또는 429(속도 제한) 응답에 대한 자동 재시도를 구현하거나 요청에 대한 지표를 추가하고 로깅하는 데 유용합니다. 이 경우 go-vcr이 자체 프로세스 내 HTTP 서버로 요청을 다시 라우팅할 수 있습니다.
간단한 예부터 시작해 보겠습니다. 무료 https://cleanuri.com API에 요청하는 패키지를 만들고 싶습니다. 이 패키지는 Shorten(string) (string, error)
이라는 하나의 기능을 제공합니다.무료 API이므로 서버에 직접 요청하여 테스트해 볼 수도 있겠죠? 이는 효과가 있을 수 있지만 몇 가지 문제가 발생할 수 있습니다.
그렇습니다. 인터페이스를 만들어 모의하면 어떨까요? 우리 패키지는 믿을 수 없을 정도로 간단하므로 지나치게 복잡해집니다. 우리가 사용하는 가장 낮은 수준은 *http.Client이므로 주위에 새로운 인터페이스를 정의하고 모의를 구현해야 합니다.
또 다른 옵션은 대상 URL을 재정의하여 httptest.Server에서 제공하는 로컬 포트를 사용하는 것입니다. 이는 기본적으로 go-vcr이 수행하는 작업의 단순화된 버전이며 간단한 경우에는 충분하지만 더 복잡한 시나리오에서는 유지 관리가 불가능합니다. 이 예에서도 생성된 픽스처를 관리하는 것이 다양한 모의 서버 구현을 관리하는 것보다 얼마나 쉬운지 확인할 수 있습니다.
인터페이스가 이미 정의되어 있고 https://cleanuri.com에서 UI를 사용해 보면 유효한 입력/출력을 알고 있으므로 테스트 기반 개발을 연습할 수 있는 좋은 기회입니다. Shorten 기능에 대한 간단한 테스트를 구현하는 것부터 시작하겠습니다.
package shortener_test func TestShorten(t *testing.T) { shortened, err := shortener.Shorten("https://dev.to/calvinmclean") if err != nil { t.Errorf("unexpected error: %v", err) } if shortened != "https://cleanuri.com/7nPmQk" { t.Errorf("unexpected result: %v", shortened) } }
아주 쉽습니다! shorter.Shorten이 정의되어 있지 않기 때문에 테스트가 컴파일되지 않을 것이라는 것을 알고 있지만 어쨌든 실행하므로 수정하는 것이 더 만족스러울 것입니다.
마지막으로 다음 기능을 구현해 보겠습니다.
package shortener var DefaultClient = http.DefaultClient const address = "https://cleanuri.com/api/v1/shorten" // Shorten will returned the shortened URL func Shorten(targetURL string) (string, error) { resp, err := DefaultClient.PostForm( address, url.Values{"url": []string{targetURL}}, ) if err != nil { return "", err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("unexpected response code: %d", resp.StatusCode) } var respData struct { ResultURL string `json:"result_url"` } err = json.NewDecoder(resp.Body).Decode(&respData) if err != nil { return "", err } return respData.ResultURL, nil }
이제 테스트가 통과되었습니다! 약속대로 너무 만족스럽습니다.
VCR 사용을 시작하려면 레코더를 초기화하고 테스트 시작 시shorter.DefaultClient를 재정의해야 합니다.
func TestShorten(t *testing.T) { r, err := recorder.New("fixtures/dev.to") if err != nil { t.Fatal(err) } defer func() { require.NoError(t, r.Stop()) }() if r.Mode() != recorder.ModeRecordOnce { t.Fatal("Recorder should be in ModeRecordOnce") } shortener.DefaultClient = r.GetDefaultClient() // ...
테스트를 실행하여 테스트 요청 및 응답에 대한 세부정보가 포함된 Fixtures/dev.to.yaml을 생성합니다. 테스트를 다시 실행하면 서버에 연결하는 대신 기록된 응답을 사용합니다. 내 말만 받아들이지 마세요. 컴퓨터의 WiFi를 끄고 테스트를 다시 실행하세요!
go-vcr이 응답 기간을 기록하고 재생하므로 테스트를 실행하는 데 걸리는 시간이 비교적 일관적이라는 점도 알 수 있습니다. YAML에서 이 필드를 수동으로 수정하여 테스트 속도를 높일 수 있습니다.
이런 종류의 테스트의 이점을 더 자세히 보여주기 위해 또 다른 기능을 추가해 보겠습니다. 속도 제한으로 인해 429 응답 후에 재시도하는 것입니다. API의 속도 제한이 초당이라는 것을 알고 있으므로 Shorten은 자동으로 1초를 기다렸다가 429 응답 코드를 수신하면 다시 시도할 수 있습니다.
이 오류를 API를 사용하여 직접 재현하려고 시도했지만 속도 제한을 고려하기 전에 캐시에서 기존 URL로 응답하는 것 같습니다. 가짜 URL로 캐시를 오염시키는 대신 이번에는 자체 모의 URL을 만들 수 있습니다.
이미 조명기를 생성했으므로 이는 간단한 프로세스입니다. Fixtures/dev.to.yaml을 새 파일에 복사/붙여넣은 후 성공적인 요청/응답 상호 작용을 복제하고 첫 번째 응답의 코드를 200에서 429로 변경합니다. 이 픽스처는 속도 제한 실패 후 성공적인 재시도를 모방합니다.
이 테스트와 원래 테스트의 유일한 차이점은 새 픽스처 파일 이름입니다. Shorten이 오류를 처리해야 하므로 예상되는 출력은 동일합니다. 이는 테스트를 더욱 동적으로 만들기 위해 루프에 테스트를 던질 수 있음을 의미합니다.
func TestShorten(t *testing.T) { fixtures := []string{ "fixtures/dev.to", "fixtures/rate_limit", } for _, fixture := range fixtures { t.Run(fixture, func(t *testing.T) { r, err := recorder.New(fixture) if err != nil { t.Fatal(err) } defer func() { require.NoError(t, r.Stop()) }() if r.Mode() != recorder.ModeRecordOnce { t.Fatal("Recorder should be in ModeRecordOnce") } shortener.DefaultClient = r.GetDefaultClient() shortened, err := shortener.Shorten("https://dev.to/calvinmclean") if err != nil { t.Errorf("unexpected error: %v", err) } if shortened != "https://cleanuri.com/7nPmQk" { t.Errorf("unexpected result: %v", shortened) } }) } }
다시 한번 새로운 테스트가 실패했습니다. 이번에는 처리되지 않은 429 응답으로 인해 테스트를 통과하기 위해 새로운 기능을 구현해 보겠습니다. 단순성을 유지하기 위해 우리 함수는 최대 재시도 및 지수 백오프를 고려하는 복잡성을 처리하는 대신 time.Sleep 및 재귀 호출을 사용하여 오류를 처리합니다.
func Shorten(targetURL string) (string, error) { // ... switch resp.StatusCode { case http.StatusOK: case http.StatusTooManyRequests: time.Sleep(time.Second) return Shorten(targetURL) default: return "", fmt.Errorf("unexpected response code: %d", resp.StatusCode) } // ...
이제 다시 테스트를 실행하여 통과하는지 확인하세요!
한 단계 더 나아가 my-fake-url과 같은 잘못된 URL을 사용할 때 발생하는 잘못된 요청에 대한 테스트를 직접 추가해 보세요.
이 예제의 전체 코드(및 잘못된 요청 테스트)는 Github에서 확인할 수 있습니다.
VCR 테스트의 이점은 이 간단한 예만으로도 분명하지만 요청과 응답이 다루기 힘든 복잡한 애플리케이션을 처리할 때 더욱 효과적입니다. 지루한 모의 작업을 처리하거나 테스트를 전혀 하지 않는 대신 자신의 애플리케이션에서 이 작업을 시도해 보시기 바랍니다. 이미 통합 테스트를 사용하고 있다면 픽스처를 생성할 수 있는 실제 요청이 이미 있으므로 VCR을 시작하는 것이 훨씬 더 쉽습니다.
패키지의 Github 저장소에서 더 많은 문서와 예제를 확인하세요: https://github.com/dnaeon/go-vcr
위 내용은 Go에서 간편한 HTTP 클라이언트 테스트의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!