Skip to content
Santekno.com | Level Up Your Engineering Skills
ID
📖 0%
24 Sep 2026 · 22 mnt baca ·Artikel 52 / 208
Go

TATD: Test-Driven AI Development Golang — Workflow Red-Green-Refactor dengan AI

Test-Driven AI Development untuk Go: workflow tests-first dengan AI yang menghasilkan kode berkualitas tinggi. Red-Green-Refactor dengan Claude Code, Cursor, dan tools lain.

IH
Ihsan Arif
Penulis di Santekno · Backend Engineer

Test-Driven AI Development (TATD) untuk Golang

Test-Driven AI Development (TATD) untuk Golang adalah kombinasi dua practice yang sudah terbukti: Test-Driven Development (TDD) yang meningkatkan code quality, dan AI yang mengakselerasi implementation. Hasilnya adalah kecepatan AI plus kualitas TDD — kode yang benar-benar memenuhi seluruh acceptance criteria tanpa lambatnya menulis test manual yang selama ini membatasi adopsi TDD.


12.1 Mengapa TDD + AI adalah Kombinasi yang Kuat

Untuk memahami nilai TATD, kita perlu melihat kekurangan masing-masing pendekatan secara terpisah. Perbandingan berikut menyandingkan TDD tradisional, AI coding tanpa TDD, dan TATD.

text
 1Traditional TDD:
 2  Red (write failing test) → Green (implement) → Refactor
 3
 4  Kelebihan: forced clarity tentang behavior, high test coverage
 5  Kekurangan: LAMBAT — menulis test dulu terasa overhead
 6  Adoption: rendah karena perceived cost tinggi
 7
 8Traditional AI Coding (no TDD):
 9  Prompt → AI generates code → developer reviews
10
11  Kelebihan: CEPAT
12  Kekurangan: coverage rendah, behavior ambigu, hard to verify correctness
13
14TATD (Test-Driven AI Development):
15  Spec → AI writes tests → Review tests → AI implements → Verify
16
17  Kelebihan: Speed AI + TDD quality + forced spec clarity
18  Kekurangan: lebih banyak upfront work (worth it untuk production code)

Kuncinya ada di baris terakhir: TATD memindahkan friksi terbesar TDD (menulis test manual yang lambat) ke AI, sambil mempertahankan disiplin “spec dulu, baru kode”.


12.2 TATD Workflow: Step by Step

Alur TATD paling mudah dipahami lewat contoh konkret. Delapan langkah berikut menelusuri fitur CancelOrder dari spec hingga verifikasi, dengan AI menulis test lebih dulu.

bash
 1# Step 1: Mulai dari spec atau task
 2# (Bisa dari .specify/features/x/spec.md atau task description)
 3
 4Spec: CancelOrder — Customer bisa cancel order PENDING dalam 30 menit
 5
 6# Step 2: AI menulis tests DULU
 7claude
 8> Baca spec ini:
 9> ---
10> Feature: CancelOrder
11> AC1: Order PENDING + dibuat < 30 menit → dapat di-cancel → 204
12> AC2: Order tidak PENDING → error ORDER_NOT_CANCELLABLE → 409
13> AC3: Order PENDING + dibuat > 30 menit → error CANCEL_WINDOW_EXPIRED → 409
14> AC4: Order tidak milik user yang request → 404 ORDER_NOT_FOUND
15> AC5: Stock di-restore secara atomic
16> AC6: Kafka event ORDER_CANCELLED dikirim
17> ---
18>
19> Tulis unit tests untuk CancelOrderUseCase yang cover SEMUA ACs.
20> Pattern: testify/suite + gomock sesuai CLAUDE.md.
21> JANGAN implement usecase-nya dulu — hanya tests.
22> Tests harus dalam keadaan FAILING (red state).
23
24# Step 3: Review tests yang di-generate
25# Verifikasi:
26# - Semua ACs sudah ada test coverage?
27# - Test setup benar (mock, suite)?
28# - Test assertions make sense?
29
30# Step 4: Jalankan untuk confirm failing
31go test ./internal/usecase/order/... -run TestCancelOrder
32# Expected: compilation error atau test failures
33
34# Step 5: AI implements untuk membuat tests pass
35claude
36> Implement CancelOrderUseCase yang membuat SEMUA tests pass.
37> File test sudah ada di cancel_order_test.go — jangan ubah.
38> Hanya buat cancel_order.go dengan implementasi yang benar.
39
40# Step 6: Run tests
41go test ./internal/usecase/order/... -race
42# Expected: SEMUA PASS
43
44# Step 7: Refactor jika perlu
45claude
46> Apakah ada yang bisa di-refactor dari implementasi ini
47> tanpa mengubah behavior? Semua tests harus tetap pass.
48# Run tests lagi setelah refactor
49
50# Step 8: Extend ke layer berikutnya (repository, handler)
51# Sama prosesnya: write test → implement → verify

