Tests and Subtests in Go

📅 Posted: Dec 6, 2020

Welcome Gophers! This is the second video in the Go testing bible course. In the previous tutorial, we start our journey writing tests in Go by learning the basics and getting started writing our first 2 unit tests and trying to understand a little better why we write tests to cover our applications.

In this section of the course, we’re going to take things a little bit further and improve the way we do write our tests by incoporating sub-tests into our Go testing strategy.

The t.Run Method

When we write tests, you may have noticed we pass in this pointer t which is a type that has been defined in the testing package in Go’s standard library. This type T is passed to all testing functions to help manage the state of all of our tests and also helps with some other auxilliary tasks like formatted test logs.

This type has a number of different methods based which we can use within our tests which range from Fail which we’ve already seen in the last tutorial, through to methods like Skip or the method we are going to look at now which is t.Run.

The t.Run method effectively enables us to define subtests within our Test functions. It effectively gives us more power when writing tests to incorporate things like parallelism in our tests.

Refactoring our Tests

Now, before we do anything, we should sanity check that adding new functionality to our package has not somehow broken our existing tests:

$ go test ./...

Perfect, we’ve now been able to validate that our code hasn’t broken anything and we can start refactoring our tests to use subtests!

Let’s dive in to our calculator_test.go file and start modifying the existing tests and add a few more in for good measure:

package calculator_test

import "testing"

type TestCase struct {
    value int
    expected bool
    actual bool
}

func TestCalculateIsArmstrong(t *testing.T) {
    t.Run("should return true for 371", func(t *testing.T) {
         testCase := TestCase{
            value: 371,
            expected: true,
        }

        testCase.Actual = CalculateIsArmstrong(testCase.value)
        if testCase.Actual != testCase.expected {
            t.Fail("CalculateIsArmstrong returned an unexpected result")
        }
    })

    t.Run("should return true for first armstrong number", func(t *testing.T) {
        testCase := TestCase{
            value: 1,
            expected: true,
        }

        testCase.Actual = CalculateIsArmstrong(testCase.value)
        if testCase.Actual != testCase.expected {
            t.Fail("CalculateIsArmstrong returned an unexpected result")
        }
    })

    t.Run("should return false for non-armstrong number", func(t *testing.T){
        testCase := TestCase{
            value: 15,
            expected: false,
        }

        testCase.Actual = CalculateIsArmstrong(testCase.value)
        if testCase.Actual != testCase.expected {
            t.Fail("CalculateIsArmstrong returned an unexpected result")
        }
    })
}

Now, you may be wondering what the point of this is? When you start building up more complex Go applications this quickly becomes apparent.

When you develop more complex apps, the number of tests that app has should start to grow quickly. Therefore the time taken to run through all of the tests becomes increasingly longer.

By grouping all of our tests as subtests under a standard test, we can quickly and easily run the full suite of tests for this particular function using the -run flag:

$ go test -run TestCalculateIsArmstrong

This will go away and run only the subtests pertinent to the function we are working in and allows reduces the amount of time between us changing a line of code, and validating that our tests still pass with these new changes!

Conclusion

Awesome, in this tutorial, we’ve covered the wonders of subtests and how they can be beneficial to you in your pursuits as a Go developer.

In the next tutorial in this course, we’ll be taking it another step further and looking at how we can use table-driven tests to further improve how we test our applications!