Golang Unit Testing

Unit Testing In Golang

Ahoy, Gopher pirates. GOing somewhere? If you’ve been reading our previous articles and practicing, you should have quite a few small projects that you’re working on ( including gqlgen, image processing, web scraping, or building web apps). However, no senior developer takes a code seriously that has no testing involved. Usually, to get your code approved, you would need at least unit testing. So today I’m going to teach you exactly that.

Unit testing – what is it?

In computer programming, unit testing is a software testing method by which individual units of source code—sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures—are tested to determine whether they are fit for use.

-Wikipedia

Usually, unit tests are automated tests written and performed by software developers to ensure that a part of an application (called the “unit”) meets its specification and performs as expected. This will become quite clear when we look at Go’s implementation of the same.

On the other side of the spectrum lies Integration testing (sometimes called integration and testing, abbreviated I&T), which is the software development process that integrates and evaluates individual software modules as a group.

I&T is generally performed by dedicated teams within the company so the individual developer doesn’t have to worry about that. However, you are expected to complete most of your unit tests.

Unit Testing in Go – testing package

The basic syntax for testing in Go is:

import “testing”

func TestConvert(t *testing.T) {
	t.Log(“Hello World”)
	t.Fail()
}

To test a program, first we’ll need a program, so let’s do that quickly. We’ll create conv.go that will take as input distance values and convert it to other specified formats.

package main

import ( 
	“errors”
	“fmt”
	“strconv”
	“strings”
)

const (
	kilometersToMiles = 0.621371
		
	milesToKilometres = 1.60934
	)

func Convert (from, to string) (string,error) {
	var result float64
	switch {
	case strings.HasSuffix(from,”mi”):
		miles,err := strconv.ParseFloat(from[:len(from)-2],64)
		if err != nil {
			return “”,err
			}



	switch to {
	case “km”:
		result = miles * milesToKilometres
	case “m”:
		result = miles * milesToKilometres * 1000
	case “mi”:
		result = miles
	}
}

This is a very simple piece of code:

Inputs are in the form of (“50mi”,”km”), (“20km”,”mi”) and so on.

The output is in whichever format specified by string to.

So now we start our test. Put this conv.go file in a directory. In the same directory, open another file called conv_test.go :

package conv
	
import “testing”

func TestConvert(t *testing.T) {
	str, err := Convert(“50 mi” , “km”)
	if err != nil {
		t.Log(“error should be nil”,err)
			t.Fail()
	}
	if str != “80.47km” {
		t.Log(“error should be 80.47km, but got”,err0
		t.Fail()
	}
}

What this does is output the value of converting 50 miles to kilometres (which if you google is ~80.47 km), and then check the value output with expected value.

Running GoLang Testing Code – go test command

You can run the test simply by go test. If we run this, we get

FAIL : TestConvert(0.00s)
		converter_test.go:12: error should be 80.47km, but got 80.47

This is because in conv.go, we only output 80.47. We can easily fix this by changing:

result = miles + to

(since to refers to the distance measure, here it will be km)

So that is all. We could add a dozen tests in this manner, but writing switch cases for each category will become tedious. We can do it easily by creating a table struct of all the from, to and expected values and then passing through all the tests in a loop. How ? Like this:

package converters
import “testing”

func TestConvert(t *testing.T) {
	cases := []struct { from, to, expected string} {
		{“50mi”, “km”, “80.47km” },
	}

	for _, c := range cases {

		str, err := Convert(c.from, c.to)
		if err != nil {
			t.Log(“error should be nil”,err)
			t.Fail()
		}
		if str != expected {
			t.Log(“error should be”+expected+”, but got”,str)
			t.Fail()
		}
	}
}

We create a struct containing all the values and then loop through them.

If we run this, we get a PASS (which means our code is working)

References