Go Testing di 2026: Unit Test, Mock, dan Pertanyaan Wawancara Teknis

Kuasai testing Go dengan table-driven test, mock, dan pola yang diharapkan pewawancara. Contoh praktis dengan testify dan gomock.

Go Testing di 2026: Unit Test, Mock, dan Pertanyaan Wawancara Teknis

Testing di Go bergantung pada package standar testing, yang tersedia di setiap instalasi Go tanpa memerlukan dependensi eksternal. Berbeda dengan framework di bahasa lain, pendekatan Go mengutamakan kesederhanaan: file test berada di direktori yang sama dengan kode produksi, fungsi test mengikuti konvensi penamaan, dan perintah go test menangani penemuan dan eksekusi secara otomatis.

Apa yang dicari pewawancara

Kandidat yang menulis table-driven test, menggunakan interface untuk dependency injection, dan memahami kapan mocking memberikan nilai versus kapan hanya menambah kompleksitas.

Setiap file test diakhiri dengan _test.go dan berada di package yang sama dengan kode yang diuji. Fungsi test dimulai dengan Test diikuti nama dengan huruf kapital. Parameter *testing.T menyediakan method untuk melaporkan kegagalan.

calculator_test.gogo
package calculator

import "testing"

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("Add(2, 3) = %d; want 5", result)
    }
}

Menjalankan go test ./... mengeksekusi semua test di modul saat ini. Flag -v menampilkan nama test individual dan status berhasil/gagal.

Table-Driven Test: Standar Go

Table-driven test mengurangi duplikasi dan membuat penambahan kasus baru menjadi trivial. Setiap baris di tabel merepresentasikan satu skenario dengan input dan output yang diharapkan. Pola ini muncul di library standar Go dan di codebase produksi di seluruh industri.

validator_test.gogo
package validator

import "testing"

func TestValidateEmail(t *testing.T) {
    tests := []struct {
        name    string
        email   string
        wantErr bool
    }{
        {"valid email", "user@example.com", false},
        {"missing at sign", "userexample.com", true},
        {"empty string", "", true},
        {"unicode local part", "user@例え.jp", false},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            err := ValidateEmail(tt.email)
            if (err != nil) != tt.wantErr {
                t.Errorf("ValidateEmail(%q) error = %v, wantErr %v",
                    tt.email, err, tt.wantErr)
            }
        })
    }
}

Method t.Run membuat subtest yang berjalan secara independen. Subtest yang gagal melaporkan kasus spesifik mana yang gagal tanpa menghentikan kasus lainnya.

Dependency Injection Melalui Interface

Interface Go memungkinkan pengujian kode yang bergantung pada sistem eksternal: database, HTTP client, file system. Mendefinisikan interface minimal di titik penggunaan memungkinkan penggantian implementasi nyata dengan test double.

service.gogo
package order

// Repository defines the data access methods this service needs
type Repository interface {
    FindByID(id string) (*Order, error)
    Save(order *Order) error
}

// Service handles order business logic
type Service struct {
    repo Repository
}

// NewService creates a service with the given repository
func NewService(repo Repository) *Service {
    return &Service{repo: repo}
}

// Process validates and saves an order
func (s *Service) Process(order *Order) error {
    if order.Total <= 0 {
        return ErrInvalidTotal
    }
    return s.repo.Save(order)
}

Service menerima tipe apapun yang memenuhi Repository. Kode produksi meneruskan implementasi berbasis database. Test meneruskan mock atau stub.

Mocking dengan gomock dan mockgen

Package gomock menghasilkan implementasi mock dari definisi interface. Perintah mockgen membaca file sumber dan menghasilkan struct mock yang merekam pemanggilan dan mengembalikan nilai yang dikonfigurasi.

bash
# Generate mocks for the Repository interface
mockgen -source=service.go -destination=mocks/repository_mock.go -package=mocks

Menggunakan mock yang dihasilkan dalam test:

service_test.gogo
package order

import (
    "testing"

    "github.com/stretchr/testify/assert"
    "go.uber.org/mock/gomock"

    "yourmodule/order/mocks"
)

func TestService_Process_SavesValidOrder(t *testing.T) {
    ctrl := gomock.NewController(t)
    mockRepo := mocks.NewMockRepository(ctrl)

    order := &Order{ID: "123", Total: 99.99}

    // Expect Save to be called once with this order, return nil
    mockRepo.EXPECT().
        Save(order).
        Return(nil).
        Times(1)

    svc := NewService(mockRepo)
    err := svc.Process(order)

    assert.NoError(t, err)
}

func TestService_Process_RejectsZeroTotal(t *testing.T) {
    ctrl := gomock.NewController(t)
    mockRepo := mocks.NewMockRepository(ctrl)

    // No calls expected to repo since validation fails first

    svc := NewService(mockRepo)
    err := svc.Process(&Order{ID: "456", Total: 0})

    assert.ErrorIs(t, err, ErrInvalidTotal)
}

Mock memverifikasi bahwa Save dipanggil tepat satu kali dengan argumen yang diharapkan. Jika kode yang diuji tidak pernah memanggil Save, atau memanggilnya dengan argumen berbeda, test gagal.

Menggunakan testify untuk Assertion dan Suite

testify menyediakan fungsi assertion yang menghasilkan pesan kegagalan lebih jelas dibanding pengecekan if manual. Package assert melanjutkan eksekusi setelah kegagalan. Package require menghentikan test segera.

user_test.gogo
package user

