Testing Your Go Apps

584 VIEWS

·

This article is aimed at helping you understand testing your Go apps, and teaching you how to effectively perform simple unit testing and table-driven testing in Go. 

Testing is the process of examining and scrutinizing your application/software to ensure that there are no defects and that the behaviors are as planned, without any possibilities of failure along the way.

If the size of your application is small, you can resort to manual testing; however, as your application size grows, it can be taxing to test all the parts of the application over time. You can write tests to automate the process of testing your code and gain insights into the behavior of your application flexibly.

When you build software, as part of the software development cycle, you’ll have to examine the software for bugs and unexpected behaviors before releasing the software for public use to avoid abnormalities that may be expensive.

Getting Started with Testing in Go

The testing package is part of the Go standard library. It is one of the most popular packages for testing in Go. The testing package provides many functionalities for testing your Go programs, such as fuzzing, benchmarking, covering, subtests, and many others.

You’ll need to create a test file for your Go test. By convention, you name the test with the _test suffix, e.g., api_test.go, and in the file, you write your tests.

touch api_test.go

his command creates a Go test file on Unix-based systems.

In your Go test files, your test functions must have the conventional test functions structure.

func TestStringToInteger(t *testing.T) {
  
}

Your test functions have to start with the Test prefix and implement the testing.T object as shown above.

Simple Unit Testing in Go

Your test model will dynamically change depending on the nature of your testing.

Here’s a simple test struct that will serve as a model for the tests in this tutorial.

type Examples struct {

  expected int

  actual int

}

Here’s a simple function that converts strings to integers with the built-in strconv package.

import (
  "strconv"
  "testing"
)

func convertString(str string) int {

  integerValue, err := strconv.Atoi(str)
  if err != nil {
    return 0
  }

  return integerValue
}

The convertString function returns 0 if there’s an error on conversion and an integer if there are no errors.

Here’s the test function for the StringToInteger function. Your test functions should be in your test files.

func TestConvert(test *testing.T) {

  expectedInt := convertString("3")
  caseInstance := Examples{
    expected: expectedInt,
    actual:   3,
  }
  if caseInstance.expected == caseInstance.actual {
    // execute code
  } else {
    test.Fail()
  }
}

The TestConvert test function implements the testing object from the testing package. The expectedInt variable is the result of the string conversion. The caseInstance variable is the instantiated Examples struct for the test.

The if statement compares the expected and actual values in the initialized struct. The Fail method returns a failed test in the else statement if the values aren’t equal.

You can run your test cases with the test command.

go test -v

Adding the -v flag would return a verbose output. Here’s the result of the test.

Go test -v

You can run all the tests in your project by specifying them after the test command.

go test ./...

You can run individual test cases in a test function with the Run command of the test instance. The Run command takes in a string and a test function where you can specify the test case.

func TestConvert(test *testing.T) {

  test.Run("", func(t *testing.T) {
    result := convertString("nine")
    exampleCase := Examples{
      expected: result,
      actual:   0,
    }
    if exampleCase.expected != exampleCase.actual {
      t.Error("There's a conversion error. Result=", exampleCase.actual)
    }
  })


}

The Run method matches the first parameter to the test command

Table-Driven Tests in Go

Table-driven tests provide a resort for testing multiple cases and units of code with a range of values. You’ll define all the cases in a slice and iterate over the slice for comparisons of cases.

Here’s how you can implement table-driven tests for your programs.

func TestConvert(test *testing.T) {

  allCases := []Examples{
    Examples{
      expected: convertString("nine"),
      actual:   0,
    },

    Examples{
      expected: convertString("ten"),
      actual:   0,
    },

    Examples{
      expected: convertString("14"),
      actual:   14,
    },
  }

  for index, cases := range allCases {
    test.Run(fmt.Sprintf("Test Case %v", index), func(test *testing.T) {
      if cases.expected != cases.actual {
        test.Fail()
      }
    })
  }
  
}

The allCases variable you declared is the slice of struct instances with different test case values. The range loop iterates through the slice’s structs. It runs the test cases for each instance with the Run method, and fails the test on unequal assertion.

table-driven test in Go

Conclusion

You’ve learned about testing, Go’s built-in testing package, performing unit and table-driven tests with Go, and running your Go tests with test files.

The testing package doesn’t provide functionality for structured logging; however, there are packages like Testify for structured logging in Go.

See more articles about how to manage your apps.


Ukeje Goodness Chukwuemeriwo is a Software developer and DevOps Enthusiast writing Go code day-in day-out.


Discussion

Click on a tab to select how you'd like to leave your comment

Leave a Comment

Your email address will not be published. Required fields are marked *

Menu
Skip to toolbar