Liam Conroy Hampton
Posted on May 27, 2021
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
}
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)
}
})
}
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)
}
}
}
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 📝
Posted on May 27, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.