Software Testing

Writing unit tests in Go

The importance of writing tests for your software cannot be overemphasized. A school of thought called Test-Driven Development suggests that developers should start building software by writing code first.

There are two approaches to writing tests:

  • writing tests for a program to check whether the functions in it perform as expected

  • writing tests during problem-solving to build software that passes the test.

You may adopt both, using each of them whenever it is suitable. Let’s explore both scenarios.

Writing tests for functions

Tests work with the testing package in Go. Here are a few rules to follow when writing tests:

  • If you’re testing for a services.go file. Your test file name will be services_test.go.

  • If you’re testing for a FetchPrice function, your test function name will be TestFetchPrice.

  • If you’re writing tests for a function with another function bearing the same name, you can add the function’s package name to the test function name. For example, if two FetchPrice functions exist in separate folders. You can test them separately using Test_StocksFetchPrice and Test_CryptoFetchPrice, where stocks and crypto are different package names.

  • The test function must accept (t *testing.T) in the parameter.

Let’s use a simple Add function to demonstrate. Create an add.go file:

package AdvancedGo

func Add(a, b int) int {
	return a + b
}

Next, create an add_test.go file with this:

package AdvancedGo_test

import (
		"testing"
)

func TestAdd(t *testing.T) {
	// test case 1: check if Add() works correctly
	testCase1 := []struct {
		a, b, expected int
	}{
		{1, 2, 3},
		{0, 0, 0},
		{-1, -1, -2},
	}

	for _, test := range testCase1 {
		result := Add(test.a, test.b)
		if result != test.expected {
			t.Errorf("Add(%d, %d) = %d; want %d", test.a, test.b, result, test.expected)
		}
	}

Run go test, and you’ll get an Ok result. When you have functions that perform serious operations in a program, you should write test cases for when it passes to check correctness and for when it fails. This gives you an idea of how your program behaves when it fails.

Writing tests for fixing things

This approach involves writing tests first and then writing code to pass the test. Let’s see an example with a program that finds the maximum element in an array. Create a max.go file and a max_test.go file. In the test file, you can write this:

package main_test

import (
	"testing"
)

func TestFindMax(t *testing.T) {
	// how FindMax func should behave
	testArray1 := []int{1, 2, 3, 4, 5}
	testArray2 := []int{6, 7, 8, 9, 10}
	testCase1 := []struct {
		array      []int
		maxElement int
	}{
		{testArray1, 5},
		{testArray2, 10},
	}

	for _, test := range testCase1 {
		result := FindMax(test.array)
		if result != test.maxElement {
			t.Errorf("FindMax(%v) = %d; want %d", test.array, result, test.maxElement)
		}
	}
}

If you run the tests using the go test command, you will get the following output:

./max_test.go:20:13: undefined: FindMax
FAIL	github.com/theghostmac/Backend-Engineering-With-Go/AdvancedGo [build failed]

This means we need the FindMax function. Let’s go ahead and create it in the max.go file with the minimal code possible:

package main

func FindMax(array []int) int {
	return 0
}

If you run it, you get:

--- FAIL: TestFindMax (0.00s)
    max_test.go:23: FindMax([1 2 3 4 5]) = 0; want 5
    max_test.go:23: FindMax([6 7 8 9 10]) = 0; want 10
FAIL
exit status 1
FAIL	/path/to/project/	4.535s

Next, we try to optimize the function to pass the test:

package main

func FindMax(array []int) int {
	max := array[0]
	for _, num := range array {
		if num > max {
			max = num
		}
	}
	return max
}

Now run the test:

go test
PASS
ok  	path/to/project/	2.439s

Great! It passes. This type of testing is suitable for building libraries and packages that are not run.

Note that

Exercise

  • Write unit tests for the functions in our dummy stock prices program above.

  • Read about different types of tests adopted in software engineering and when to use which. Proceed to learn how Go does whichever test you believe you should learn to use in your code.

Whenever you're ready

There are 4 ways we can help you become a great backend engineer:

The MB Platform

Join 1000+ backend engineers learning backend engineering. Build real-world backend projects, learn from expert-vetted courses and roadmaps, track your learnings and set schedules, and solve backend engineering tasks, exercises, and challenges.

The MB Academy

The “MB Academy” is a 6-month intensive Advanced Backend Engineering BootCamp to produce great backend engineers.

Join Backend Weekly

If you like post like this, you will absolutely enjoy our exclusive weekly newsletter, Sharing exclusive backend engineering resources to help you become a great Backend Engineer.

Get Backend Jobs

Find over 2,000+ Tailored International Remote Backend Jobs or Reach 50,000+ backend engineers on the #1 Backend Engineering Job Board