“This is the sixth day of my participation in the First Challenge 2022. For details: First Challenge 2022”

introduce

Programming is not easy, and good programmers can’t guarantee a bug-free program. Therefore, an important part of the software development process is testing. Writing tests for our code is a great way to ensure quality and improve reliability.

  • Go provides the Testing package that you can use to write automated tests for your code.
  • go testCommand to run the code for these tests.

Unit testing is an important part of writing a principled Go program. The test pack provides the tools we need to write unit tests, which can be run by the go test command. For demonstration purposes, this code is in package Main, but it could be any package. The test code is usually in the same package as the code that corresponds to the required tests.

Test code structure

Go recommends that the test file be placed with the source code file, and the test file ends with _test.go. For example, the current package has a file called average. Go, and we want to test the average function in Average. Go, we should create average_test.go as the test file.

example/
   |--average.go
   |--average_test.go
Copy the code

The Go Test tool will help us find files with that suffix.

What a test file should look like:

package average

import "testing" // Import the standard library "tesing" package

func TestAverage(t *testing.T) { // Pass a pointer to testing.t to the function
	// Call the testing.t method to indicate that the test failed
  t.Error("wrong anwser")}Copy the code

The test file consists of the normal Go function, but you must follow certain conventions to use the Go test command:

  • There is no need to put the code under test in the same package as the code under test, but it is best to do so if you want to access unexported types or functions from the package.
  • Tests need to use the types in the Testing package, so they need to be in each test fileimportBlock to import the package.
  • Test function names should start with Test. (The rest of the name can be anything you want, but it should start with a capital letter)
  • Test functions should take a single argument: a pointertesing.TA pointer to value.
  • Can betesing.TValue call method (Error) to report test failures, but more often the option is to passErrorfAccept a string that helps interpret the test failure message.ErrorfAccept a string with a formatted verb, as infmt.Printffmt.SprintfFunction, can be usedErrorfInclude additional information in the failure message of the test, such as the parameters passed to the function, the returned value obtained, and the expected value.

Translate a piece of test code

We will test a simple implementation that evaluates the minimum value of an integer.

Follow the structure of the test code above:

  1. We need to put the test code into a code namedintutils.goIn the source file
  2. And its test file will be namedintutils_test.go
package main

import (
    "fmt"
    "testing"
)

func IntMin(a, b int) int {
    if a < b {
        return a
    }
    return b
}

func TestIntMinBasic(t *testing.T) {
    ans := IntMin(2.2 -)
    ifans ! =2 - {

        t.Errorf("IntMin(2, -2) = %d; want -2", ans)
    }
}

func TestIntMinTableDriven(t *testing.T) {
    var tests = []struct {
        a, b int
        want int
    }{
        {0.1.0},
        {1.0.0},
        {2.2 -.2 -},
        {0.- 1.- 1},
        {- 1.0.- 1}},for _, tt := range tests {

        testname := fmt.Sprintf("%d,%d", tt.a, tt.b)
        t.Run(testname, func(t *testing.T) {
            ans := IntMin(tt.a, tt.b)
            ifans ! = tt.want { t.Errorf("got %d, want %d", ans, tt.want)
            }
        })
    }
}

func BenchmarkIntMin(b *testing.B) {
    for i := 0; i < b.N; i++ {
        IntMin(1.2)}}Copy the code

Create a Test by writing a function whose name begins with Test.

T.ror * reports a test failure but continues to execute the test. T.fatal * will report a test failure and stop the test immediately.

Writing tests can be repetitive, so it is customary to use a table-driven style, where test inputs and expected outputs are listed in a table, and a single loop runs through them and executes the test logic.

Run can run “child tests”, one for each entry. When you run go test -v, these child tests are displayed separately.

Run all tests in rough mode in the current project.

Running results:

