Just as important as programming new features is ensuring that nothing breaks with each change. Fortunately, Go includes everything necessary to run tests quickly and conveniently.
If you are new to the world of Go, I recommend the following previous articles:
To achieve a reproducible build, Go recommends using similar versions of Go. For this, we can generate the go.mod
file manually or initialize a module to have Go generate it for us:
cd go_testing/
go mod init name
We can view the contents of the go.mod
file:
module name
go 1.21.12
Reproducible Build:
Including a go.mod file in your project can contribute to a reproducible build. This means that when sharing your code with
other developers or running tests in different environments, the same versions of dependencies will be used, which can help
avoid compatibility issues.
As an example, we will simply have a function called Hello that, if it receives an input string, will return [string,nil]
. If it receives an empty input, it will return ["",ERROR]
.
package main
import (
"errors"
"fmt"
)
func hello(name string) (string, error) {
if name == "" {
return name, errors.New("Empty name")
}
return name, nil
}
func main() {
name, error := hello("Kr0m")
if error != nil {
fmt.Println("ERROR:", error)
} else {
fmt.Println("Name:", name)
}
}
All test files must follow the pattern XXX_test.go, the testing package must be imported, and the functions must start with Test.
We create the unit test which will check that the hello() function returns a correct value,
package main
import (
"testing"
)
// Test hello() function with input string:
func TestHelloName(t *testing.T) {
// Function input
name := "Kr0m"
// What we expect
want := "Kr0m"
// Save hello() returned values
msg, err := hello(name)
// Compare want with msg OR an error has occurred:
if msg != want || err != nil {
t.Fatalf(`hello(name) = %v, %v, want match for %v, nil`, msg, err, want)
}
}
// Test hello() function with empty string:
func TestHelloEmpty(t *testing.T) {
// Save hello() returned values
msg, err := hello("")
// If msg is not empty string OR err NULL:
if msg != "" || err == nil {
t.Fatalf(`hello("") = %v, %v, want "", error`, msg, err)
}
}
We pass the tests without issues:
PASS
ok name 0.002s
If we add the -cover
parameter, we can see the percentage of code that our tests cover:
PASS
coverage: 42.9% of statements
ok name 0.002s
We introduce a bug, making the function always return [AAAA,nil]
:
package main
import (
"errors"
"fmt"
)
func hello(name string) (string, error) {
if name == "" {
return name, errors.New("Empty name")
}
name = "AAAA" // BUG
return name, nil
}
func main() {
name, error := hello("Kr0m")
if error != nil {
fmt.Println("ERROR:", error)
} else {
fmt.Println("Name:", name)
}
}
We run the tests again:
We can see that this time it failed:
--- FAIL: TestHelloName (0.00s)
name_test.go:17: hello(name) = AAAA, <nil>, want match for Kr0m, nil
FAIL
exit status 1
FAIL name 0.002s
Line 17 of name_test
:
if msg != want || err != nil {
t.Fatalf(`hello(name) = %v, %v, want match for %v, nil`, msg, err, want)
}
Indeed, want
does not match msg
:
name := "Kr0m"
want := "Kr0m"
// msg -> AAAA