Martin Sulzmann
Nobody likes to write tests! But tests are necessary. The purpose of testing. Find bugs!
We take a look at the following topics.
“Classic” methods:
User tests
Unit tests
“Clever” methods (such as QuickCheck):
Type-specific test (input) data generation
Property-based testing
// count.go
package main
import "fmt"
// Counting words in a string.
// A word consists of a sequence of bytes separated by blanks (' ').
func skip(p func(byte) bool) func(string) string {
return func(s string) string {
switch {
case len(s) == 0:
return ""
case p(s[0]):
return skip(p)(s[1:])
default:
return s
}
}
}
func count(s string) int {
skipBlanks := skip(func(b byte) bool {
return b == ' '
})
skipWord := skip(func(b byte) bool {
return b != ' '
})
switch {
case len(s) == 0:
return 0
case s[0] == ' ':
return count(skipBlanks(s))
default:
return 1 + count(skipWord(s))
}
}
func main() {
// Some user tests
fmt.Printf("\n%d",count(""))
fmt.Printf("\n%d",count("Hi df looo"))
}// count_test.go
package main
import "testing"
// Go testing package
func TestCount(t *testing.T) {
var tests = []struct {
input string
expected int
}{
{"", 0},
{" ", 0},
{"Hi", 1},
{"Hi Ho ", 2},
}
for _, x := range tests {
n := count(x.input)
if x.expected != n {
t.Errorf("got %d, expected %d", n, x.expected)
}
}
}Place above files in some directory (for example with the name
count).
Run go mod init count.go
Run go test to execute your tests
Writing tests by hand is painful!
How many test cases are enough?
Need to make sure that expected results are correct!
The QuickCheck idea:
Type-specific test case generation to automate the process of generating test inputs.
Property-based testing to automate the process of checking if the “test unit” behaves correctly.
These ideas have been around for a while. Made popular through the QuickCheck Haskell library. QuickCheck has been adopted to many other languages besides Haskell. See QuickCheck for other languages.
type Gen[T any] interface {
generate() T
}
func quickCheck[T any](g Gen[T], p func(T) bool) {
n := 100
for i := 0; i < n; i++ {
if p(g.generate()) {
fmt.Println("ok")
} else {
fmt.Println("fail")
}
}
}Type-specific generator matches the property
Properties are functions from input value to bool
type Ch byte
func (x Ch) generate() byte {
return (byte)(rand.Intn(255))
}
type S string
func (x S) generate() string {
n := rand.Intn(15)
var s string
var ch Ch
for i := 0; i < n; i++ {
s = s + string(ch.generate())
}
return s
}Generator for strings relies on a generator for byte.
User tests
Unit tests
QuickCheck
None of the above is meant to replace the other.
A wide range of testing methods are required.