86. Studi Kasus: Testing Layanan CRUD dengan gRPC
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:
1syntax = "proto3";
2
3package book;
4
5service BookService {
6 rpc CreateBook (Book) returns (BookId) {}
7 rpc GetBook (BookId) returns (Book) {}
8 rpc UpdateBook (Book) returns (Book) {}
9 rpc DeleteBook (BookId) returns (Empty) {}
10}
11
12message Book {
13 string id = 1;
14 string title = 2;
15 string author = 3;
16}
17
18message BookId {
19 string id = 1;
20}
21
22message 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.
1bookservice/
2 ├── proto/
3 │ └── book.proto
4 ├── main.go
5 ├── server.go
6 ├── repository.go
7 └── repository_test.goImplementasi Server Sederhana
Bagian server akan menghandle request CRUD. Repository di-abstract agar memudahkan testing:
1// repository.go
2type Book struct {
3 ID string
4 Title string
5 Author string
6}
7
8type BookRepository interface {
9 Create(book Book) (string, error)
10 Get(id string) (Book, error)
11 Update(book Book) error
12 Delete(id string) error
13}
14
15type InMemoryBookRepo struct {
16 books map[string]Book
17}
18
19func (r *InMemoryBookRepo) Create(book Book) (string, error) {
20 r.books[book.ID] = book
21 return book.ID, nil
22}
23
24func (r *InMemoryBookRepo) Get(id string) (Book, error) {
25 if book, ok := r.books[id]; ok {
26 return book, nil
27 }
28 return Book{}, errors.New("not found")
29}
30
31func (r *InMemoryBookRepo) Update(book Book) error {
32 if _, ok := r.books[book.ID]; ok {
33 r.books[book.ID] = book
34 return nil
35 }
36 return errors.New("not found")
37}
38
39func (r *InMemoryBookRepo) Delete(id string) error {
40 if _, ok := r.books[id]; ok {
41 delete(r.books, id)
42 return nil
43 }
44 return errors.New("not found")
45}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:
1// repository_test.go
2func TestInMemoryBookRepo_CRUD(t *testing.T) {
3 repo := &InMemoryBookRepo{books: map[string]Book{}}
4 book := Book{ID: "1", Title: "Go in Action", Author: "John Doe"}
5
6 // Test Create
7 id, err := repo.Create(book)
8 if err != nil || id != "1" {
9 t.Errorf("Create failed, got id=%v, err=%v", id, err)
10 }
11
12 // Test Read
13 b, err := repo.Get("1")
14 if err != nil || b.Title != "Go in Action" {
15 t.Errorf("Get failed, got book=%v, err=%v", b, err)
16 }
17
18 // Test Update
19 book.Title = "Go Lang in Action"
20 err = repo.Update(book)
21 if err != nil {
22 t.Errorf("Update failed: %v", err)
23 }
24
25 // Test Delete
26 err = repo.Delete("1")
27 if err != nil {
28 t.Errorf("Delete failed: %v", err)
29 }
30}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.
1// integration_test.go (pseudo)
2func TestBookServiceIntegration(t *testing.T) {
3 // 1. Jalankan server (tanpa perlu listen di port production)
4 lis := bufconn.Listen(1024 * 1024)
5 s := grpc.NewServer()
6 repo := &InMemoryBookRepo{books: map[string]Book{}}
7 RegisterBookServiceServer(s, &BookServiceServer{repo: repo})
8 go s.Serve(lis)
9 defer s.Stop()
10
11 // 2. Set client dengan custom dial
12 ctx := context.Background()
13 conn, _ := grpc.DialContext(
14 ctx, "bufnet",
15 grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
16 return lis.Dial()
17 }),
18 grpc.WithInsecure(),
19 )
20 defer conn.Close()
21 client := NewBookServiceClient(conn)
22
23 // 3. Proses uji integrasi end-to-end
24 resp, err := client.CreateBook(ctx, &Book{Id: "123", Title: "gRPC for Dummies", Author: "Jane"})
25 // 4. Assert validations
26 if err != nil || resp.Id != "123" {
27 t.Fatalf("Expected Id=123, got %v, err=%v", resp.Id, err)
28 }
29}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!