As important as programming new features is ensuring that nothing is broken with each change; fortunately, Go includes everything necessary to run tests quickly and conveniently.
If you’re new to the world of Go, I recommend the following previous articles:
To achieve reproducible builds, Go recommends using similar versions of Go. To do this, we can either manually generate the go.mod file or initialize a module, and Go will generate it for us:
cd go_testing/
go mod init name
We can view the contents of the go.mod file by running:
module name
go 1.20
Reproducible Build:
Including a go.mod file in your project can contribute to reproducible builds. This means that, when sharing your code with other developers or running tests in different environments, the same versions of dependencies will be used, helping to avoid compatibility issues.
As an example, we will simply have a function called Hello. If it receives a non-empty string as input, it will return [string, nil]
. If an empty input is provided, 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)
}
}
We create the unit test that will verify that calling the Hello() function returns the correct value:
package main
import (
"testing"
"regexp"
)
// Test Hello() function with input string:
func TestHelloName(t *testing.T) {
name := "Kr0m" // Function input
want := regexp.MustCompile(`\b`+name+`\b`) // What we expect
msg, err := Hello(name) // Save Hello() returned values
// Compare want with msg OR an error has ocurred:
if !want.MatchString(msg) || err != nil {
t.Fatalf(`Hello(name) = %q, %v, want match for %#q, nil`, msg, err, want)
}
}
// Test Hello() function with empty string:
func TestHelloEmpty(t *testing.T) {
msg, err := Hello("") // Save Hello() returned values
// If msg is not empty string OR err NULL:
if msg != "" || err == nil {
t.Fatalf(`Hello("") = %q, %v, want "", error`, msg, err)
}
}
We pass the tests without any issues:
PASS
ok name 0.002s
We introduce a bug, causing the function to 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 pass the tests again:
We can see that this time it has failed:
--- FAIL: TestHelloName (0.00s)
name_test.go:15: Hello(name) = "AAAA", <nil>, want match for `\bKr0m\b`, nil
FAIL
exit status 1
FAIL name 0.002s
Line 15 at name_test:
if !want.MatchString(msg) || err != nil {
t.Fatalf(`Hello(name) = %q, %v, want match for %#q, nil`, msg, err, want)
}
Indeed, want
doesn’t match msg
:
name := "Kr0m"
want := regexp.MustCompile(`\b`+name+`\b`)
// msg content: AAAA