Unit testing allows you to check the quality of your code after you've written it, but you can also use unit testing to improve your development process. Instead of writing tests after you finish developing your application, consider writing the tests as you write the app. This helps you design small, maintainable, reusable units of code. It also makes it easier for you to test your code thoroughly and quickly.
When you do local unit testing, you run tests that stay inside your own development environment without involving remote components. App Engine provides testing utilities that use local implementations of datastore and other App Engine services . This means you can exercise your code's use of these services locally, without deploying your code to App Engine.
The development services simulate the behaviour of the real service locally for testing. For example, the datastore usage shown in Writing Datastore and Memcache Tests allows you to test your datastore code without making any requests to the real datastore. Any entity stored during a datastore unit test is stored locally and is deleted after the test run. You can run small, fast tests without any dependency on the datastore itself.
This document describes how to write unit tests against local App Engine services using the Go testing package.
- Introducing the Go testing package
-
Introducing the
aetest
package - Writing Datastore and memcache tests
Introducing the Go testing package
The Go App Engine SDK comes bundled with
goapp
, which automates the
downloading, building, and testing of Go packages: it is the App
Engine-equivalent of the
standard go
tool
.
In particular, the combination of the
goapp test
command and the standard Go
testing
package can be used to run unit tests against your application code.
For a background on testing with Go, see the Testing section of
How to Write
Go Code
and the
testing package
reference
.
Unit tests are contained in files ending with the suffix
_test.go
. For
example, suppose you want to test a function named
composeNewsletter
which
returns a
*mail.Message
. The following
newsletter_test.go
file shows
a simple test for that function:
package newsletter
import (
"reflect"
"testing"
"appengine/mail"
)
func TestComposeNewsletter(t *testing.T) {
want := &mail.Message{
Sender: "[email protected]",
To: []string{"User <[email protected]>"},
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)
}
}
This test would be invoked using the
goapp test
command from within the
package's directory:
goapp test
The
goapp
tool is found in the root directory of the App Engine SDK. We
recommend putting this directory in your system's
PATH
variable to make
running tests simpler.
Introducing the
aetest
package
Many function calls to App Engine services require an
appengine.Context
as an argument. The
appengine/aetest
package provided with the SDK
allows you to create a fake
appengine.Context
to run your tests using the
services provided in the development environment.
import (
"testing"
"appengine/aetest"
)
func TestMyFunction(t *testing.T) {
c, err := aetest.NewContext(nil)
if err != nil {
t.Fatal(err)
}
defer c.Close()
// Run code and tests requiring the appengine.Context using c.
}
The call to
aetest.NewContext
will start
dev_appserver.py
in a subprocess,
which will be used to service API calls during the test. This subprocess will be
shutdown with the call to
Close
.
Writing Datastore and memcache tests
Testing code which uses the datastore or memcache is simple once you create an
appengine.Context
with the
aetest
package: in your test call
aetest.NewContext
to create a context to pass to the function under test.
The
transaction
demo application in the SDK has an example of structuring the
code to allow testability, and how to test code which uses the datastore:
func TestWithdrawLowBal(t *testing.T) {
c, err := aetest.NewContext(nil)
if err != nil {
t.Fatal(err)
}
defer c.Close()
key := datastore.NewKey(c, "BankAccount", "", 1, nil)
if _, err := datastore.Put(c, key, &BankAccount{100}); err != nil {
t.Fatal(err)
}
err = withdraw(c, "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(c, key, &b); err != nil {
t.Fatal(err)
}
if bal, want := b.Balance, 100; bal != want {
t.Errorf("Balance %d, want %d", bal, want)
}
}
This test can be run using the
goapp test
command:
goapp test ./demos/transaction
Tests for memcache follow the same pattern: set up the intial state of the memcache in your test, run the function being tested, and verify the function has queried/modified the memcache in the way you expect.