programming

How to Create Unit Tests in Golang

Unit Testing Using the Go Library

programming is not easy, even the best programmers cannot write programs that work exactly as desired every time. Therefore, an important part of the software development process is testing. Writing tests for our code is a good way to ensure quality and increase reliability.

Go provides a testing package, containing lots of tools for unit testing purposes. In this chapter we will learn about testing, benchmarks, and also testing using testimonials.

Go includes special programs that make writing tests easier, so let’s create some tests for the packages we create in this session. In the 01-math folder create a new file called math_test.go which contains this.

Previously we created a function as follows.

package main

func Average(xs []float64) float64 {
	total := float64(0)
	for _, x := range xs {
		total += x
	}
	return total / float64(len(xs))
}

So that we can create unit tests, we generate them using vscode, it will create a new file math_test.go and generate the TestAverage function with the contents below.

func TestAverageGenerate(t *testing.T) {
	type args struct {
		xs []float64
	}
	tests := []struct {
		name string
		args args
		want float64
	}{
		{
			name: "must 10",
			args: args{
				xs: []float64{10.0, 10.0},
			},
			want: float64(10),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := Average(tt.args.xs); got != tt.want {
				t.Errorf("Average() = %v, want %v", got, tt.want)
			}
		})
	}
}

Unit Test Program Odd Even

Create a function to determine that the program outputs the input information regarding odd and even numbers.

func GanjilGenap(angka int) string {
	if angka%2 == 0 {
		return "genap"
	}
	return "ganjil"
}

Maka kita akan buat unit test-nya seperti ini:

package math

import (
	"testing"
)

func TestAverageGenerate(t *testing.T) {
	type args struct {
		xs []float64
	}
	tests := []struct {
		name string
		args args
		want float64
	}{
		{
			name: "must 10",
			args: args{
				xs: []float64{10.0, 10.0},
			},
			want: float64(10),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := Average(tt.args.xs); got != tt.want {
				t.Errorf("Average() = %v, want %v", got, tt.want)
			}
		})
	}
}

type testpair struct {
	values  []float64
	average float64
}

var tests = []testpair{
	{[]float64{1, 2}, 1.5},
	{[]float64{1, 1, 1, 1, 1, 1}, 1},
	{[]float64{-1, 1}, 0},
}

func TestAverage(t *testing.T) {
	for _, pair := range tests {
		v := Average(pair.values)
		if v != pair.average {
		}
	}
}

func TestGanjilGenap(t *testing.T) {
	type args struct {
		angka int
	}
	tests := []struct {
		name string
		args args
		want string
	}{
		{
			name: "test case mengeluarkan ganjil",
			args: args{
				angka: 1,
			},
			want: "ganjil",
		},
		{
			name: "test case mengeluarkan genap",
			args: args{
				angka: 2,
			},
			want: "genap",
		},
		{
			name: "test case mengeluarkan -1",
			args: args{
				angka: -1,
			},
			want: "ganjil",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := GanjilGenap(tt.args.angka); got != tt.want {
				t.Errorf("GanjilGenap() = %v, want %v", got, tt.want)
			}
		})
	}
}

Testing for Tube

First, prepare a Tube struct. We will later use the object variables resulting from this struct as testing material.

package math

import "math"

type Tabung struct {
	Jarijari, Tinggi float64
}

func (t Tabung) Volume() float64 {
	return math.Phi * math.Pow(t.Jarijari, 2) * t.Tinggi
}

func (t Tabung) Luas() float64 {
	return 2 * math.Phi * t.Jarijari * (t.Jarijari + t.Tinggi)
}

func (t Tabung) KelilingAlas() float64 {
	return 2 * math.Phi * t.Jarijari
}

Then we will create unit tests one by one from the functions we have created. It can be seen as follows.

Unit Test for Volume function

func TestTabung_Volume(t *testing.T) {
	type fields struct {
		Jarijari float64
		Tinggi   float64
	}
	tests := []struct {
		name   string
		fields fields
		want   float64
	}{
		{
			name: "testing hitung volume",
			fields: fields{
				Jarijari: 7, Tinggi: 10,
			},
			want: float64(792.8366544874485),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tr := Tabung{
				Jarijari: tt.fields.Jarijari,
				Tinggi:   tt.fields.Tinggi,
			}
			if got := tr.Volume(); got != tt.want {
				t.Errorf("Tabung.Volume() = %v, want %v", got, tt.want)
			}
		})
	}
}