Perhatikan Step 5 yang eksplisit “file test sudah ada — jangan ubah”: inilah pengaman inti TATD, memaksa AI membuat implementasi lolos spec, bukan sebaliknya.


12.3 Template Prompt untuk TATD: Tests-First

Kualitas test yang di-generate sangat bergantung pada prompt. Tiga template berikut menutup skenario paling umum: dari spec, dari signature, dan dari kode yang sudah ada.

text
 1Prompt yang paling efektif untuk generate tests:
 2
 3Template A: Dari spec (paling structured)
 4"Tulis unit tests untuk [UseCaseName] yang cover semua ACs ini:
 5@specs/order/cancel-order.md (AC1-AC10)
 6
 7Requirements:
 8- Pattern: testify/suite + gomock (sesuai CLAUDE.md)
 9- Setiap AC = minimal 1 test function
10- Include edge cases: concurrent scenario, nil inputs
11- Tests harus FAILING saat ini (sebelum implementation)
12- File: internal/usecase/[domain]/[feature]_test.go"
13
14Template B: Dari function signature (backward)
15"Ini adalah function yang akan saya implement:
16    func (uc *CancelOrderUseCase) Execute(ctx context.Context, input CancelOrderInput) error
17
18Input: CancelOrderInput{OrderID uuid.UUID, UserID uuid.UUID}
19
20Behavioral expectations:
21- Happy path: PENDING order dalam 30 menit → nil error
22- Not found: nil, nil dari repo → ErrOrderNotFound
23- Wrong status: bukan PENDING → OrderNotCancellableError
24- Timeout: dibuat > 30 menit lalu → ErrCancelWindowExpired
25
26Tulis comprehensive tests sebelum saya implement."
27
28Template C: Dari existing implementation (coverage improvement)
29"Berikut implementasi yang sudah ada:
30@internal/usecase/order/cancel_order.go
31
32Identifikasi semua scenarios yang belum di-cover test,
33kemudian tulis tests untuk coverage yang lebih baik."

Ketiganya berbagi satu prinsip: sebutkan pattern (testify/suite + gomock) dan minta edge case eksplisit — tanpa itu, AI cenderung menulis test dangkal yang hanya menguji happy path.


12.4 TATD untuk Repository Layer

Repository test butuh pendekatan berbeda karena menyentuh database sungguhan. Contoh Go berikut menunjukkan pola integration test dengan build tag integration yang memisahkannya dari unit test.

go
 1// Dua approach untuk repository tests:
 2
 3// APPROACH 1: Interface mock (unit test, recommended)
 4// Mock repository untuk test usecase yang depend pada repository
 5// → No real DB needed
 6// → Fast, isolated
 7
 8// APPROACH 2: Real DB (integration test)
 9// Untuk test repository implementation sendiri
10// → Butuh test database (Docker compose)
11// → Lebih slow tapi test yang lebih comprehensive
12
13// Integration test pattern dengan build tag:
14//go:build integration
15
16package postgres_test
17
18import (
19    "context"
20    "testing"
21    "github.com/stretchr/testify/suite"
22)
23
24type OrderRepositoryIntegrationSuite struct {
25    suite.Suite
26    db   *pgxpool.Pool
27    repo *orderRepository
28}
29
30func (s *OrderRepositoryIntegrationSuite) SetupSuite() {
31    // Connect ke test DB (dari env var)
32    dbURL := os.Getenv("TEST_DATABASE_URL")
33    pool, err := pgxpool.New(context.Background(), dbURL)
34    s.Require().NoError(err)
35    s.db = pool
36    s.repo = &orderRepository{db: pool}
37}
38
39func (s *OrderRepositoryIntegrationSuite) TearDownSuite() {
40    s.db.Close()
41}
42
43func (s *OrderRepositoryIntegrationSuite) SetupTest() {
44    // Clean tables sebelum setiap test
45    _, err := s.db.Exec(context.Background(),
46        "TRUNCATE orders, order_items, inventory RESTART IDENTITY CASCADE")
47    s.Require().NoError(err)
48}
49
50func (s *OrderRepositoryIntegrationSuite) TestCancelWithStockRestore_HappyPath() {
51    // Arrange: create order in DB
52    orderID := s.createTestOrder(domain.StatusPending)
53
54    // Act
55    err := s.repo.CancelWithStockRestore(context.Background(), orderID)
56
57    // Assert
58    s.NoError(err)
59    order, err := s.repo.GetByID(context.Background(), orderID)
60    s.NoError(err)
61    s.Equal(domain.StatusCancelled, order.Status)
62    // Verify stock restored
63}
64
65func TestOrderRepositoryIntegration(t *testing.T) {
66    suite.Run(t, new(OrderRepositoryIntegrationSuite))
67}

