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 後置字串的檔案中。舉例來說,假設您要測試名為 composeNewsletter 的函式,該函式會傳回 *mail.Message。下列 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 的呼叫會在子程序中啟動 dev_appserver.py,用於在測試期間處理 API 呼叫。這個子程序會隨著對 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 測試

使用資料儲存庫或 memcache 測試程式碼是很簡單的,特別是當您使用 aetest 套件建立了 context.Context 時:在測試呼叫 aetest.NewContext 中建立 context,以傳送至測試之下的函式。

SDK 中的 transaction 示範應用程式提供程式碼結構範例,可供測試,並說明如何測試使用資料儲存區的程式碼:

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。