86. Studi Kasus: Testing Layanan CRUD dengan gRPC
Dewasa ini, pengembangan aplikasi skala besar cenderung mengadopsi arsitektur microservices serta protokol komunikasi yang efisien, seperti gRPC. Banyak perusahaan menjadikan gRPC pilihan utama karena performa tinggi dan schema-driven API menggunakan Protocol Buffers. Namun, tantangan terbesar yang kerap muncul adalah bagaimana melakukan pengujian (testing) dengan efektif, khususnya untuk layanan CRUD (Create, Read, Update, Delete).
Pada artikel ini, saya ingin mengajak Anda menyelami sebuah studi kasus nyata mengenai testing layanan CRUD berbasis gRPC. Saya akan membahas struktur layanan, strategi pengujian unit, integration testing, hingga automatisasi pengujian. Simulasi kode akan menggunakan bahasa Go (Golang) karena popularitasnya dalam ekosistem gRPC.
Latar Belakang Masalah
Bayangkan sebuah tim mengembangkan service BookService yang menawarkan operasi CRUD pada entitas Book. Layanan ini harus cepat, andal, dan mudah untuk diuji. Pengujian otomatis diperlukan demi memastikan perubahan kode tidak menimbulkan regresi. Tes juga harus mudah dijalankan, baik di lingkungan lokal maupun CI/CD pipeline.
Diagram Alur CRUD di BookService
flowchart TD
C(Client) -->|CreateRequest| S(BookService)
C(Client) -->|ReadRequest| S(BookService)
C(Client) -->|UpdateRequest| S(BookService)
C(Client) -->|DeleteRequest| S(BookService)
subgraph S
direction TB
DB[(Database)]
end
S --> DB
Definisi Protobuf
Langkah pertama dalam pengembangan layanan gRPC adalah mendeskripsikan API dengan protobuf:
syntax = "proto3";
package book;
service BookService {
rpc CreateBook (Book) returns (BookId) {}
rpc GetBook (BookId) returns (Book) {}
rpc UpdateBook (Book) returns (Book) {}
rpc DeleteBook (BookId) returns (Empty) {}
}
message Book {
string id = 1;
string title = 2;
string author = 3;
}
message BookId {
string id = 1;
}
message Empty {}
Dengan skema ini, struktur API jelas, mudah didokumentasi dan dikonsumsi oleh klien lintas bahasa.
Struktur Proyek Go
Mari simulasikan struktur direktori sederhana untuk BookService.
bookservice/
├── proto/
│ └── book.proto
├── main.go
├── server.go
├── repository.go
└── repository_test.go
Implementasi Server Sederhana
Bagian server akan menghandle request CRUD. Repository di-abstract agar memudahkan testing:
// repository.go
type Book struct {
ID string
Title string
Author string
}
type BookRepository interface {
Create(book Book) (string, error)
Get(id string) (Book, error)
Update(book Book) error
Delete(id string) error
}
type InMemoryBookRepo struct {
books map[string]Book
}
func (r *InMemoryBookRepo) Create(book Book) (string, error) {
r.books[book.ID] = book
return book.ID, nil
}
func (r *InMemoryBookRepo) Get(id string) (Book, error) {
if book, ok := r.books[id]; ok {
return book, nil
}
return Book{}, errors.New("not found")
}
func (r *InMemoryBookRepo) Update(book Book) error {
if _, ok := r.books[book.ID]; ok {
r.books[book.ID] = book
return nil
}
return errors.New("not found")
}
func (r *InMemoryBookRepo) Delete(id string) error {
if _, ok := r.books[id]; ok {
delete(r.books, id)
return nil
}
return errors.New("not found")
}
Pengujian Unit: Fokus pada Repository
Sebelum menguji service penuh, pengujian unit pada repository penting dilakukan. Tujuannya untuk memastikan logika dasar CRUD sudah benar, independen dari gRPC. Berikut contoh test case menggunakan testing package di Go:
// repository_test.go
func TestInMemoryBookRepo_CRUD(t *testing.T) {
repo := &InMemoryBookRepo{books: map[string]Book{}}
book := Book{ID: "1", Title: "Go in Action", Author: "John Doe"}
// Test Create
id, err := repo.Create(book)
if err != nil || id != "1" {
t.Errorf("Create failed, got id=%v, err=%v", id, err)
}
// Test Read
b, err := repo.Get("1")
if err != nil || b.Title != "Go in Action" {
t.Errorf("Get failed, got book=%v, err=%v", b, err)
}
// Test Update
book.Title = "Go Lang in Action"
err = repo.Update(book)
if err != nil {
t.Errorf("Update failed: %v", err)
}
// Test Delete
err = repo.Delete("1")
if err != nil {
t.Errorf("Delete failed: %v", err)
}
}
Manfaat Pengujian Unit
- Isolasi: Memastikan komponen repository berjalan seperti yang diharapkan, terlepas dari protokol komunikasi.
- Kecepatan: Test berjalan sangat cepat.
Integration Testing dengan gRPC
Selanjutnya, kita perlu menguji apakah service server gRPC benar-benar meng-translate request menjadi operasi CRUD yang benar.
Setup Integration Test
- Spin up server gRPC dengan repository in-memory.
- Kirim request ke server menggunakan client gRPC secara langsung.
// integration_test.go (pseudo)
func TestBookServiceIntegration(t *testing.T) {
// 1. Jalankan server (tanpa perlu listen di port production)
lis := bufconn.Listen(1024 * 1024)
s := grpc.NewServer()
repo := &InMemoryBookRepo{books: map[string]Book{}}
RegisterBookServiceServer(s, &BookServiceServer{repo: repo})
go s.Serve(lis)
defer s.Stop()
// 2. Set client dengan custom dial
ctx := context.Background()
conn, _ := grpc.DialContext(
ctx, "bufnet",
grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return lis.Dial()
}),
grpc.WithInsecure(),
)
defer conn.Close()
client := NewBookServiceClient(conn)
// 3. Proses uji integrasi end-to-end
resp, err := client.CreateBook(ctx, &Book{Id: "123", Title: "gRPC for Dummies", Author: "Jane"})
// 4. Assert validations
if err != nil || resp.Id != "123" {
t.Fatalf("Expected Id=123, got %v, err=%v", resp.Id, err)
}
}
Tabel Hasil Pengujian Skenario CRUD
| Operasi | Input | Output / Ekspetasi | Status |
|---|---|---|---|
| Create | id=123, title=“gRPC for Dummies” | Success, id=123 | Pass |
| Read | id=123 | Book ditemukan | Pass |
| Update | id=123, title=“gRPC 101” | Book diperbarui | Pass |
| Delete | id=123 | Book dihapus | Pass |
| Read | id=999 (belum ada) | Error: not found | Pass |
Automasi Testing di CI/CD Pipeline
Testing service gRPC di atas bisa langsung diotomasi dalam pipeline CI-CD (misal dengan Github Actions atau Gitlab CI). Kunci utama keberhasilan: tidak ada dependency ke infrastruktur eksternal, servis bisa spin-up dan testing sendiri dengan dependency in-memory.
Tips Otomasi Testing:
- Gunakan test double untuk resource eksternal (e.g. in-memory or mock).
- Pastikan coverage test minimal untuk seluruh fitur CRUD.
- Pisahkan test unit vs integration (misal dengan suffix
_test.go).
Key Takeaways
- gRPC sangat powerful untuk layanan high performance dan schema-first.
- Testing CRUD wajib dilakukan di dua level: unit (logic) dan integration (antarmuka gRPC).
- Strategy dengan in-memory repository memudahkan isolasi dan otomasi test di pipeline.
- Test coverage tidak boleh hanya di operation “bahagia” (happy case), tapi juga kasus limit & error.
Kesimpulan
Testing layanan CRUD berbasis gRPC bisa dibuat sederhana dan powerful dengan strategi modularisasi komponen, penulisan test unit & integration yang sistematis, serta mengandalkan in-memory dependency. Dengan pendekatan ini error lebih cepat ditemukan, development velocity meningkat, serta codebase lebih tangguh menghadapi perubahan.
Secara praktis, model seperti ini mudah direplikasi untuk layanan microservice lain, dan membangun fondasi engineering excellence yang kuat di tim pengembang Anda.
Referensi:
- grpc-go/testing
- “gRPC - Google’s high performance, open source universal RPC framework”
- Go testing pkg
Semoga studi kasus ini menambah insight Anda dalam membangun dan menguji layanan CRUD gRPC yang scalable dan reliable!