Perhatikan //go:build integration dan SetupTest yang men-TRUNCATE tabel: pola ini menjaga test tetap deterministik sekaligus bisa dikecualikan dari run unit test biasa.

Untuk menjalankan kedua jenis test secara terpisah, gunakan flag build tag seperti berikut.

bash
1# Run integration tests:
2go test -tags=integration ./internal/repository/postgres/... -v
3
4# Run unit tests only (no integration):
5go test ./internal/repository/postgres/...
6# (integration tests excluded by build tag)

Dengan build tag, unit test tetap cepat di setiap commit sementara integration test yang lambat hanya dijalankan saat dibutuhkan — pemisahan yang penting agar TATD tidak memperlambat loop harian.


12.5 AI-Generated Tests: Common Issues dan Fix

Issue 1: Test yang tidak really testing behavior

Masalah paling umum: AI menghasilkan test yang lolos tapi tidak menguji apa pun. Bandingkan test trivial berikut dengan versi yang benar-benar menegakkan behavior spesifik.

go
 1// AI generate ini (too trivial):
 2func (s *CancelOrderSuite) TestExecute() {
 3    err := s.uc.Execute(context.Background(), CancelOrderInput{
 4        OrderID: uuid.New(),
 5        UserID:  uuid.New(),
 6    })
 7    // No assertions about specific behavior!
 8    s.NoError(err)
 9}
10
11// Yang harusnya:
12func (s *CancelOrderSuite) TestExecute_PendingOrder_WithinWindow_Success() {
13    // Arrange: specific state
14    orderID := uuid.New()
15    order := &domain.Order{
16        ID:        orderID,
17        UserID:    s.userID,
18        Status:    domain.StatusPending,
19        CreatedAt: time.Now().Add(-10 * time.Minute), // within 30min window
20    }
21    s.mockRepo.EXPECT().
22        GetByIDAndUserID(gomock.Any(), orderID, s.userID).
23        Return(order, nil)
24    s.mockRepo.EXPECT().
25        CancelWithStockRestore(gomock.Any(), orderID).
26        Return(nil)
27
28    // Act
29    err := s.uc.Execute(context.Background(), CancelOrderInput{
30        OrderID: orderID,
31        UserID:  s.userID,
32    })
33
34    // Assert: specific behavior
35    s.NoError(err)
36    // If Kafka: also assert event was published
37}

Bedanya: versi benar menyiapkan state spesifik (order PENDING dalam window 30 menit) dan mengatur ekspektasi mock — sehingga saat test hijau, kamu tahu behavior yang tepat sudah terpenuhi.

Issue 2: Missing error cases

Test yang di-generate sering hanya mencakup happy path. Prompt berikut secara eksplisit memaksa cakupan seluruh skenario error.

text
1Prompt yang lebih baik untuk comprehensive error coverage:
2"Tulis tests yang cover SEMUA scenarios termasuk:
3- Happy path
4- Not found (nil, nil dari repo)
5- Permission check (order milik user lain)
6- Business rule violations (status, time window)
7- Infrastructure errors (DB error, Kafka error)
8- Concurrent scenarios
9- Nil input handling"

Menyebutkan daftar skenario secara eksplisit adalah cara termurah menaikkan coverage — AI jarang menebak error case sendiri kecuali diminta.

Issue 3: Wrong mock setup

Kesalahan halus tapi sering: AI mencampur gaya testify/mock dengan gomock. Contoh berikut menunjukkan bentuk yang salah dan yang benar.

go
1// AI generate (wrong):
2s.mockRepo.On("GetByIDAndUserID", mock.Anything, mock.Anything, mock.Anything).
3    Return(order, nil)
4// testify/mock style, bukan gomock!
5
6// Correct (gomock style):
7s.mockRepo.EXPECT().
8    GetByIDAndUserID(gomock.Any(), gomock.Any(), gomock.Any()).
9    Return(order, nil)

Perbaikannya struktural: pastikan CLAUDE.md memuat contoh exact mock setup dengan gomock, agar AI tidak jatuh ke gaya testify/mock yang tidak kompatibel.


12.6 TATD Metrics: Cara Mengukur Improvement

Adopsi TATD sebaiknya diukur, bukan sekadar dirasakan. Lima metrik berikut adalah yang paling berguna dilacak per sprint untuk membuktikan dampaknya.

text
 1Metrics yang worth tracking setelah adopt TATD:
 2
 31. Test Coverage:
 4   go test -cover ./...
 5   Target: > 80% untuk usecase layer
 6
 7   Before TATD adoption: [baseline]
 8   After 1 bulan: [new number]
 9