$ go test -v=== RUN TestIntMinBasic --- PASS: TestIntMinBasic (0.00 s) = = = RUN TestIntMinTableDriven = = = RUN TestIntMinTableDriven / 0, 1 = = = the RUN TestIntMinTableDriven / 1, 0 = = = RUN TestIntMinTableDriven / 2, 2 = = = RUN TestIntMinTableDriven / 0, 1 = = = the RUN TestIntMinTableDriven/-1,0 -- PASS: TestIntMinTableDriven (0.00s) -- PASS: TestIntMinTableDriven / 0, 1 (0.00 s) - PASS: TestIntMinTableDriven / 1, 0 (0.00 s) - PASS: TestIntMinTableDriven/2,-2 (0.00s) -- PASS: TestIntMinTableDriven/0,-1 (0.00s) -- PASS: TestIntMinTableDriven/2,-2 (0.00s) -- PASS: TestIntMinTableDriven/-1 0 (0.00s) PASS OK main.go 0.005sCopy the code

The above code is translated from: gobyexample.com/testing

A simple “average” function test

For example, our code for calculating average scores could be written as follows:

package average

import "testing"

func GetAverage(scores []float64) float64 {
	sum := float64(0)
	for _, value := range scores {
		sum += value
	}
	return sum / float64((len(scores)))
}

func TestAverage(t *testing.T) {

	var v float64
	v = GetAverage([]float64{1.2.3.4.5})

	ifv ! =3 {
		t.Error("Expected 3, got ", v)
	}
}
Copy the code

Test results:

$ go test -v=== RUN TestAverage -- PASS: TestAverage (0.00s) PASS OK main.go/Go_test_demo 0.005sCopy the code

Table driven development

The average test above can only be a single test data. If we want to test multiple different data, we can use table-driven development.

Instead of maintaining separate test functions, you can build a table of input data and expected output, and then use a single test function to check each item in the table.

You can use a testpair structure to hold values slices and average results, as in:

type testpair struct {
	values  []float64
	average float64
}

var tests = []testpair{
	{[]float64{1.2}, 1.5},
	{[]float64{1.1.1.1.1.1}, 1},
	{[]float64{- 1.1}, 0},
  {[]float64{1.1.4}, 1.2}},Copy the code

Then loop through the values of each test and pass them to the GetAverage function:

package average

import "testing"

type testpair struct {
	values  []float64
	average float64
}

var tests = []testpair{
	{[]float64{1.2}, 1.5},
	{[]float64{1.1.1.1.1.1}, 1},
	{[]float64{- 1.1}, 0},
	{[]float64{1.1.4}, 1.2}},func GetAverage(scores []float64) float64 {
	sum := float64(0)
	for _, value := range scores {
		sum += value
	}
	return sum / float64((len(scores)))
}

func TestAverage(t *testing.T) {
	for _, pair := range tests {
		v := GetAverage(pair.values)
		ifv ! = pair.average { t.Error("For", pair.values,
				"expected", pair.average,
				"got", v,
			)
		}
	}
}
Copy the code

To run the appeal code:

$ go test -v=== RUN TestAverage -- PASS: TestAverage (0.00s) PASS OK main.go/Go_test_demo 0.005sCopy the code

Test-driven development

We hear a lot about test-driven Development (TDD) :

  1. Write tests: Write tests for the feature you want, even if it doesn’t exist yet, and then run the test to make sure it fails.
  2. Make sure the test passes: inmainImplement this feature in your code to ensure that the run tests pass.
  3. Refactor code: As long as you pass the unit tests, you are free to refactor your code, modify it, and improve it.

conclusion

  • Go containstestingPackage that you can use to write automated tests for your code
  • go testThe command is used to run these tests_test.goThe closing document.
  • Automated tests use a specific set of inputs to run code and look for specific results. If the code input matches the expected value, the test passes, otherwise the test fails with an error.
  • The test function must take a single argument: a pointertesting.TIs a pointer
  • A table-driven test is a test of a “table” that processes inputs and expected outputs, passing in a set of inputs, testing the set of outputs, and checking that the output of the code matches the expected values. This reduces code duplication.

Reference Books:

  • Head First Go
  • gobyexample.com/testing