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
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
➜ 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>