102. Time to PR:
11   Dari task start ke PR ready
12   Before: [baseline]
13   After: seharusnya naik (lebih confident, lebih rework)
14
153. PR Review Comments (mechanical):
16   Comments seperti "missing error check", "wrong error handling"
17   Before: [baseline]
18   After: harusnya turun significant
19
204. Post-deploy bugs:
21   Bugs yang discovered setelah deployment
22   Before: [baseline]
23   After: harusnya turun (coverage yang lebih baik)
24
255. Developer Confidence:
26   Survey sederhana: "seberapa confident kamu dengan code yang kamu push?"
27   Scale 1-10
28   Before: [baseline]
29   After: harusnya naik
30
31Track semua ini per sprint, evaluate setelah 3 bulan.

Metrik paling meyakinkan bagi manajemen adalah nomor 3 dan 4 — turunnya PR review comment mekanis dan post-deploy bug menerjemahkan TATD langsung ke penghematan biaya.


12.7 TATD dengan Multiple Tools

Pengalaman TATD berbeda-beda antar tool. Ringkasan berikut memetakan cara terbaik menjalankan TATD di Claude Code, Cursor, Copilot, dan Windsurf.

text
 1TATD approach per tool:
 2
 3Claude Code (best TATD experience):
 4  - Plan mode untuk design tests sebelum write
 5  - Dapat iterate sendiri: write test → run → fix → repeat
 6  - Dapat verify bahwa tests benar-benar failing sebelum implement
 7
 8  Optimal flow:
 9  > "Write tests for [feature]. Run them and confirm they fail.
10     Then implement to make them pass. Then run again to confirm pass."
11  Claude handles semua step otomatis.
12
13Cursor (good TATD experience):
14  - Composer: "Write test file first, then implementation"
15  - Visual diff yang sangat membantu untuk review test quality
16  - Agent Mode: bisa run tests between steps
17
18  Optimal flow:
19  Composer step 1: "tests only" → review diff → accept
20  Composer step 2: "implementation only" → review diff → accept
21  Terminal: go test
22
23Copilot (adequate TATD):
24  - /tests command dari function signature
25  - Manual workflow (tidak autonomous)
26  - Good untuk adding test coverage ke existing code
27
28Windsurf (good for TATD via Flows):
29  - Setup Flow: write-test → verify-failing → implement → verify-passing
30  - Cascade membantu maintain test context antar sessions

Claude Code unggul karena bisa menutup loop write→run→fix secara otonom; tool lain tetap mampu TATD tapi menuntut lebih banyak langkah manual dari kamu.


12.8 Red-Green-Refactor dengan AI: Cycle yang Sehat

Siklus klasik Red-Green-Refactor tetap jadi jantung TATD, hanya saja AI mempercepat tiap fase. Rincian berikut menunjukkan estimasi waktu dan commit di setiap fase.

text
 1Healthy TATD cycle:
 2
 3🔴 RED Phase (5-10 menit):
 4  AI: "Write tests yang comprehensive untuk [feature]"
 5  → Review tests: apakah cover semua scenarios?
 6  → Run: go test → confirm FAIL
 7  → Commit: git commit -m "test: add tests for cancel order [failing]"
 8
 9🟢 GREEN Phase (10-20 menit):
10  AI: "Implement untuk membuat semua tests pass.
11       Jangan ubah test files."
12  → Run: go test → confirm PASS
13  → Commit: git commit -m "feat: implement cancel order"
14
15🔵 REFACTOR Phase (5-10 menit):
16  AI: "Apakah ada refactoring opportunity?
17       Harus tetap pass semua tests."
18  → Run: go test -race → confirm still PASS
19  → Commit: git commit -m "refactor: simplify cancel order logic"
20
21Total untuk standard feature: 20-40 menit
22vs traditional (no TDD, no AI): 60-120 menit dengan lebih banyak bugs

Angka totalnya menjawab keberatan paling umum: 20-40 menit dengan TATD melawan 60-120 menit cara tradisional — TATD justru lebih cepat sekaligus menghasilkan lebih sedikit bug.


12.9 TATD untuk Hotfix dan Bug Fix

TATD tidak hanya untuk fitur baru; ia sangat efektif untuk bug fix. Alur berikut menunjukkan cara menulis test yang mereproduksi bug lebih dulu, baru memperbaikinya.

text
 1TATD juga sangat efektif untuk bug fixes:
 2
 3Bug report: "Order total amount salah jika ada item yang di-diskon"
 4
 5TATD approach:
 61. AI: "Tulis test yang reproduce bug ini:
 7        Order dengan item diskon → total amount salah
 8        Test harus FAIL dengan behavior saat ini"
 9   → AI generate failing test
