Go용 로컬 단위 테스트

로컬 단위 테스트는 원격 구성요소에 액세스하지 않고 환경 내에서 실행됩니다. App Engine은 Cloud Datastore 및 기타 App Engine 서비스의 로컬 구현을 사용하는 테스트 유틸리티를 제공합니다.

개발 서비스는 테스트 목적으로 실제 서비스의 동작을 로컬에서 시뮬레이션합니다. 예를 들어, Cloud Datastore 및 Memcache 테스트 작성에 표시된 데이터 저장소를 사용하면 실제 데이터 저장소에 어떠한 요청도 하지 않고 데이터 저장소 코드를 테스트할 수 있습니다. 데이터 저장소 단위 테스트 중에 저장된 모든 항목은 로컬에 저장되고 테스트 실행 후에 삭제됩니다. 데이터 저장소 자체에 어떠한 종속 항목도 없이 소규모 테스트를 빠르게 실행할 수 있습니다.

이 문서는 Go 테스트 패키지를 사용하여 로컬 App Engine 서비스에 대한 단위 테스트를 작성하는 방법을 설명합니다.

Go 테스트 패키지

goapp 도구를 사용하여 Go 패키지의 다운로드, 빌드, 테스트를 자동화할 수 있습니다. goappGo용 App Engine SDK의 일부입니다.

goapp test 명령어와 표준 Go testing 패키지를 조합하여 애플리케이션 코드에 대한 단위 테스트를 실행할 수 있습니다. Go를 사용한 테스트의 배경 정보는 Go 코드 작성 방법의 테스트 섹션과 테스트 패키지 참조 문서를 확인하세요.

단위 테스트는 _test.go 서픽스로 끝나는 파일에 포함되어 있습니다. 예를 들어 *mail.Message를 반환하는 composeNewsletter라는 함수를 테스트한다고 가정해보세요. 다음 newsletter_test.go 파일은 이 함수에 대한 간단한 테스트를 보여줍니다.

package newsletter

import (
	"reflect"
	"testing"

	"google.golang.org/appengine/mail"
)

func TestComposeNewsletter(t *testing.T) {
	want := &mail.Message{
		Sender:  "newsletter@appspot.com",
		To:      []string{"User <user@example.com>"},
		Subject: "Weekly App Engine Update",
		Body:    "Don't forget to test your code!",
	}
	if msg := composeNewsletter(); !reflect.DeepEqual(msg, want) {
		t.Errorf("composeMessage() = %+v, want %+v", msg, want)
	}
}

이 테스트는 패키지 디렉터리 내에서 goapp test 명령어를 사용하여 호출됩니다.

goapp test

goapp 도구는 App Engine SDK의 루트 디렉터리에 있습니다. 보다 간단하게 테스트를 실행할 수 있도록 이 디렉터리를 시스템의 PATH 변수에 넣어 두는 것이 좋습니다.

aetest 패키지

App Engine 서비스에 대한 대부분의 함수 호출에는 인수로 context.Context가 필요합니다. SDK와 함께 제공되는 appengine/aetest 패키지를 사용하면 개발 환경에서 제공되는 서비스를 사용하여 테스트를 실행하기 위해 모의 context.Context를 만들 수 있습니다.

import (
	"testing"

	"google.golang.org/appengine/aetest"
)

func TestWithContext(t *testing.T) {
	ctx, done, err := aetest.NewContext()
	if err != nil {
		t.Fatal(err)
	}
	defer done()

	// Run code and tests requiring the context.Context using ctx.
	// ...
}

aetest.NewContext를 호출하면 테스트 중에 API 호출을 제공하는 데 사용되는 하위 프로세스의 dev_appserver.py가 시작됩니다. 이 하위 프로세스는 done을 호출하면 종료됩니다.

기본 인스턴스를 보다 세부적으로 제어하려면 aetest.NewInstance를 대신 사용할 수 있습니다. 그러면 여러 컨텍스트를 만들어 http.Request 객체와 연결할 수 있습니다.

import (
	"errors"
	"testing"

	"golang.org/x/net/context"

	"google.golang.org/appengine"
	"google.golang.org/appengine/aetest"
	"google.golang.org/appengine/datastore"
)

func TestMyFunction(t *testing.T) {
	inst, err := aetest.NewInstance(nil)
	if err != nil {
		t.Fatalf("Failed to create instance: %v", err)
	}
	defer inst.Close()

	req1, err := inst.NewRequest("GET", "/gophers", nil)
	if err != nil {
		t.Fatalf("Failed to create req1: %v", err)
	}
	c1 := appengine.NewContext(req1)

	req2, err := inst.NewRequest("GET", "/herons", nil)
	if err != nil {
		t.Fatalf("Failed to create req2: %v", err)
	}
	c2 := appengine.NewContext(req2)

	// Run code and tests with *http.Request req1 and req2,
	// and context.Context c1 and c2.
	// ...
}

자세한 내용은 aetest 패키지 참조 문서를 확인하세요.

Cloud Datastore 및 Memcache 테스트 작성

aetest 패키지를 사용하여 context.Context를 만들면 Datastore 또는 Memcache를 사용하는 코드를 간단하게 테스트할 수 있습니다. 테스트에서 aetest.NewContext를 호출하여 테스트 중인 함수로 전달할 컨텍스트를 만들 수 있습니다.

SDK의 transaction 데모 애플리케이션에는 테스트할 수 있도록 코드를 구조화하는 예시와 Datastore를 사용하는 코드 테스트 방법이 있습니다.

func TestWithdrawLowBal(t *testing.T) {
	ctx, done, err := aetest.NewContext()
	if err != nil {
		t.Fatal(err)
	}
	defer done()
	key := datastore.NewKey(ctx, "BankAccount", "", 1, nil)
	if _, err := datastore.Put(ctx, key, &BankAccount{100}); err != nil {
		t.Fatal(err)
	}

	err = withdraw(ctx, "myid", 128, 0)
	if err == nil || err.Error() != "insufficient funds" {
		t.Errorf("Error: %v; want insufficient funds error", err)
	}

	b := BankAccount{}
	if err := datastore.Get(ctx, key, &b); err != nil {
		t.Fatal(err)
	}
	if bal, want := b.Balance, 100; bal != want {
		t.Errorf("Balance %d, want %d", bal, want)
	}
}

이 테스트는 goapp test 명령어를 사용하여 실행할 수 있습니다.

goapp test ./demos/transaction

Memcache에 대한 테스트도 동일한 패턴을 따릅니다. 즉, 테스트에서 Memcache의 초기 상태를 설정하고 테스트 중인 함수를 실행한 후 함수가 예상대로 Memcache를 쿼리하고 수정했는지 확인합니다.