DEV Community

Cover image for Unit Testing - Table Tests in Go
Liam Conroy Hampton
Liam Conroy Hampton

Posted on

Unit Testing - Table Tests in Go

There are many types of testing that takes place throughout the software development lift cycle (SDLC) but in this post I will concentrate on table testing within unit tests.

First letโ€™s look at WHAT test cases are, specifically unit testing. Unit testing is a granular level of testing within your project. It tests the individual parts of the puzzle for fitment and correctness - "Does this function behave how I expect it to behave?".

WHY do we use unit testing? - The aim of unit testing is to get high code coverage and check each moving part of your program. You shouldn't need to run your program everytime you want to check one small piece of functionality. That is only a good way to test "the happy path", its manual and time consuming. So, what if things donโ€™t go as expected? You should be able to test failures and handle unexpected output from the operating function. With unit testing, you can do this, and you should do this.

In practise, unit testing is a must but it can get messy very quickly. I will show you using an example function below.

Function

package main

import "fmt"

func main() {
    str := "Liam"
    reversedStr := reverseString(str)
    fmt.Println(reversedStr)
}

func reverseString(str string) string {
    var reversedStr string
    for _, s := range str {
        reversedStr = string(s) + reversedStr
    }
    return reversedStr
}
Enter fullscreen mode Exit fullscreen mode

This function will take the string "Liam" and reverse it. The result of running this program should be "maiL".

So lets write some tests for the reverseString() function.

Test Function

package main

import "testing"

func TestReverseString(t *testing.T) {
    t.Run("reverse of Liam", func(t *testing.T) {
        str := "Liam"
        expected := "maiL"
        got := reverseString(str)
        if got != expected {
            t.Errorf("expected %s, got %s", expected, got)
        }
    })

    t.Run("reverse of foobar", func(t *testing.T) {
        str := "foobar"
        expected := "raboof"
        got := reverseString(str)
        if got != expected {
            t.Errorf("expected %s, got %s", expected, got)
        }
    })

    t.Run("reverse of testing", func(t *testing.T) {
        str := "testing"
        expected := "gnitset"
        got := reverseString(str)
        if got != expected {
            t.Errorf("expected %s, got %s", expected, got)
        }
    })
}
Enter fullscreen mode Exit fullscreen mode

You can see by the tests above; with only 3 test cases this is quite a lot of code. This is still valid, but it is long and messy. Just imagine what these tests might look like if the original function was a bit more complex.

Letโ€™s simplify these cases with a table test.

Test Function Simplified

func TestReverseString(t *testing.T) {
    tests := []struct {
        input  string
        output string
    }{
        {input: "Liam", output: "maiL"},
        {input: "foobar", output: "raboof"},
        {input: "testing", output: "gnitset"},
    }

    for _, testCase := range tests {
        got := reverseString(testCase.input)
        expected := testCase.output
        if got != expected {
            t.Errorf("expected %s, got %s", expected, got)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

That looks much better but what did I just do?

To begin with, I created a struct that houses each individual test case input and expected output of the type string. This is what the original function expects as input and what it returns as an output.

Next, I created a range loop. This loop iterates through the test cases in the struct containing the input / output. Inside the loop is a single test case that will be fed the input / output for each test case from the struct. The code inside the loop is the same code used in the first set of test cases but introducing the loop removes the need to duplicate test cases if the only part changing is the input / output.

Just like that, I have simplified the tests, made them easier to follow and easier to ammend.

This code can be found at https://github.com/liamchampton/go-string-manipulation

If you have any questions or want to see more content like this, feel free to drop me a line ๐Ÿ“

GitHub | Twitter | Instagram | LinkedIn

Top comments (2)

Collapse
 
developerally profile image
Alison Haire

Great intro to unit testing on GO! Thanks Liam

Collapse
 
liamchampton profile image
Liam Conroy Hampton

Thank you!