10
112. Review test: apakah benar-benar capture bug?
12   Run: go test → confirm FAIL
13
143. AI: "Fix bug tanpa ubah test.
15        Test adalah spec dari expected behavior."
16   → AI fix implementation
17
184. Run: go test → confirm PASS
19
205. Add test ke regression suite
21   "Test ini akan prevent bug ini dari reappearing"
22
23Value yang tidak obvious:
24  Bug fix tanpa test = bug bisa muncul lagi (regression)
25  Bug fix dengan test = permanent prevention
26  TATD memaksa kamu buat regression test, yang paling valuable

Nilai tersembunyinya: menulis failing test lebih dulu memaksa setiap bug fix menghasilkan regression test — pencegahan permanen agar bug yang sama tidak pernah muncul lagi.


12.10 Advanced: Property-Based Testing dengan AI

Untuk domain dengan aturan matematis, property-based testing bisa jauh lebih kuat dari test contoh. Kode Go berikut memakai library gopter untuk memverifikasi properti kalkulasi harga terhadap ribuan input acak.

go
 1// Install: go get github.com/leanovate/gopter
 2
 3// AI generate property-based test untuk price calculation:
 4
 5import (
 6    "github.com/leanovate/gopter"
 7    "github.com/leanovate/gopter/gen"
 8    "github.com/leanovate/gopter/prop"
 9)
10
11func TestPriceCalculation_Properties(t *testing.T) {
12    properties := gopter.NewProperties(nil)
13
14    properties.Property("subtotal never negative", prop.ForAll(
15        func(price int64, qty int) bool {
16            // Property: subtotal harus always >= 0
17            if price < 0 || qty <= 0 {
18                return true // skip invalid inputs
19            }
20            result := CalculateSubtotal(price, qty)
21            return result >= 0
22        },
23        gen.Int64Range(0, 1_000_000_000), // price 0 - 10 juta rupiah
24        gen.IntRange(1, 100),             // quantity 1-100
25    ))
26
27    properties.Property("discount never exceeds subtotal", prop.ForAll(
28        func(price int64, qty int, discountPct int) bool {
29            if price <= 0 || qty <= 0 || discountPct < 0 || discountPct > 100 {
30                return true
31            }
32            subtotal := CalculateSubtotal(price, qty)
33            discount := ApplyDiscount(subtotal, discountPct)
34            return discount <= subtotal
35        },
36        gen.Int64Range(1, 1_000_000_000),
37        gen.IntRange(1, 100),
38        gen.IntRange(0, 100),
39    ))
40
41    properties.TestingRun(t)
42}

Kekuatan pendekatan ini: alih-alih menebak beberapa contoh, gopter mengeksekusi ratusan kombinasi acak untuk menguji properti universal seperti “discount tidak pernah melebihi subtotal”.

Untuk mendapatkan test seperti ini dari AI, arahkan ia mengidentifikasi properti matematis lebih dulu lewat prompt berikut.

bash
1# Prompt ke AI untuk property-based tests:
2claude
3> Domain: OrderPricing (calculation of total, discount, tax)
4>
5> Identifikasi semua mathematical properties yang harus selalu berlaku:
6> contoh: total >= 0, discount <= subtotal, tax >= 0, etc.
7>
8> Generate property-based tests menggunakan gopter library
9> yang verify semua properties ini dengan random input.

Meminta AI mendaftar properti dulu sebelum menulis test menghasilkan cakupan yang jauh lebih menyeluruh daripada langsung meminta “buatkan test”.


12.11 TATD Integration dengan CI/CD

Agar disiplin TATD tidak bergantung pada ingatan developer, tegakkan lewat pipeline. Workflow GitHub Actions berikut menjalankan unit test, integration test, dan coverage gate 80% untuk usecase layer.

yaml
 1# .github/workflows/tatd-quality.yml
 2name: TATD Quality Gates
 3
 4on: [push, pull_request]
 5
 6jobs:
 7  test-quality:
 8    runs-on: ubuntu-latest
 9    steps:
10      - uses: actions/checkout@v4
11      - uses: actions/setup-go@v5
12        with:
13          go-version: '1.22'
14
15      - name: Unit Tests (required)
16        run: go test -race -count=1 ./...
17
18      - name: Integration Tests (optional, non-blocking)
19        run: go test -tags=integration -race ./... || true
20        env:
21          TEST_DATABASE_URL: ${{ secrets.TEST_DB_URL }}
22
23      - name: Coverage Report
24        run: |
25          go test -coverprofile=coverage.out ./...
26          go tool cover -func=coverage.out
27
28          # Fail if usecase coverage < 80%
29          USECASE_COVERAGE=$(go tool cover -func=coverage.out | \
30            grep "internal/usecase" | \
31            awk '{print $3}' | \
32            tr -d '%' | \
33            sort -n | head -1)
34
35          if (( $(echo "$USECASE_COVERAGE < 80" | bc -l) )); then
36            echo "❌ Usecase coverage ${USECASE_COVERAGE}% < 80% threshold"
37            exit 1
38          fi
39          echo "✅ Usecase coverage: ${USECASE_COVERAGE}%"

