4.5 KiB
date | id | title |
---|---|---|
2020-08-26 | d8edb970-fe2c-4774-b325-2f932b262ef5 | Tests in Golang |
Rules
Test writing rules:
- It needs to be in a file with a name like `xxx
test.go` - The test funciton must start with the word `Test`
- The test function takes one argument only `t *testing.T`
package main
import "testing"
func TestHello(t *testing.T) {
assertCorrectMessage := func(t *testing.T, got, want string) {
t.Helper()
if got != want {
t.Errorf("got %q want %q", got, want)
}
}
t.Run("saying hello to people", func(t *testing.T) {
got := Hello("Chris", "")
want := "Hello, Chris"
assertCorrectMessage(t, got, want)
})
t.Run("empty string defaults to 'World'", func(t *testing.T) {
got := Hello("", "")
want := "Hello, World"
assertCorrectMessage(t, got, want)
})
t.Run("in Spanish", func(t *testing.T) {
got := Hello("Elodie", "Spanish")
want := "Hola, Elodie"
assertCorrectMessage(t, got, want)
})
t.Run("in French", func(t *testing.T) {
got := Hello("Jean Pierre", "French")
want := "Bonjour, Jean Pierre"
assertCorrectMessage(t, got, want)
})
t.Run("in Dutch", func(t *testing.T) {
got := Hello("Frans", "Dutch")
want := "Hoi, Frans"
assertCorrectMessage(t, got, want)
})
}
Examples
Examples can also be added to test.go files.
func ExampleAdd() {
sum := Add(1, 5)
fmt.Println(sum)
// Output: 6
}
Example function will not be execute if the comment is removed
Benchmarking
Benchmarks are a first-class feature of Go, fantastic stuff!
func BenchmarkRepeat(b *testing.B) {
for i := 0; i < b.N; i++ {
Repeat("a")
}
}
Following command runs benchmarks:
go test -bench=.
Tools
Coverage
Coverage is built in as well:
go test -cover
Race conditions
In Go you can detect race conditions by adding the -race
argument:
go test -race
DeepEqual
For `slices` & friends you can use `reflect.DeepEqual` to compare variables in tests
func TestSumAll(t *testing.T) {
got := SumAll([]int{1, 2}, []int{0, 9})
want := []int{3, 9}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
}
TableDrivenTests
Writing good tests is not trivial, but in many situations a lot of ground can be covered with table-driven tests: Each table entry is a complete test case with inputs and expected results, and sometimes with additional information such as a test name to make the test output easily readable. If you ever find yourself using copy and paste when writing a test, think about whether refactoring into a table-driven test or pulling the copied code out into a helper function might be a better option.
Given a table of test cases, the actual test simply iterates through all table entries and for each entry performs the necessary tests. The test code is written once and amortized over all table entries, so it makes sense to write a careful test with good error messages.
var flagtests = []struct {
in string
out string
}{
{"%a", "[%a]"},
{"%-a", "[%-a]"},
{"%+a", "[%+a]"},
{"%#a", "[%#a]"},
{"% a", "[% a]"},
{"%0a", "[%0a]"},
{"%1.2a", "[%1.2a]"},
{"%-1.2a", "[%-1.2a]"},
{"%+1.2a", "[%+1.2a]"},
{"%-+1.2a", "[%+-1.2a]"},
{"%-+1.2abc", "[%+-1.2a]bc"},
{"%-1.2abc", "[%-1.2a]bc"},
}
func TestFlagParser(t *testing.T) {
var flagprinter flagPrinter
for _, tt := range flagtests {
t.Run(tt.in, func(t *testing.T) {
s := Sprintf(tt.in, &flagprinter)
if s != tt.out {
t.Errorf("got %q, want %q", s, tt.out)
}
})
}
}
Fatals
Sometimes you want to throw fatal errors in tests to prevent problems, for example in case `nil` is returned and you need to do stuff with the return value in later tests:
func assertError(t *testing.T, got error, want error) {
t.Helper()
if got == nil {
t.Fatal("didn't get an error but wanted one")
}
if got != want {
t.Errorf("got %q, want %q", got, want)
}
}