This example shows several of the types of checks this library is designed to assist with. Suppose we have the following function:
cat ./large_example_function/example.go// Package example demonstrates a larger test function.
package example
import (
"fmt"
"log"
)
// Process returns known changes to its parameters for testing.
func Process(factor int, msg string) (int, string, float64) {
const mulFactor = 2
const aThird = 1.0 / 3.0
// Function being tested.
log.Printf("Entered process(%d, %q)", factor, msg)
if factor < 1 || factor > 10 {
log.Panicf("factor (%d) out of bounds: %q", factor, msg)
}
fmt.Println("Processing with factor:", factor, "and message:", msg)
return factor * mulFactor, "Processed: " + msg, float64(factor) * aThird
}we could test all of the expected outputs with the following test (with passing and failing version)s
cat ./large_example_function/example_test.gopackage example
import (
"testing"
"github.com/dancsecs/sztest"
)
// Passing test.
func Test_PASS_GeneralForm(t *testing.T) {
chk := sztest.CaptureLogAndStdout(t)
defer chk.Release()
chk.FailFast(false) // Don't exit test on first failure.
// Test panic condition.
chk.Panic(
func() {
Process(0, "failing message")
},
"factor (0) out of bounds: \"failing message\"",
)
// Test valid operation.
gotInt, gotStr, gotFloat := Process(2, "Hello")
chk.Int(gotInt, 4)
chk.Str(gotStr, "Processed: Hello")
chk.Float64(gotFloat, 0.6666, 0.0005) // Tolerance (± 0.0005)
// Check output.
chk.Stdout(
"Processing with factor: 2 and message: Hello",
)
// Check logging.
chk.Log(chk.TrimAll(`
Entered process(0, "failing message")
factor (0) out of bounds: "failing message"
Entered process(2, "Hello")
`))
}
// Failing test.
func Test_FAIL_GeneralForm(t *testing.T) {
chk := sztest.CaptureLogAndStdout(t)
defer chk.Release()
chk.FailFast(false) // Don't exit test on first failure.
// Test panic condition.
chk.Panic(
func() {
Process(0, "failing message")
},
"factor (0) out of bounds: \"wrong message\"",
)
// Test valid operation.
gotInt, gotStr, gotFloat := Process(2, "Hello")
chk.Int(gotInt, 3)
chk.Str(gotStr, "Hello", "missing", " ", `"Processed"`, " ", "prefix")
// Tolerance (± 0.0005)
chk.Float64f(gotFloat, 0.6661, 0.0005, "off by %f", 0.6661-0.6665)
// Check output.
chk.Stdout(
"Processing with factor: 2 and message: Processed Hello",
)
// Check logging.
chk.Log(chk.TrimAll(`
Entered process(0, "wrong message")
factor (0) out of bounds: "wrong message"
Entered process(2, "Processed Hello")
`))
}causing the following output when these tests are run:
go test -v -cover ./large_example_function
Given the following main.go file:
cat ./large_example_main_function/main.gopackage main
import (
"flag"
"fmt"
"log"
"math"
"strconv"
"strings"
)
const radiansPerDegree = math.Pi / 180.0
const degreesPerRadian = 180.0 / math.Pi
const bits64 = 64
const numDecimals = 6
func makeReport(degrees, radians float64, useRadians, verbose bool) []string {
var rep []string
addToReport := func(v float64, name string) {
line := strconv.FormatFloat(v, 'f', numDecimals, bits64)
if verbose {
arg := ""
if !useRadians {
arg = strconv.FormatFloat(degrees, 'f', numDecimals, bits64) + "°"
} else {
arg = strconv.FormatFloat(radians, 'f', numDecimals, bits64)
}
line = name + "(" + arg + ") = " + line
}
rep = append(rep, line)
}
addToReport(math.Sin(radians), "Sin")
addToReport(math.Cos(radians), "Cos")
return rep
}
// Program takes an angle and reports its Sin and Cos values.
// -v causes a more detailed response.
// -r cause the angle input to be interrupted as radians.
func main() {
var degrees, radians float64
verbose := flag.Bool("v", false, "More detailed information.")
useRadians := flag.Bool("r", false, "Value is in radians.")
flag.Parse()
if flag.NArg() != 1 {
log.Panic("angle required")
}
v, err := strconv.ParseFloat(flag.Args()[0], bits64)
if err != nil {
log.Panicf("invalid angle: %s", flag.Args()[0])
}
if *useRadians {
degrees = degreesPerRadian * v
radians = v
} else {
degrees = v
radians = radiansPerDegree * v
}
if *verbose {
if *useRadians {
fmt.Printf("Report on %f radians (%f degrees)\n", radians, degrees)
} else {
fmt.Printf("Report on %f degrees (%f radians)\n", degrees, radians)
}
}
fmt.Print(
strings.Join(makeReport(degrees, radians, *useRadians, *verbose), "\n"),
"\n",
)
}we can provide 100% test coverage with the following test file showing both successful and failing responses:
cat ./large_example_main_function/main_test.gopackage main
import (
"fmt"
"log"
"math"
"strconv"
"testing"
"github.com/dancsecs/sztest"
)
func Test_PASS_Main_No_Args(t *testing.T) {
chk := sztest.CaptureLogWithStderrAndStdout(t)
defer chk.Release()
log.Println("Testing missing angle")
chk.SetArgs("progname")
chk.Panic(
main,
"angle required",
)
log.Println("Testing invalid angle")
chk.SetArgs("progname", "notANumber")
chk.Panic(
main,
"invalid angle: notANumber",
)
fmt.Println("Testing angle 0 no flags")
chk.SetArgs("progname", "0")
chk.NoPanic(main)
fmt.Println("Testing angle 0 with verbose flag")
chk.SetArgs("progname", "-v", "0")
chk.NoPanic(main)
twoPi := strconv.FormatFloat(math.Pi*2, 'f', -1, 64)
fmt.Println("Testing angle 2Pi with radian flag")
chk.SetArgs("progname", "-r", twoPi)
chk.NoPanic(main)
fmt.Println("Testing angle 2Pi with radian and verbose flag")
chk.SetArgs("progname", "-v", "-r", twoPi)
chk.NoPanic(main)
chk.Stdout(
"Testing angle 0 no flags",
"0.000000",
"1.000000",
"Testing angle 0 with verbose flag",
"Report on 0.000000 degrees (0.000000 radians)",
"Sin(0.000000°) = 0.000000",
"Cos(0.000000°) = 1.000000",
"Testing angle 2Pi with radian flag",
"-0.000000",
"1.000000",
"Testing angle 2Pi with radian and verbose flag",
"Report on 6.283185 radians (360.000000 degrees)",
"Sin(6.283185) = -0.000000",
"Cos(6.283185) = 1.000000",
)
chk.Log(
"Testing missing angle",
"angle required",
//
"Testing invalid angle",
"invalid angle: notANumber",
//
)
}
func Test_FAIL_Main_No_Args(t *testing.T) {
chk := sztest.CaptureLogWithStderrAndStdout(t)
defer chk.Release()
chk.FailFast(false) // Do not terminate function on first error.
log.Println("Testing missing angle")
chk.SetArgs("progname")
chk.Panic(
main,
"angle is required",
)
log.Println("Testing invalid angle")
chk.SetArgs("progname", "notANumber")
chk.Panic(
main,
"invalid angle: not A Number",
)
fmt.Println("Testing angle 0 no flags")
chk.SetArgs("progname", "0")
chk.NoPanic(main)
fmt.Println("Testing angle 0 with verbose flag")
chk.SetArgs("progname", "-v", "0")
chk.NoPanic(main)
twoPi := strconv.FormatFloat(math.Pi*2, 'f', -1, 64)
fmt.Println("Testing angle 2Pi with radian flag")
chk.SetArgs("progname", "-r", twoPi)
chk.NoPanic(main)
fmt.Println("Testing angle 2Pi with radian and verbose flag")
chk.SetArgs("progname", "-v", "-r", twoPi)
chk.NoPanic(main)
chk.Stdout(
"Testing angle 0 no flags",
"0.000000",
"1.000000",
"Testing angle 0 with verbose flag",
"Report on 0.000000 degrees (0.000000 radians)",
"Sin(0.000000°) = 0.000000",
"Cos(0.000000°) = 1.000000",
"Testing angle 2PI with radian flag",
"-0.000000",
"1.000000",
"Testing angle 2Pi with radian and verbose flag",
"Report on 6.283185 radians (360.000000 degrees)",
"Sin(6.283185) = -0.000000",
"Cos(6.283185) = 1.000000",
)
chk.Log(
"Testing missing angle",
"angle is required",
//
"Testing invalid angle",
"invalid angle: not A Number",
//
)
}go test -v -cover ./large_example_main_function