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.
1package main
2
3func Average(xs []float64) float64 {
4 total := float64(0)
5 for _, x := range xs {
6 total += x
7 }
8 return total / float64(len(xs))
9}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.
1func TestAverageGenerate(t *testing.T) {
2 type args struct {
3 xs []float64
4 }
5 tests := []struct {
6 name string
7 args args
8 want float64
9 }{
10 {
11 name: "must 10",
12 args: args{
13 xs: []float64{10.0, 10.0},
14 },
15 want: float64(10),
16 },
17 }
18 for _, tt := range tests {
19 t.Run(tt.name, func(t *testing.T) {
20 if got := Average(tt.args.xs); got != tt.want {
21 t.Errorf("Average() = %v, want %v", got, tt.want)
22 }
23 })
24 }
25}Unit Test Program Odd Even
Create a function to determine that the program outputs the input information regarding odd and even numbers.
1func GanjilGenap(angka int) string {
2 if angka%2 == 0 {
3 return "genap"
4 }
5 return "ganjil"
6} 1package math
2
3import (
4 "testing"
5)
6
7func TestAverageGenerate(t *testing.T) {
8 type args struct {
9 xs []float64
10 }
11 tests := []struct {
12 name string
13 args args
14 want float64
15 }{
16 {
17 name: "must 10",
18 args: args{
19 xs: []float64{10.0, 10.0},
20 },
21 want: float64(10),
22 },
23 }
24 for _, tt := range tests {
25 t.Run(tt.name, func(t *testing.T) {
26 if got := Average(tt.args.xs); got != tt.want {
27 t.Errorf("Average() = %v, want %v", got, tt.want)
28 }
29 })
30 }
31}
32
33type testpair struct {
34 values []float64
35 average float64
36}
37
38var tests = []testpair{
39 {[]float64{1, 2}, 1.5},
40 {[]float64{1, 1, 1, 1, 1, 1}, 1},
41 {[]float64{-1, 1}, 0},
42}
43
44func TestAverage(t *testing.T) {
45 for _, pair := range tests {
46 v := Average(pair.values)
47 if v != pair.average {
48 }
49 }
50}
51
52func TestGanjilGenap(t *testing.T) {
53 type args struct {
54 angka int
55 }
56 tests := []struct {
57 name string
58 args args
59 want string
60 }{
61 {
62 name: "test case mengeluarkan ganjil",
63 args: args{
64 angka: 1,
65 },
66 want: "ganjil",
67 },
68 {
69 name: "test case mengeluarkan genap",
70 args: args{
71 angka: 2,
72 },
73 want: "genap",
74 },
75 {
76 name: "test case mengeluarkan -1",
77 args: args{
78 angka: -1,
79 },
80 want: "ganjil",
81 },
82 }
83 for _, tt := range tests {
84 t.Run(tt.name, func(t *testing.T) {
85 if got := GanjilGenap(tt.args.angka); got != tt.want {
86 t.Errorf("GanjilGenap() = %v, want %v", got, tt.want)
87 }
88 })
89 }
90}Testing for Tube
First, prepare a Tube struct. We will later use the object variables resulting from this struct as testing material.
1package math
2
3import "math"
4
5type Tabung struct {
6 Jarijari, Tinggi float64
7}
8
9func (t Tabung) Volume() float64 {
10 return math.Phi * math.Pow(t.Jarijari, 2) * t.Tinggi
11}
12
13func (t Tabung) Luas() float64 {
14 return 2 * math.Phi * t.Jarijari * (t.Jarijari + t.Tinggi)
15}
16
17func (t Tabung) KelilingAlas() float64 {
18 return 2 * math.Phi * t.Jarijari
19}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
1func TestTabung_Volume(t *testing.T) {
2 type fields struct {
3 Jarijari float64
4 Tinggi float64
5 }
6 tests := []struct {
7 name string
8 fields fields
9 want float64
10 }{
11 {
12 name: "testing hitung volume",
13 fields: fields{
14 Jarijari: 7, Tinggi: 10,
15 },
16 want: float64(792.8366544874485),
17 },
18 }
19 for _, tt := range tests {
20 t.Run(tt.name, func(t *testing.T) {
21 tr := Tabung{
22 Jarijari: tt.fields.Jarijari,
23 Tinggi: tt.fields.Tinggi,
24 }
25 if got := tr.Volume(); got != tt.want {
26 t.Errorf("Tabung.Volume() = %v, want %v", got, tt.want)
27 }
28 })
29 }
30}Unit Test for Surface Area function
1func TestTabung_Luas(t *testing.T) {
2 type fields struct {
3 Jarijari float64
4 Tinggi float64
5 }
6 tests := []struct {
7 name string
8 fields fields
9 want float64
10 }{
11 {
12 name: "testing hitung luas permukaan",
13 fields: fields{
14 Jarijari: 7, Tinggi: 10,
15 },
16 want: float64(385.092089322475),
17 },
18 }
19 for _, tt := range tests {
20 t.Run(tt.name, func(t *testing.T) {
21 tr := Tabung{
22 Jarijari: tt.fields.Jarijari,
23 Tinggi: tt.fields.Tinggi,
24 }
25 if got := tr.Luas(); got != tt.want {
26 t.Errorf("Tabung.Luas() = %v, want %v", got, tt.want)
27 }
28 })
29 }
30}Unit test for Base Perimeter
1func TestTabung_KelilingAlas(t *testing.T) {
2 type fields struct {
3 Jarijari float64
4 Tinggi float64
5 }
6 tests := []struct {
7 name string
8 fields fields
9 want float64
10 }{
11 {
12 name: "testing hitung keliling alas",
13 fields: fields{
14 Jarijari: 7, Tinggi: 10,
15 },
16 want: float64(22.65247584249853),
17 },
18 }
19 for _, tt := range tests {
20 t.Run(tt.name, func(t *testing.T) {
21 tr := Tabung{
22 Jarijari: tt.fields.Jarijari,
23 Tinggi: tt.fields.Tinggi,
24 }
25 if got := tr.KelilingAlas(); got != tt.want {
26 t.Errorf("Tabung.KelilingAlas() = %v, want %v", got, tt.want)
27 }
28 })
29 }
30}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.
1➜ tabung git:(main) ✗ go test -v
2=== RUN TestTabung_Volume
3=== RUN TestTabung_Volume/testing_hitung_volume
4--- PASS: TestTabung_Volume (0.00s)
5 --- PASS: TestTabung_Volume/testing_hitung_volume (0.00s)
6=== RUN TestTabung_Luas
7=== RUN TestTabung_Luas/testing_hitung_luas_permukaan
8--- PASS: TestTabung_Luas (0.00s)
9 --- PASS: TestTabung_Luas/testing_hitung_luas_permukaan (0.00s)
10=== RUN TestTabung_KelilingAlas
11=== RUN TestTabung_KelilingAlas/testing_hitung_keliling_alas
12--- PASS: TestTabung_KelilingAlas (0.00s)
13 --- PASS: TestTabung_KelilingAlas/testing_hitung_keliling_alas (0.00s)
14PASS
15ok github.com/santekno/tabung 0.486sMethod Test
| Method | Uses |
|---|---|
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
1➜ tabung git:(main) ✗ go test -coverprofile=coverage.out
2PASS
3coverage: 100.0% of statements
4ok github.com/santekno/tabung 0.750sCommand to see which code has covered the unit test
1➜ tabung git:(main) ✗ go tool cover -html=coverage.outThen 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
1$ cd <folder-yang-akan-di-generate>
2$ go generate ./...Also make sure that the interface function is added to the top of struct like this
1// go:generate moq -out main_mock_test.go . UserRepositoryInterfaceWith a rule like this go:generate moq -out <mock-test-file-name> . <struct-interface-to-be-mocked>