Unit Test for Surface Area function

func TestTabung_Luas(t *testing.T) {
	type fields struct {
		Jarijari float64
		Tinggi   float64
	}
	tests := []struct {
		name   string
		fields fields
		want   float64
	}{
		{
			name: "testing hitung luas permukaan",
			fields: fields{
				Jarijari: 7, Tinggi: 10,
			},
			want: float64(385.092089322475),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tr := Tabung{
				Jarijari: tt.fields.Jarijari,
				Tinggi:   tt.fields.Tinggi,
			}
			if got := tr.Luas(); got != tt.want {
				t.Errorf("Tabung.Luas() = %v, want %v", got, tt.want)
			}
		})
	}
}

Unit test for Base Perimeter

func TestTabung_KelilingAlas(t *testing.T) {
	type fields struct {
		Jarijari float64
		Tinggi   float64
	}
	tests := []struct {
		name   string
		fields fields
		want   float64
	}{
		{
			name: "testing hitung keliling alas",
			fields: fields{
				Jarijari: 7, Tinggi: 10,
			},
			want: float64(22.65247584249853),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tr := Tabung{
				Jarijari: tt.fields.Jarijari,
				Tinggi:   tt.fields.Tinggi,
			}
			if got := tr.KelilingAlas(); got != tt.want {
				t.Errorf("Tabung.KelilingAlas() = %v, want %v", got, tt.want)
			}
		})
	}
}

The way to execute testing is using the go test command. The -v or verbose argument is used to display all log output at the time of testing.

Run the program as below, it can be seen that none of the tests fail.

➜  tabung git:(main) ✗ go test -v
=== RUN   TestTabung_Volume
=== RUN   TestTabung_Volume/testing_hitung_volume
--- PASS: TestTabung_Volume (0.00s)
    --- PASS: TestTabung_Volume/testing_hitung_volume (0.00s)
=== RUN   TestTabung_Luas
=== RUN   TestTabung_Luas/testing_hitung_luas_permukaan
--- PASS: TestTabung_Luas (0.00s)
    --- PASS: TestTabung_Luas/testing_hitung_luas_permukaan (0.00s)
=== RUN   TestTabung_KelilingAlas
=== RUN   TestTabung_KelilingAlas/testing_hitung_keliling_alas
--- PASS: TestTabung_KelilingAlas (0.00s)
    --- PASS: TestTabung_KelilingAlas/testing_hitung_keliling_alas (0.00s)
PASS
ok      github.com/santekno/tabung      0.486s

Method Test

MethodUses
Log()Display logs
Logf()Displays logs using
Fail()Indicates that Fail() has occurred and the function testing process continues
FailNow()Indicates that Fail() has occurred and the function testing process has stopped
Failed()Display file report
Error()Log() followed by Fail()
Errorf()Logf() followed by Fail()
Fatal()Log() followed by failNow()
Fatalf()Logf() followed by failNow()
Skip()Log() followed by SkipNow()
Skipf()Logf() followed by SkipNow()
SkipNow()Stop the function testing process, proceed to function testing
Skiped()Displays the skip
Parallel()Sets that test execution is parallel

Test Command Notes

Command to view the coverage of unit tests in a project

➜  tabung git:(main) ✗ go test -coverprofile=coverage.out
PASS
coverage: 100.0% of statements
ok      github.com/santekno/tabung      0.750s

Command to see which code has covered the unit test

➜  tabung git:(main) ✗ go tool cover -html=coverage.out

Then the result will be ‘generated’ html in the form of a visualization of unit tests that have been covered or not

Command to regenerate using moq

$ cd <folder-yang-akan-di-generate>
$ go generate ./...

Also make sure that the interface function is added to the top of struct like this

// go:generate moq -out main_mock_test.go . UserRepositoryInterface

With a rule like this go:generate moq -out <mock-test-file-name> . <struct-interface-to-be-mocked>

comments powered by Disqus