-
Notifications
You must be signed in to change notification settings - Fork 387
Testing
This document describes testing guidelines for Storj. Occasionally, code may diverge from this guide, however it should be a significant reason for the alternate behavior.
If the guide is not followed then CI may fail.
Refer to Style for information about the style.
All errors must be handled and checked and don't hide errors.
See Error Handling for more details.
By using different package name we are testing the exposed behavior rather than the internal behavior making the tests more robust against changes.
Use log := zaptest.NewLogger(t)
as the root logger for services. Use t.Log
, if you need single logging.
Using fmt.Print
or other global loggers bypass built-in logging behavior in testing
package. This means that when running tests in parallel logs can appear in the wrong places.
In tests we should check all closing errors and check that everything gets closed properly. The program and any test should shut-down cleanly without any leaks.
Following are the common resources that are leaked:
- Files
- Database Connections
- Connections
- Servers
- Goroutines
internal/testcontext
package contains useful utilities for it:
func TestBasic(t *testing.T) {
ctx := testcontext.New(t) // we create a context with the specific test
defer ctx.Cleanup() // this waits for all goroutines to terminate
db, err := CreateDatabase(ctx.File("example.db"))
if err != nil {
t.Fatal(err)
}
defer ctx.Check(db.Close)
server := NewServer(db)
defer ctx.Check(server.Stop)
ctx.Go(func() error {
return server.Run(ctx)
})
}
When server.Stop
is not called then this func will stall with a message: "some goroutines are still running...".
https://github.com/loov/leakcheck can be used to detect open files or connections either in tests or the final binaries.
Use real dependencies that are used in production. Try to create the appropriate data using the exiting API-s rather than creating them manually.
Avoid using mocks and stub-data, if possible. By using mocks we are not testing the actual system and how the systems work together; hence missing some bugs.
Package internal/testplanet
helps to setup a full environment that can be used for testing. For the basic examples see internal/testplanet/planet_test.go. Of course many packages already use it, so refer them for more examples.
For testing erroring behavior a wrapper can be used. For example:
// assume we want to test this interface
type Server interface { Do(ctx context.Context) error }
// we can write a type that has all the same methods but returns an error
type erroringServer struct { Server }
func (_ erroringServer) Do(ctx context.Context) error { return errors.New("invalid request") }
Note: there are places where using mocks are unavoidable or have a significant benefit.
Prefer table-driven tests when you have multiple similar tests. Try to keep the table as simple as possible. Needing a function closure often indicates a table has become too complicated. Separating erroring and non-erroring examples makes the code easier to read.
Example:
func TestNormalizeEmail(t *testing.T) {
type test struct {
input string
expected string
}
validEmails := []test{
{"alpha@example.com", "alpha@example.com",
{"aLPha+bETa@example.com", "alpha@example.com",
...
}
for i, tt := range validEmails {
errTag := fmt.Sprintf("%d. %+v", i, tt)
normalized, err := normalizeEmail(tt.input)
if assert.NoError(t, err, errTag) {
assert.Equal(t, tt.expected, normalized, errTag)
}
}
}
func TestNormalizeEmail_Invalid(t *testing.T) {
invalidEmails := []string{
"@example.com",
...
}
for i, tt := range invalidEmails {
errTag := fmt.Sprintf("%d. %+v", i, tt)
_, err := normalizeEmail(tt)
assert.Error(t, err, errTag)
}
}
Tests should only use automatic port selection when starting servers. Use net.Listen("127.0.0.1:0")
for this.
Developers usually have many things running on their own system, so eventually we will use a port that is already in use and cause a failure. Similarly this prevents the test being tested in parallel. Using "127.0.0.1:0"
is necessary for Windows users, because using ":0"
causes firewall notices when running tests.
Preassigned addresses also interfere with running all tests in parallel.
CI environment blocks attempts to use fixed port numbers.
Tests should only create data in temp directory. The created data must be cleaned-up.
By creating things inside source directory we risk the data being committed or accumulating in the source directory. By not cleaning up the temp directory we may end up creating gigabytes of data there.
internal/testcontext
package contains useful utilities for it:
func TestBasic(t *testing.T) {
ctx := testcontext.New(t) // we create a context with the specific test
defer ctx.Cleanup() // this deletes all created directories
t.Log(ctx.Dir("a", "b", "c")) // create a directory inside a temporary directory
t.Log(ctx.File("a", "w", "c.txt")) // get a filename inside a temporary directory
}
Use only domains intended for testing and examples, otherwise tests may end up accidentally sending an email to an actual person.
For testing or documentation use .test
domain, it is reserved for that purpose. Examples: mail.test
, example.test
.
For documentation example.com
can also be used.
Part of our current implementation uses different database backends, some of them are used to run the test in local without having to always depend of third party external systems when developing.
Because using a different database backend for development than in production can cause that some tests pass in local meanwhile fail using the production database backend, the CI runs all the tests with all the supported backends or at least with the one used in production.
Sometimes, meanwhile developing, it's less than ideal that for having feedback of each change the developer must push the code to run the CI, making pretty convenient to run the tests which the production databse backend. Currently this is the case for PostgreSQL in:
- Database migrations.
- Test SIM (i.e.
make test-sim
).
In order to use PostgreSQL1 for running the tests in a local development machine you have to setup and run PostgreSQL v9.6 in your machine or run a Docker container using a PostgreSQL image and run the tests as follow:
- For Go tests, use the environment variable
STORJ_TEST_POSTGRES
to specify a connection URL (i.e.postgres://[user][:password]@[host]?sslmode=disable
) when running them, for exampleSTORJ_TEST_POSTGRES="postgres://postgres:pass@localhost?sslmode=disable" go test ./...
- For test sim use the environment variable
STORJ_SIM_POSTGRES
to specify a connection URL with an existing database (preferably an empty one) (i.epostgres://[user][:password]@[host]/[database]?sslmode=disable
) when running them, for exampleSTORJ_SIM_POSTGRES="postgres://postgres:pass@localhost/teststorj?sslmode=disable" make test-sim
. For creating an empty database in Postgres, you can easily do with it's Postgres client running for examplepsql -U postgres -c 'create database teststorj;'
for creating a database namedteststorj
(you can also use the Docker image commented above rather than the locally installed Postgres clientpsql
).
1PostgreSQL is currently required to run some Go tests.