Coverage gate inilah yang mengubah TATD dari niat baik menjadi standar yang ditegakkan: PR yang menurunkan coverage usecase di bawah 80% otomatis gagal sebelum sempat di-merge.


12.12 Tips & Gotchas

💡 Tip 1: Tests sebagai spec verification

Sebelum approve test dari AI, baca setiap test function dan tanya: “Jika test ini pass, apakah itu berarti AC ini terpenuhi?” Jika ragu, refine test sebelum lanjut.

💡 Tip 2: Satu AC = minimal satu test

Hitung ACs di spec, hitung test function. Jika test lebih sedikit dari ACs, ada yang terlewat.

💡 Tip 3: Mock assertions yang strict

Gunakan gomock.InOrder() untuk memverifikasi bahwa call terjadi dalam urutan yang benar untuk workflow sekuensial.

💡 Tip 4: Table-driven tests untuk kombinasi besar

Untuk banyak kombinasi status atau input, table-driven test jauh lebih ringkas. Contoh berikut menguji seluruh matriks status order dalam satu fungsi.

go
 1func (s *CancelOrderSuite) TestCancelOrder_StatusMatrix() {
 2    testCases := []struct {
 3        name           string
 4        orderStatus    domain.OrderStatus
 5        expectedErr    error
 6    }{
 7        {"pending", domain.StatusPending, nil},
 8        {"confirmed", domain.StatusConfirmed, &domain.OrderNotCancellableError{}},
 9        {"shipped", domain.StatusShipped, &domain.OrderNotCancellableError{}},
10        {"cancelled", domain.StatusCancelled, &domain.OrderNotCancellableError{}},
11    }
12
13    for _, tc := range testCases {
14        s.Run(tc.name, func() {
15            // test dengan tc.orderStatus dan tc.expectedErr
16        })
17    }
18}

Pola table-driven ini idiomatik di Go: menambah kasus baru cukup satu baris di slice, sehingga cakupan matriks status tetap mudah dipelihara.

⚠️ Gotcha 1: AI yang “fixes” tests

Ketika implementasi gagal, kadang AI mengedit file test (bukan implementasi) agar test pass. Instruksi eksplisit: “JANGAN ubah test files.”

⚠️ Gotcha 2: Mock yang terlalu permissive

gomock.Any() untuk semua parameter = test yang tidak benar-benar menguji apa pun. Gunakan spesifik: gomock.Eq(orderID) untuk ID yang sudah diketahui.


12.13 Common Resistance dan Cara Mengatasinya

Adopsi TATD sering menghadapi keberatan yang bisa diprediksi. Tiga argumen umum berikut dijawab dengan konteks kapan keberatan itu valid dan kapan tidak.

text
 1"TATD lebih lambat dari langsung implement"
 2→ True untuk fitur kecil (< 30 menit).
 3  Untuk fitur yang butuh > 1 jam: TATD lebih CEPAT karena:
 4  - Kurangi debugging time post-implementation
 5  - Kurangi rework dari misunderstood requirements
 6  - Tests catch regressions di future sprints
 7
 8"AI bisa langsung generate tests + implementation sekaligus"
 9→ Bisa, tapi hasilnya sering: tests yang pass implementation
10  bukan implementation yang pass behavior spec.
11  Tests-first memaksa: spec → tests → impl (urutan yang benar)
12
13"Tidak semua developer disiplin untuk TDD"
14→ TATD lebih mudah karena AI yang "menulis" tests
15  Developer hanya perlu REVIEW, bukan write dari scratch
16  Barrier to entry jauh lebih rendah

Jawaban terkuat ada di keberatan ketiga: justru karena AI yang menulis test, hambatan disiplin TDD runtuh — developer tinggal me-review, sebuah tugas yang jauh lebih ringan daripada menulis dari nol.


12.14 TATD untuk Golang Generics

Generics membuat TATD makin bernilai karena utility generik harus benar untuk banyak tipe. Contoh berikut menulis test untuk tipe Result[T] sebelum tipe generiknya diimplementasikan.

go
 1// Dengan Go 1.22 generics, TATD sangat membantu untuk generic utilities
 2
 3// Prompt:
 4// "Tulis tests untuk generic Result type yang akan kita buat:
 5//
 6// Properties yang harus di-test:
 7// - Result[T].Ok() returns true jika tidak ada error
 8// - Result[T].Err() returns error jika ada error
 9// - Result[T].Value() panics jika ada error
