Teste de unidade local para Go

Testes de unidades locais são executados dentro do ambiente, sem acessar componentes remotos. O App Engine fornece utilitários para testes que usam implementações locais do Datastore e outros serviços do App Engine.

Os serviços de desenvolvimento simulam o comportamento do serviço real localmente para testes. Por exemplo, o uso do armazenamento de dados mostrado em Como escrever testes de Cloud Datastore e Memcache permite que você teste o código de armazenamento de dados sem fazer solicitações para o armazenamento de dados real. O armazenamento de qualquer entidade durante um teste de unidade de armazenamento de dados é feito localmente e excluído após a realização do teste. É possível executar testes pequenos e rápidos sem qualquer dependência do armazenamento de dados propriamente dito.

Este documento mostra como escrever testes de unidade em serviços locais do App Engine usando o pacote de testes Go.

O pacote de testes Go

É possível automatizar o download, a criação e o teste de pacotes em Go usando a ferramenta goapp. goapp faz parte do SDK do App Engine para Go.

A combinação do comando goapp test e do pacote Go testing padrão pode ser usada para executar testes de unidade no código do aplicativo. Para mais informações sobre testes com Go, consulte a seção "Testing" em Como gravar o código Go e a referência do pacote de teste (páginas em inglês).

Os testes de unidade estão em arquivos que terminam com o sufixo _test.go. Por exemplo, suponha que você queira testar uma função chamada composeNewsletter, que retorna um *mail.Message. O arquivo newsletter_test.go a seguir mostra um teste simples para essa função:

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)
	}
}

Esse teste seria invocado usando o comando goapp test do diretório do pacote:

goapp test

A ferramenta goapp se encontra no diretório raiz do SDK do App Engine. Recomendamos colocar esse diretório na variável PATH do sistema para simplificar a execução de testes.

O pacote aetest

Muitas chamadas de função para serviços do App Engine exigem um context.Context como argumento. O pacote appengine/aetest fornecido com o SDK permite que você crie um context.Context falso para executar testes usando os serviços fornecidos no ambiente para desenvolvedores.

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.
	// ...
}

A chamada para aetest.NewContext iniciará dev_appserver.py em um subprocesso, que será usado para atender chamadas de API durante o teste. O subprocesso será encerrado com a chamada para done.

Para ter mais controle sobre a instância subjacente, use aetest.NewInstance. Isso permite criar vários contextos e associá-los a objetos 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.
	// ...
}

Para mais informações, leia a referência do pacote aetest.

Como escrever testes do Cloud Datastore e memcache

É simples testar código que usa o armazenamento de dados ou o Memcache depois de criar um context.Context com o pacote aetest: na chamada de teste aetest.NewContext, para criar um contexto a ser passado para a função em teste.

O aplicativo de demonstração transaction no SDK apresenta um exemplo de como estruturar o código para permitir a capacidade de teste e como testar o código que usa o armazenamento de dados:

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)
	}
}

Este teste pode ser executado usando o comando goapp test:

goapp test ./demos/transaction

Os testes para Memcache seguem o mesmo padrão: configure o estado inicial do Memcache no teste, execute a função que está sendo testada e verifique se ela consultou/modificou o Memcache da maneira que você esperava.