Unit Testing - Table Tests in Go
Tags: beginners, testing, go, devops
Published: 2021-05-27 15:29:14

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
```golang
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
}
```

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
```golang
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)
		}
	})
}
```

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
```golang
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)
		}
	}
}
```
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 :memo:

[GitHub](https://github.com/liamchampton) | [Twitter](https://twitter.com/liamchampton) | [Instagram](https://www.instagram.com/liamhampton/) | [LinkedIn](www.linkedin.com/in/liam-conroy-hampton)