10// - Result[T].ValueOrDefault(def) returns def jika ada error
11//
12// Generate tests SEBELUM saya implement generic type ini."
13
14// AI generate:
15func TestResult_OkAndErr(t *testing.T) {
16    t.Run("success result", func(t *testing.T) {
17        r := Ok[string]("hello")
18        assert.True(t, r.IsOk())
19        assert.NoError(t, r.Err())
20        assert.Equal(t, "hello", r.Value())
21    })
22
23    t.Run("error result", func(t *testing.T) {
24        r := Err[string](errors.New("something failed"))
25        assert.False(t, r.IsOk())
26        assert.Error(t, r.Err())
27        assert.Panics(t, func() { r.Value() })
28    })
29
30    t.Run("value or default on error", func(t *testing.T) {
31        r := Err[int](errors.New("err"))
32        assert.Equal(t, 42, r.ValueOrDefault(42))
33    })
34}
35// Tests dulu, baru implement generic type

Dengan menuliskan kontrak Result[T] sebagai test lebih dulu, kamu memaksa desain API generik yang jelas sebelum satu baris implementasi ditulis — persis prinsip TATD diterapkan ke generics.


12.15 Ringkasan

TATD adalah pattern yang sangat bernilai untuk Go production development: Spec → AI tests → Review → AI implements → Verify.

Key benefits:

  • Kecepatan AI plus kualitas TDD
  • Forced spec clarity sebelum implementation
  • Regression prevention yang built-in
  • Developer confidence yang lebih tinggi

Adoption path: Mulai dengan satu usecase kompleks di sprint berikutnya. Lacak coverage dan PR review comment, lalu evaluasi setelah 2 sprint.


12.16 TATD untuk Existing Codebase (Retrofit)

Tidak semua project mulai dari nol. Alur berikut menunjukkan cara menerapkan TATD ke codebase yang sudah ada, dimulai dari mengidentifikasi kode yang belum ter-test.

bash
 1# Step 1: Identify untested code
 2go test -coverprofile=coverage.out ./...
 3go tool cover -html=coverage.out -o coverage.html
 4# Open browser: mana yang merah (tidak ter-test)?
 5
 6# Step 2: AI generate tests untuk existing code
 7claude
 8> Analyze fungsi ini yang belum ada test-nya:
 9> @internal/delivery/http/handler/order_handler.go
10>
11> Identifikasi semua scenarios yang harus di-test,
12> kemudian generate comprehensive test suite.
13> Pattern: testify/suite + gomock.
14> Jangan ubah implementation.
15
16# Step 3: Fix implementation jika tests reveal bugs
17# Sering terjadi: ketika nulis tests untuk existing code,
18# ditemukan bug yang sudah ada di production!
19
20# Step 4: Commit tests sebagai regression suite
21git add internal/usecase/order/cancel_order_test.go
22git commit -m "test: add missing test coverage for cancel order"

Efek samping yang berharga di Step 3: menulis test untuk kode lama sering mengungkap bug yang sudah lama ada di production — retrofit TATD sekaligus menjadi audit kualitas.


12.17 TATD Checklist Per Feature

Sebelum menandai sebuah fitur “DONE”, verifikasi cakupan dan kualitas test-nya. Checklist berikut memisahkan test coverage, test quality, dan integrasi.

text
 1Sebelum mark feature "DONE":
 2
 3Test coverage:
 4□ Semua ACs dari spec ada test coverage
 5□ Happy path test ada
 6□ Not found scenario ada (nil, nil dari repo)
 7□ Business rule violation ada (misal: status bukan PENDING)
 8□ Infrastructure error ada (DB error, Kafka error)
 9□ Concurrent scenario ada jika relevan
10□ Semua tests pass dengan go test -race
11
12Test quality:
13□ Setiap test punya meaningful name (Test[Feature]_[Scenario]_[Expected])
14□ Assertions specific (tidak hanya s.NoError untuk semua)
15□ Mock expectations specific (tidak semua gomock.Any())
16□ Test independent (tidak ada shared state antar tests)
17
18Integration:
19□ Integration test ada untuk repository layer (jika butuh)
20□ CI pipeline run semua tests
21□ Coverage gate pass (> 80% untuk usecase layer)

Bagian “Test quality” adalah yang paling sering diabaikan: coverage tinggi tanpa assertion spesifik hanya memberi rasa aman palsu — pastikan setiap test benar-benar menegakkan behavior.


12.18 TATD vs BDD (Behavior-Driven Development)

TATD dan BDD sering disamakan padahal berbeda fokus. Perbandingan berikut membantu memilih di antara keduanya, atau memadukannya secara hybrid.