import (
    "testing"

    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

func TestParseUser(t *testing.T) {
    input := `{"id": "u1", "name": "Alice"}`

    user, err := ParseUser(input)

    require.NoError(t, err, "parsing should not fail")
    assert.Equal(t, "u1", user.ID)
    assert.Equal(t, "Alice", user.Name)
    assert.Empty(t, user.Email, "email should default to empty")
}

Panggilan require.NoError menghentikan test jika parsing gagal, mencegah panic nil pointer pada assertion berikutnya.

Siap menguasai wawancara Go Anda?

Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.

Testing HTTP Handler dengan httptest

Package net/http/httptest menyediakan ResponseRecorder untuk menangkap output handler dan Server untuk menjalankan server test lokal. Sebagian besar unit test menggunakan ResponseRecorder untuk menghindari overhead jaringan.

handler_test.gogo
package api

import (
    "net/http"
    "net/http/httptest"
    "testing"

    "github.com/stretchr/testify/assert"
)

func TestHealthHandler(t *testing.T) {
    req := httptest.NewRequest(http.MethodGet, "/health", nil)
    rec := httptest.NewRecorder()

    HealthHandler(rec, req)

    assert.Equal(t, http.StatusOK, rec.Code)
    assert.JSONEq(t, `{"status": "ok"}`, rec.Body.String())
}

Untuk handler yang memanggil layanan eksternal, inject mock client atau gunakan httptest.NewServer untuk membuat backend palsu.

Pertanyaan Wawancara Umum tentang Go Testing

Pewawancara menilai pengetahuan testing melalui pertanyaan yang mengungkap pengalaman dengan codebase nyata. Berikut pola yang membedakan kandidat yang siap:

"Bagaimana Anda menguji fungsi yang memanggil API eksternal?"

Definisikan interface untuk HTTP client, inject ke dalam fungsi atau struct, dan sediakan mock yang mengembalikan respons yang telah ditentukan. Ini menghindari panggilan jaringan dan membuat test deterministik.

"Kapan menggunakan stub versus mock?"

Stub mengembalikan respons yang telah ditentukan tanpa memverifikasi bagaimana mereka dipanggil. Mock memverifikasi bahwa method tertentu dipanggil dengan argumen tertentu. Gunakan stub ketika nilai kembalian lebih penting daripada interaksi. Gunakan mock ketika memverifikasi interaksi adalah tujuan test.

"Apa itu table-driven test dan mengapa Go menyukainya?"

Table-driven test mendefinisikan kasus test sebagai data dalam slice of struct. Mereka mengurangi boilerplate, membuat penambahan kasus menjadi trivial, dan menghasilkan output test yang mudah dibaca dengan t.Run. Pola ini selaras dengan preferensi Go untuk kode eksplisit dan repetitif daripada abstraksi yang rumit.

"Bagaimana menangani fixture test atau setup yang digunakan bersama oleh beberapa test?"

Gunakan TestMain untuk setup dan teardown level package. Gunakan fungsi helper atau test suite (dari testify) untuk setup bersama antar test terkait. Hindari state global yang menciptakan coupling antar test.

Mempersiapkan wawancara Go? Modul testing Go membahas pola-pola ini secara mendalam dengan pertanyaan latihan.

Menjalankan Test dengan Coverage dan Race Detection

Perintah go test menerima flag yang mengungkap jalur kode yang belum diuji dan bug concurrency.

bash
# Run tests with coverage report
go test -cover ./...

# Generate HTML coverage visualization
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html

# Detect race conditions (slower, but catches real bugs)
go test -race ./...

Race detector menginstrumentasi akses memori dan menandai pembacaan dan penulisan bersamaan ke variabel yang sama. Menjalankan test dengan -race di CI menangkap bug yang hanya muncul pada timing tertentu.

Mengorganisir Test di Codebase Besar

Seiring pertumbuhan proyek, organisasi test mempengaruhi maintainability:

  • Same package test (package foo) mengakses fungsi dan field yang tidak diekspor. Gunakan untuk unit test yang memverifikasi perilaku internal.
  • External package test (package foo_test) mengimpor package seperti kode eksternal. Gunakan untuk integration test dan memverifikasi public API.
  • Direktori testdata menyimpan fixture seperti file JSON, golden output, atau seed database. Go mengabaikan ini saat build.
  • Build tag memisahkan unit test dari integration test yang memerlukan sistem eksternal. Jalankan go test -tags=integration ./... untuk menyertakannya.

Untuk lebih lanjut tentang best practice Go di luar testing, artikel pola concurrency membahas goroutine dan channel.

Hal yang Perlu Diingat tentang Go Testing

  • Package testing tersedia bersama Go dan tidak memerlukan dependensi eksternal
  • Table-driven test mengurangi duplikasi dan merupakan pola yang diharapkan dalam wawancara
  • Interface memungkinkan dependency injection; definisikan di titik penggunaan, bukan di implementasi
  • gomock menghasilkan mock dari interface; testify menyediakan assertion yang lebih bersih
  • httptest.ResponseRecorder menangkap output handler tanpa overhead jaringan
  • Flag -race mendeteksi bug concurrency yang tidak tertangkap unit test biasa
  • Metrik coverage memandu perhatian tetapi tidak menjamin kebenaran

Mulai berlatih!

Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.

Tantangan harian

Bisakah kamu menemukan bug di Go?

Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Anthony Fillion-Maillet

Ditulis oleh

Anthony Fillion-Maillet

Pendiri SharpSkill

Developer fullstack selama lebih dari 10 tahun. Ia menjalankan SharpSkill dan bertanggung jawab atas semua yang diterbitkan di sini.

Diperbarui 24 Agustus 2026

Bagikan

Artikel terkait