Go Testing năm 2026: Unit Test, Mock và Câu hỏi Phỏng vấn Kỹ thuật
Thành thạo testing Go với table-driven test, mock và các pattern mà nhà tuyển dụng mong đợi. Ví dụ thực tế với testify và gomock.

Testing trong Go dựa vào package chuẩn testing, được tích hợp sẵn trong mọi bản cài đặt Go và không yêu cầu bất kỳ dependency bên ngoài nào. Khác với các framework trong các ngôn ngữ khác, cách tiếp cận của Go ưu tiên sự đơn giản: các file test nằm cùng thư mục với code production, các hàm test tuân theo quy ước đặt tên, và lệnh go test xử lý việc tìm kiếm và thực thi tự động.
Ứng viên viết table-driven test, sử dụng interface cho dependency injection, và hiểu khi nào mocking mang lại giá trị so với khi nào chỉ tạo thêm phức tạp.
Viết Unit Test với Package testing
Mọi file test kết thúc bằng _test.go và nằm trong cùng package với code cần test. Các hàm test bắt đầu bằng Test theo sau là tên viết hoa. Tham số *testing.T cung cấp các method để báo cáo lỗi.
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)
}
}Chạy go test ./... sẽ thực thi tất cả test trong module hiện tại. Flag -v hiển thị tên từng test và trạng thái pass/fail.
Table-Driven Test: Tiêu chuẩn của Go
Table-driven test giảm thiểu trùng lặp và làm cho việc thêm case mới trở nên đơn giản. Mỗi hàng trong bảng đại diện cho một kịch bản với input và output mong đợi. Pattern này xuất hiện trong thư viện chuẩn của Go và trong các codebase production trên toàn ngành.
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 tạo các subtest chạy độc lập. Các subtest thất bại sẽ báo cáo case cụ thể nào bị lỗi mà không dừng các case khác.
Dependency Injection thông qua Interface
Interface trong Go cho phép test code phụ thuộc vào các hệ thống bên ngoài: database, HTTP client, file system. Việc định nghĩa interface tối thiểu tại điểm sử dụng cho phép thay thế implementation thực bằng test double.
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 chấp nhận bất kỳ type nào thỏa mãn Repository. Code production truyền implementation dựa trên database. Test truyền mock hoặc stub.
Mocking với gomock và mockgen
Package gomock tạo các implementation mock từ định nghĩa interface. Lệnh mockgen đọc file nguồn và tạo ra các struct mock ghi lại các lần gọi và trả về giá trị được cấu hình.
# Generate mocks for the Repository interface
mockgen -source=service.go -destination=mocks/repository_mock.go -package=mocksSử dụng mock đã tạo trong test:
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 xác minh rằng Save được gọi đúng một lần với argument mong đợi. Nếu code đang test không bao giờ gọi Save, hoặc gọi với argument khác, test sẽ thất bại.
Sử dụng testify cho Assertion và Suite
testify cung cấp các hàm assertion tạo ra thông báo lỗi rõ ràng hơn so với kiểm tra if thủ công. Package assert tiếp tục thực thi sau khi thất bại. Package require dừng test ngay lập tức.
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")
}Lệnh gọi require.NoError dừng test nếu parsing thất bại, ngăn chặn panic nil pointer ở các assertion tiếp theo.
Sẵn sàng chinh phục phỏng vấn Go?
Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.
Testing HTTP Handler với httptest
Package net/http/httptest cung cấp ResponseRecorder để capture output của handler và Server để chạy test server local. Hầu hết unit test sử dụng ResponseRecorder để tránh overhead mạng.
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())
}Đối với handler gọi các dịch vụ bên ngoài, inject mock client hoặc sử dụng httptest.NewServer để tạo backend giả.
Câu hỏi Phỏng vấn Phổ biến về Go Testing
Nhà tuyển dụng đánh giá kiến thức testing thông qua các câu hỏi bộc lộ kinh nghiệm với codebase thực tế. Dưới đây là các pattern phân biệt ứng viên đã chuẩn bị:
"Làm thế nào để test một hàm gọi API bên ngoài?"
Định nghĩa interface cho HTTP client, inject vào hàm hoặc struct, và cung cấp mock trả về response đã định trước. Điều này tránh các cuộc gọi mạng và làm cho test deterministic.
"Khi nào sử dụng stub so với mock?"
Stub trả về response cố định mà không xác minh cách chúng được gọi. Mock xác minh rằng các method cụ thể được gọi với argument cụ thể. Sử dụng stub khi giá trị trả về quan trọng hơn tương tác. Sử dụng mock khi xác minh tương tác là mục đích của test.
"Table-driven test là gì và tại sao Go ưa chuộng chúng?"
Table-driven test định nghĩa các test case dưới dạng dữ liệu trong slice of struct. Chúng giảm boilerplate, làm cho việc thêm case trở nên đơn giản, và tạo ra output test dễ đọc với t.Run. Pattern này phù hợp với sở thích của Go về code rõ ràng, lặp lại thay vì abstraction phức tạp.
"Làm thế nào để xử lý fixture test hoặc setup mà nhiều test chia sẻ?"
Sử dụng TestMain cho setup và teardown cấp package. Sử dụng helper function hoặc test suite (từ testify) cho setup chung giữa các test liên quan. Tránh state toàn cục tạo ra coupling giữa các test.
Chuẩn bị cho phỏng vấn Go? Module testing Go đề cập các pattern này chi tiết với câu hỏi luyện tập.
Chạy Test với Coverage và Race Detection
Lệnh go test chấp nhận các flag bộc lộ các đường dẫn code chưa được test và bug concurrency.
# 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 instrument các truy cập bộ nhớ và đánh dấu các đọc/ghi đồng thời vào cùng một biến. Chạy test với -race trong CI bắt được các bug chỉ xuất hiện ở timing cụ thể.
Tổ chức Test trong Codebase Lớn
Khi dự án phát triển, tổ chức test ảnh hưởng đến khả năng bảo trì:
- Same package test (
package foo) truy cập các hàm và field không export. Sử dụng cho unit test xác minh hành vi nội bộ. - External package test (
package foo_test) import package như code bên ngoài. Sử dụng cho integration test và xác minh public API. - Thư mục testdata lưu trữ fixture như file JSON, golden output, hoặc seed database. Go bỏ qua chúng khi build.
- Build tag tách unit test khỏi integration test yêu cầu hệ thống bên ngoài. Chạy
go test -tags=integration ./...để bao gồm chúng.
Để tìm hiểu thêm về best practice Go ngoài testing, bài viết về concurrency pattern đề cập đến goroutine và channel.
Những Điều Cần Nhớ về Go Testing
- Package
testingđi kèm với Go và không yêu cầu dependency bên ngoài - Table-driven test giảm trùng lặp và là pattern được mong đợi trong phỏng vấn
- Interface cho phép dependency injection; định nghĩa tại điểm sử dụng, không phải tại implementation
gomocktạo mock từ interface;testifycung cấp assertion sạch hơnhttptest.ResponseRecordercapture output handler mà không có overhead mạng- Flag
-racephát hiện bug concurrency mà unit test thông thường bỏ lỡ - Các chỉ số coverage hướng dẫn sự chú ý nhưng không đảm bảo tính đúng đắn
Bắt đầu luyện tập!
Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.
Bạn có tìm ra lỗi trong Go không?
Một đoạn mã thật, một lỗi ẩn, mỗi ngày một lượt. Không cần tài khoản để thử.

Viết bởi
Anthony Fillion-MailletNgười sáng lập SharpSkill
Lập trình viên fullstack hơn 10 năm. Anh điều hành SharpSkill và chịu trách nhiệm về mọi nội dung đăng tại đây.
Cập nhật ngày 24 tháng 8, 2026
Chia sẻ
Bài viết liên quan

Profiling và Benchmarking Go 2026: pprof, trace và Câu hỏi Phỏng vấn
Hướng dẫn toàn diện về profiling Go với pprof và runtime/trace. Kỹ thuật phân tích CPU, bộ nhớ và goroutine để tối ưu hiệu năng và chuẩn bị phỏng vấn.

Interface Go Nâng Cao 2026: Composition, Type Assertion và Câu Hỏi Phỏng Vấn
Hướng dẫn chuyên sâu về interface Go hiện đại: composition interface, type assertion an toàn, generic tự tham chiếu, và các câu hỏi phỏng vấn thường gặp cho lập trình viên Go.

Go Context Package 2026: Hủy bỏ, Timeout và Câu hỏi Phỏng vấn
Làm chủ package context Go để hủy bỏ, timeout và request-scoped values. Hướng dẫn đầy đủ với ví dụ code và câu hỏi phỏng vấn.