2024-05-06 20:40:05 +00:00
|
|
|
---
|
2024-10-30 17:04:36 +00:00
|
|
|
date: 20200826
|
2024-05-06 20:40:05 +00:00
|
|
|
id: d8edb970-fe2c-4774-b325-2f932b262ef5
|
|
|
|
title: 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\`
|
|
|
|
|
|
|
|
``` go
|
|
|
|
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.
|
|
|
|
|
|
|
|
``` go
|
|
|
|
func ExampleAdd() {
|
|
|
|
sum := Add(1, 5)
|
|
|
|
fmt.Println(sum)
|
|
|
|
// Output: 6
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Example function will not be execute if the comment is removed
|
|
|
|
|
|
|
|
# Benchmarking
|
|
|
|
|
|
|
|
[Benchmarks](https://golang.org/pkg/testing/#hdr-Benchmarks) are a
|
|
|
|
first-class feature of Go, fantastic stuff!
|
|
|
|
|
|
|
|
``` go
|
|
|
|
func BenchmarkRepeat(b *testing.B) {
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
Repeat("a")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Following command runs benchmarks:
|
|
|
|
|
|
|
|
``` shell
|
|
|
|
go test -bench=.
|
|
|
|
```
|
|
|
|
|
|
|
|
# Tools
|
|
|
|
|
|
|
|
## Coverage
|
|
|
|
|
|
|
|
[Coverage](https://blog.golang.org/cover) is built in as well:
|
|
|
|
|
|
|
|
``` shell
|
|
|
|
go test -cover
|
|
|
|
```
|
|
|
|
|
|
|
|
## Race conditions
|
|
|
|
|
|
|
|
In Go you can detect race conditions by adding the `-race` argument:
|
|
|
|
|
|
|
|
``` shell
|
|
|
|
go test -race
|
|
|
|
```
|
|
|
|
|
|
|
|
# DeepEqual
|
|
|
|
|
|
|
|
For \`slices\` & friends you can use \`reflect.DeepEqual\` to compare
|
|
|
|
variables in tests
|
|
|
|
|
|
|
|
``` go
|
|
|
|
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](https://github.com/golang/go/wiki/TableDrivenTests): 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.
|
|
|
|
|
|
|
|
``` go
|
|
|
|
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:
|
|
|
|
|
|
|
|
``` go
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
# Handy packages
|
|
|
|
|
|
|
|
- httptest[^1]
|
|
|
|
- quick[^2]
|
|
|
|
|
|
|
|
# Footnotes
|
|
|
|
|
|
|
|
[^1]: <https://golang.org/pkg/net/http/httptest/>
|
|
|
|
|
|
|
|
[^2]: <https://golang.org/pkg/testing/quick/>
|