text
 1TATD dan BDD punya overlap tapi berbeda:
 2
 3BDD:
 4  "Given [state], When [action], Then [outcome]"
 5  Lebih business-readable
 6  Tools: godog (Cucumber untuk Go)
 7
 8TATD:
 9  "Spec AC → failing Go test → implementation"
10  Developer-centric
11  Tools: testify/suite + gomock
12
13Pilih TATD jika:
14  - Tim adalah engineers (tidak ada non-technical stakeholders)
15  - Go codebase dengan Clean Architecture
16  - Speed adalah concern (BDD lebih verbose)
17
18Pilih BDD jika:
19  - Business analyst menulis scenarios
20  - Acceptance tests perlu dibaca oleh non-engineers
21  - Project sudah pakai Cucumber atau Gherkin format
22
23Hybrid approach yang work:
24  - Spec Kit atau Kiro untuk spec (business-readable)
25  - TATD untuk implementation tests (developer-centric)
26  - specify audit untuk verify alignment

Untuk tim engineering murni dengan Clean Architecture Go, TATD biasanya lebih tepat; BDD baru unggul ketika ada stakeholder non-teknis yang perlu membaca acceptance test.


12.19 Measuring TATD Success: 30-Day Scorecard

Untuk membuktikan dampak TATD secara konkret, gunakan scorecard 30 hari. Format berikut membandingkan baseline minggu ke-1 dengan hasil minggu ke-4 pada metrik kunci.

text
 1Setelah 30 hari TATD adoption, track ini:
 2
 3WEEK 1 baseline:
 4  Usecase test coverage: ____%
 5  PR mechanical review comments/PR: ____
 6  Average time test → implementation: ____ menit
 7  Post-deploy bugs/bulan: ____
 8
 9WEEK 4 result:
10  Usecase test coverage: ____%  (target: +20-30%)
11  PR mechanical review comments/PR: ____ (target: -40%)
12  Average time test → implementation: ____ menit (target: similar or faster)
13  Post-deploy bugs/bulan: ____ (target: -50%)
14
15Developer satisfaction:
16  "Seberapa confident kamu dengan code yang kamu push?" (1-10)
17  Week 1 average: ____
18  Week 4 average: ____ (target: +2 points)

Scorecard ini mengubah “rasanya lebih baik” menjadi bukti terukur: target seperti coverage +20-30% dan post-deploy bug -50% memberi manajemen alasan konkret untuk melanjutkan adopsi.


12.20 Ringkasan Extended

Test-Driven AI Development adalah evolusi natural dari TDD di era AI. AI menghapus friksi terbesar TDD (menulis test manual yang lambat) sambil mempertahankan manfaatnya: forced spec clarity, high coverage, dan regression prevention.

Three key habits untuk adopt:

  1. Selalu minta AI menulis tests DULU, konfirmasi failing, baru implement
  2. Review setiap generated test — apakah benar-benar menguji behavior, bukan implementation?
  3. Track metrics setiap sprint — coverage, PR comments, post-deploy bugs

Bottom line: TATD adalah cara mendapatkan kecepatan AI tanpa mengorbankan kualitas kode. Untuk Go production systems, ini kombinasi yang sangat layak diadopsi.


12.21 Tooling untuk Support TATD Workflow

Sebagai penutup, sediakan tooling agar workflow TATD mudah dijalankan. Target Makefile berikut membungkus perintah test, coverage, dan enforcement threshold dalam satu tempat.

bash
 1# Coverage enforcement via Makefile
 2# Makefile
 3
 4test:
 5    go test -race -count=1 ./...
 6
 7test-coverage:
 8    go test -coverprofile=coverage.out ./...
 9    go tool cover -func=coverage.out | grep "total:"
10
11test-coverage-check:
12    @COVERAGE=$$(go test -coverprofile=/tmp/cov.out ./... 2>/dev/null; \
13                 go tool cover -func=/tmp/cov.out | \
14                 grep "total:" | awk '{print $$3}' | tr -d '%'); \
15     if [ "$$(echo "$$COVERAGE < 75" | bc)" -eq 1 ]; then \
16       echo "FAIL: Coverage $$COVERAGE% < 75% threshold"; exit 1; \
17     else \
18       echo "PASS: Coverage $$COVERAGE%"; \
19     fi
20
21test-integration:
22    go test -tags=integration -race ./... -timeout 120s
23
24# Run sebelum commit:
25pre-commit: test-coverage-check lint

Dengan target pre-commit yang memanggil test-coverage-check, disiplin TATD menjadi bagian alami dari workflow harian — bukan langkah tambahan yang mudah dilupakan.

Artikel Terkait

💬 Komentar