pemrograman

Cara Membuat Unit Test Pada Golang

Unit Testing Menggunakan Library Go

Pemrograman tidak mudah bahkan programmer terbaik tidak mampu menulis program yang bekerja persis seperti yang diinginkan setiap saat. Oleh karena itu bagian penting dari proses pengembangan perangkat lunak adalah pengujian (testing). Menulis test untuk kode kita adalah cara yang baik untuk memastikan kualitas dan meningkatkan keandalan.

Go menyediakan package testing, berisikan banyak sekali tools untuk keperluan unit test. Pada chapter ini kita akan belajar mengenai testing, benchmark, dan juga testing menggunakan testify.

Go menyertakan program khusus yang membuat tes menulis lebih mudah, jadi mari kita buat beberapa test untuk paket yang kita buat di sesi ini. Di folder 01-math buat file baru bernama math_test.go yang berisi ini.

Sebelumnya kita telah buat suatu function sebagai berikut ini.

package main

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

agar kita bisa membuat unit test maka kita generate menggunakan vscode, maka akan membuat file baru math_test.go dan men-generate fungsi TestAverage dengan isi dibawah ini.

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 Ganjil Genap

Buat fungsi untuk menentukan bahwa program tersebut mengeluarkan informasi yang diinput terkait angka ganjil dan genap.

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 Tabung

Pertama siapkan terlebih dahulu sebuah struct Tabung. Variabel object hasil struct ini nantinya kita gunakan sebagai bahan testing.

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
}

Maka kita akan membuat unit test satu-satu dari fungsi yang sudah kita buat. Bisa dilihat sebagai berikut.

Unit Test untuk fungsi Volume

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 untuk fungsi Luas Permukaan

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 untuk Keliling Alas

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)
			}
		})
	}
}

Cara eksekusi testing adalah menggunakan command go test. Argument -v atau verbose digunakan menampilkan semua output log pada saat pengujian.

Jalankan program seperti di bawah ini, terlihat bahwa tidak ada test yang 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

MethodKegunaan
Log()Menampilkan log
Logf()Menampilkan log menggunakan format
Fail()Menandakan terjadi Fail() dan proses testing fungsi tetap diteruskan
FailNow()Menandakan terjadi Fail() dan proses testing fungsi dihentikan
Failed()Menampilkan laporan fail
Error()Log() diikuti dengan Fail()
Errorf()Logf() diikuti dengan Fail()
Fatal()Log() diikuti dengan failNow()
Fatalf()Logf() diikuti dengan failNow()
Skip()Log() diikuti dengan SkipNow()
Skipf()Logf() diikuti dengan SkipNow()
SkipNow()Menghentikan proses testing fungsi, dilanjutkan ke testing fungsi
Skiped()Menampilkan laporan skip
Parallel()Menge-set bahwa eksekusi testing adalah parallel

Catatan Perintah Test

Perintah untuk melihat coverage dari unit test dalam projek

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

Perintah untuk melihat code mana saja yang sudah cover unit test

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

Maka hasilnya akan generate html yang berupa visualiasi dari unit test yg sudah tercover ataupun yang belum

Perintah untuk melakukan generate ulang menggunakan moq

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

Pastikan juga pada fungsi interface ditambahkan paling atas struct seperti ini

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

dengan aturan seperti ini go:generate moq -out <nama-file-mock-test> . <struct-interface-yang-akan-dibuat-mock>

comments powered by Disqus