Go에서 간편한 HTTP 클라이언트 테스트

王林
풀어 주다: 2024-07-17 12:24:28
원래의
1183명이 탐색했습니다.

Effortless HTTP Client Testing in Go

소개

소프트웨어 엔지니어로서 여러분은 아마도 외부 HTTP 서비스와 상호 작용하는 코드 작성에 익숙할 것입니다. 결국 그것은 우리가 하는 가장 일반적인 일 중 하나입니다! 데이터 가져오기, 공급자를 통한 결제 처리, 소셜 미디어 게시물 자동화 등 우리 애플리케이션에는 거의 항상 외부 HTTP 요청이 포함됩니다. 우리 소프트웨어가 안정적이고 유지 관리 가능하도록 하려면 이러한 요청을 실행하고 발생할 수 있는 오류를 처리하는 코드를 테스트하는 방법이 필요합니다. 그러면 몇 가지 옵션이 남습니다.

  • 메인 애플리케이션 코드로 조롱할 수 있는 클라이언트 래퍼를 구현하지만 여전히 테스트에 공백이 남아 있습니다
  • 실제 요청 실행과 별도로 테스트 응답 구문 분석 및 처리. 이 하위 레벨 유닛을 개별적으로 테스트하는 것도 좋은 생각일 수 있지만 실제 요청과 함께 쉽게 처리할 수 있으면 좋을 것 같습니다
  • 개발 속도를 늦출 수 있고 일부 오류 시나리오를 테스트할 수 없으며 다른 서비스의 안정성에 영향을 받을 수 있는 통합 테스트로 테스트를 이동하세요

이러한 옵션은 모두 함께 사용할 수 있다면 나쁘지 않습니다. 하지만 VCR 테스트라는 더 나은 옵션이 있습니다.

비디오카세트 레코더의 이름을 딴 VCR 테스트는 실제 요청에서 테스트 픽스처를 생성하는 모의 테스트 유형입니다. 픽스처는 요청과 응답을 기록하여 향후 테스트에서 자동으로 재사용합니다. 동적 시간 기반 입력을 처리하거나 자격 증명을 제거하기 위해 나중에 픽스처를 수정해야 할 수도 있지만 처음부터 모의를 만드는 것보다 훨씬 간단합니다. VCR 테스트에는 몇 가지 추가 이점이 있습니다.

  • HTTP 수준까지 코드를 실행하여 애플리케이션을 엔드 투 엔드로 테스트할 수 있습니다
  • 실제 응답을 취하고 생성된 픽스처를 수정하여 응답 시간을 늘리고 속도 제한을 유발하는 등의 현상을 발생시켜 유기적으로 자주 발생하지 않는 오류 시나리오를 테스트할 수 있습니다
  • 코드가 API와 상호작용하기 위해 외부 패키지/라이브러리를 사용하는 경우 요청과 응답이 어떻게 생겼는지 정확히 알 수 없으므로 VCR 테스트에서 자동으로 파악합니다.
  • 생성된 픽스처는 테스트 디버깅 및 코드가 예상 요청을 실행하는지 확인하는 데에도 사용할 수 있습니다

Go를 사용한 심층 분석

이제 VCR 테스트의 동기를 확인했으므로 dnaeon/go-vcr을 사용하여 Go에서 이를 구현하는 방법을 더 자세히 살펴보겠습니다.

이 라이브러리는 모든 HTTP 클라이언트 코드에 완벽하게 통합됩니다. 클라이언트 라이브러리 코드가 아직 *http.Client 또는 클라이언트의 http.Transport 설정을 허용하지 않는 경우 지금 추가해야 합니다.

익숙하지 않은 분들을 위해 설명하자면 http.Transport는 기본적으로 요청/응답에 액세스할 수 있는 클라이언트측 미들웨어인 http.RoundTripper의 구현입니다. 500 수준 또는 429(속도 제한) 응답에 대한 자동 재시도를 구현하거나 요청에 대한 지표를 추가하고 로깅하는 데 유용합니다. 이 경우 go-vcr이 자체 프로세스 내 HTTP 서버로 요청을 다시 라우팅할 수 있습니다.

URL 단축기 예

간단한 예부터 시작해 보겠습니다. 무료 https://cleanuri.com API에 요청하는 패키지를 만들고 싶습니다. 이 패키지는 Shorten(string) (string, error)

이라는 하나의 기능을 제공합니다.

무료 API이므로 서버에 직접 요청하여 테스트해 볼 수도 있겠죠? 이는 효과가 있을 수 있지만 몇 가지 문제가 발생할 수 있습니다.

  • 서버의 속도 제한은 초당 2개 요청이며, 테스트가 많으면 문제가 될 수 있습니다
  • 서버가 다운되거나 응답하는 데 시간이 오래 걸리면 테스트가 실패할 수 있습니다
  • 단축 URL이 캐시되기는 하지만 매번 동일한 결과가 나올 것이라는 보장은 없습니다
  • 무료 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

원천:dev.to
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!