DevOps Strategy 2025

Microservices Testing Strategy 2025

คู่มือการทดสอบระบบ Microservices แบบครบวงจร - Contract Testing, E2E Testing, Chaos Engineering และ Sidecar Pattern

Contract Testing
E2E Testing
Chaos Engineering
Sidecar Pattern
TypeScript/Node.js
Python
Docker & K8s
GitLab CI
GitHub Actions

1 สรุป (Summary)

ระบบ Microservices ได้กลายเป็น architectural pattern ที่นิยมมากที่สุดในปี 2025 โดยเฉพาะอย่างยิ่งในประเทศไทยที่บริษัทเริ่มย้ายจาก Monolith ไปสู่ระบบกระจาย (Distributed System)

However, การทดสอบ Microservices ยากกว่า Monolith มาก เพราะต้องจัดการกับ Network Calls, Data Consistency, และ Service Dependencies

2 สิ่งที่ต้องเตรียมพร้อม (Prerequisites)

Software

  • Ubuntu 22.04+ หรือ Proxmox VE 7+
  • Node.js 20+ (สำหรับ Pact JS)
  • Python 3.11+ (สำหรับ Testcontainers)
  • Docker & Docker Compose
  • Kubernetes Cluster (Local: Minikube/Kind)

Infrastructure

  • RAM: 8GB+ (Recommended: 16GB)
  • CPU: 4 cores+ (Recommended: 8 cores)
  • Storage: 50GB+ free space
  • Network: Stable internet connection

Budget (Thai Startup)

Testing Tools Free
Proxmox Cluster (3 nodes) ฿8,000-12,000
Cloud (AWS/GCP) ฿15,000-30,000/mo
Engineering Time ฿80,000-150,000

Total Setup Cost (Proxmox): ~฿90,000-120,000

3 Microservices Testing Architecture

การทดสอบ Microservices มีความซับซ้อนกว่า Monolith ต้องใช้หลายเทคนิคร่วมกัน

Developer Team TypeScript/Python Tests + Contracts TESTING LAYER Pact.js Consumer-Driven Contracts Testcontainers Real Services Docker Chaos Toolkit Resilience Testing Microservices Architecture User Service REST API Order Service Kafka Payment gRPC Legends Developer Team Testing Layer Contract Testing E2E Testing Chaos Testing Microservices

Architecture Flow: Developer → Testing Layer (Pact, Testcontainers, Chaos) → Microservices

4 Contract Testing ด้วย Pact.js

Contract Testing เป็นเทคนิคที่สำคัญที่สุดสำหรับ Microservices ช่วยตรวจสอบว่า Service A กับ Service B สื่อสารกันได้ถูกต้องตามข้อตกลง

สิ่งที่ต้องรู้เกี่ยวกับ Contract Testing

ผู้บริโภค (Consumer)

Service ที่ต้องการใช้งาน API จาก Service อื่น สร้าง Contract (Pact file)

ผู้ผลิต (Producer)

Service ที่ให้บริการ API ต้องผ่านการตรวจสอบโดย Pact Verifier

Pact ใช้ Consumer-Driven Contract (CDC) pattern ซึ่ง Consumer กำหนดว่าต้องการ API แบบไหน Producer ต้องทำให้ตรงตาม contract นี้

ข้อดี: ตรวจจับ breaking changes ตั้งแต่ช่วงพัฒนา
ข้อเสีย: ต้องมี Pact Broker หรือ Git repository สำหรับเก็บ contract files

TypeScript/JavaScript Implementation (Pact JS v3)

Recommended for Node.js

1 Setup Packages

npm install --save-dev @pact-foundation/pact jest
npm install --save-dev pactum

Pact JS v3: ใช้สำหรับ Node.js/TypeScript, รองรับทั้ง HTTP และ gRPC

2 Consumer Test (TypeScript)

import { PactV3, MatchersV3 } from '@pact-foundation/pact'; import path from 'path'; const provider = new PactV3({ dir: path.resolve(process.cwd(), 'pacts'), consumer: 'UserServiceClient', provider: 'UserApi', logLevel: 'info', }); const getUserResponse = { id: 'user-123', name: 'John Doe', email: 'john.doe@example.com', }; const EXPECTED_USER = MatchersV3.like(getUserResponse); export const setupGetUserInteraction = () => { provider .given('a user exists with ID user-123') .uponReceiving('a GET request for user 123') .withRequest({ method: 'GET', path: MatchersV3.regex({ equals: '/api/users/user-123', generate: '/api/users/(?<userId>[a-zA-Z0-9-]+)', pattern: '/api/users/[a-zA-Z0-9-]+', }), headers: { Accept: 'application/json', Authorization: MatchersV3.term({ generate: 'Bearer eyJhbGciOiJIUzI1NiJ9', matcher: '^Bearer eyJ[a-zA-Z0-9-_=]+\\.eyJ[a-zA-Z0-9-_=]+\\.[a-zA-Z0-9-_=]+$', }), }, }) .willRespondWith({ status: 200, headers: { 'Content-Type': 'application/json', }, body: EXPECTED_USER, }); }; describe('User Service Consumer Tests', () => { beforeAll(async () => { await provider.setup(); }); afterAll(async () => { await provider.finalize(); }); afterEach(async () => { await provider.verify(); }); it('should fetch user by ID', async () => { setupGetUserInteraction(); return provider.executeTest(async (mockserver) => { const userService = createUserService(mockserver.url); const user = await userService.getUser('user-123'); expect(user.id).toBe('user-123'); expect(user.name).toBe('John Doe'); }); }); });

3 Provider State Handler

import { Request, Response } from 'express'; import { User } from '../models/User'; export const setupTestState = async (req: Request, res: Response): Promise => { const { state, parameters } = req.body; switch (state) { case 'a user exists with ID user-123': await User.create({ id: 'user-123', name: 'John Doe', email: 'john.doe@example.com', createdAt: new Date(), }); break; case 'a user does not exist with ID user-999': await User.destroy({ where: { id: 'user-999' } }); break; default: break; } res.status(200).end(); };

4 CI/CD Pipeline (Publish & Verify)

name: Publish Pacts on: push: branches: [ develop ] jobs: publish-pacts: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Run consumer tests and publish env: PACT_BROKER_BASE_URL: ${{ secrets.PACT_BROKER_URL }} PACT_BROKER_TOKEN: ${{ secrets.PACT_BROKER_TOKEN }} run: | npm run test:consumer npx @pact-foundation/pact publish ./pacts \ --consumer-app-version=${GITHUB_SHA} \ --branch=${GITHUB_REF_NAME}
name: Verify Pacts on: pull_request: branches: [ main, release/* ] jobs: verify-pacts: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Install Pact CLI run: | curl -LO https://github.com/pact-foundation/pact-reference/releases/download/pact_verifier_cli-v1.1.1/pact_verifier_cli-linux-$(uname -m).gz gunzip pact_verifier_cli-linux-$(uname -m).gz sudo mv pact_verifier_cli-linux-$(uname -m) /usr/local/bin/pact_verifier_cli - name: Run Pact verification run: npm run test:provider

5 E2E Testing ด้วย Testcontainers

Testcontainers ช่วยสร้าง environment สำหรับ testing แบบครบวงจร โดยการใช้ Docker container ที่มี service จริงๆ

Testcontainers คืออะไร?

เป็น library สำหรับ Node.js และ Python ที่ช่วยจัดการ Docker containers สำหรับการทดสอบ ทำให้เราสามารถ run จริงๆ ทั้ง PostgreSQL, Kafka, Redis รวมถึง Kubernetes cluster ใน local environment

ข้อดีของ Testcontainers

  • ใช้ service จริง (ไม่ mock)
  • Environment เหมือน production จริง
  • ติดตั้งง่าย (npm install / pip install)
  • Cleanup อัตโนมัติหลัง test เสร็จ
  • ไม่ต้องติดตั้ง service บน machine เอง

Node.js/TypeScript Implementation

npm install --save-dev testcontainers
import { TestContainers, Docker } from 'testcontainers'; import { expect } from '@jest/globals'; describe('Microservices E2E', () => { let docker: Docker; let testcontainers: TestContainers; beforeAll(async () => { docker = new Docker(); testcontainers = new TestContainers(docker); }, 60000); afterAll(async () => { await testcontainers.cleanup(); }); it('should test service with real PostgreSQL', async () => { const postgresContainer = await testcontainers.createContainer() .withImage('postgres:15') .withEnv('POSTGRES_USER', 'testuser') .withEnv('POSTGRES_PASSWORD', 'testpass') .withEnv('POSTGRES_DB', 'testdb') .withExposedPorts(5432) .start(); const host = postgresContainer.getHost(); const port = postgresContainer.getContainerPort(5432); // Test database connection const pool = createPool({ host, port, user: 'testuser', password: 'testpass', database: 'testdb', }); const result = await pool.query('SELECT 1'); expect(result.rows[0].count).toBe('1'); await postgresContainer.stop(); }); });

Python Implementation

pip install testcontainers pytest
from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_for_logs from testcontainers.postgres import PostgresContainer from testcontainers.redis import RedisContainer import psycopg2 import pytest def test_postgres_connection(): """Test PostgreSQL connection using Testcontainers""" with PostgresContainer("postgres:15") as postgres: import os os.environ['POSTGRES_HOST'] = postgres.get_container_host_ip() os.environ['POSTGRES_PORT'] = postgres.get_exposed_port(5432) os.environ['POSTGRES_USER'] = postgres.POSTGRES_USER os.environ['POSTGRES_PASSWORD'] = postgres.POSTGRES_PASSWORD os.environ['POSTGRES_DB'] = postgres.POSTGRES_DB conn = psycopg2.connect( host=postgres.get_container_host_ip(), port=postgres.get_exposed_port(5432), user=postgres.POSTGRES_USER, password=postgres.POSTGRES_PASSWORD, dbname=postgres.POSTGRES_DB ) cursor = conn.cursor() cursor.execute("SELECT 1") assert cursor.fetchone()[0] == 1 cursor.close() conn.close() def test_redis_connection(): """Test Redis connection using Testcontainers""" with RedisContainer("redis:7-alpine") as redis: import redis as redis_client r = redis_client.Redis( host=redis.get_container_host_ip(), port=redis.get_exposed_port(6379) ) r.set("test_key", "test_value") assert r.get("test_key") == b"test_value"

6 Sidecar Pattern สำหรับ Test Data Management

Sidecar Pattern คือการเพิ่ม container เพิ่มเติมใน pod สำหรับทำงานเฉพาะด้าน เช่น การจัดการ test data, initialization script, หรือ health check

Kubernetes Sidecar Pattern Architecture

Kubernetes Pod Main Application user-service:3000 Business Logic Sidecar Container test-data-sidecar Test Data Init /test-data Legends Main Application Sidecar Container Shared Volume

Sidecar Container ทำงานร่วมกับ Main Container ผ่าน Shared Volume (/test-data)

Kubernetes Deployment with Sidecar

apiVersion: apps/v1 kind: Deployment metadata: name: user-service namespace: e2e-test spec: replicas: 1 selector: matchLabels: app: user-service template: metadata: labels: app: user-service spec: containers: # Main application container - name: user-service image: user-service:latest ports: - containerPort: 3000 volumeMounts: - name: test-data mountPath: /app/test-data livenessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 5 periodSeconds: 10 readinessProbe: httpGet: path: /ready port: 3000 initialDelaySeconds: 5 periodSeconds: 5 # Sidecar for test data management - name: test-data-sidecar image: user-service-test-data:latest command: ["/bin/sh", "-c"] args: - | echo "Initializing test data..." curl -X POST http://localhost:3000/test/setup \ -H "Content-Type: application/json" \ -d '{"state": "users exist", "count": 5}' echo "Test data initialization complete" tail -f /dev/null volumeMounts: - name: test-data mountPath: /app/test-data resources: requests: memory: "64Mi" cpu: "100m" limits: memory: "128Mi" cpu: "200m" volumes: - name: test-data emptyDir: {}

Sidecar ทำงานร่วมกับ main container ผ่าน shared volume (emptyDir) sidecar init script จะเรียก API ของ main container เพื่อ seed test data ก่อนที่main container จะพร้อมรับ request

Sidecar Container Dockerfile

# test-data-sidecar/Dockerfile FROM node:20-alpine WORKDIR /app # Install curl for health checks and setup calls RUN apk add --no-cache curl jq # Copy test data scripts COPY --chown=node:node scripts/seed-test-data.js /app/scripts/ # Run as non-root user USER node CMD ["node", "/app/scripts/seed-test-data.js"]

7 Chaos Engineering ด้วย Chaos Toolkit

Chaos Engineering คือการ intentionally inject faults ลงในระบบเพื่อทดสอบว่าระบบสามารถฟื้นตัวจากความล้มเหลวได้หรือไม่

Chaos Engineering Workflow

1. Define Steady State (Baseline) 2. Hypothesis "System survives" 3. Inject Faults 4. Observe Metrics 5. Verify Resilience

Chaos Engineering Flow: Define steady state → Form hypothesis → Inject faults → Observe → Verify resilience

Installation (Ubuntu/Proxmox)

7.1 ติดตั้ง Chaos Toolkit

sudo apt-get update sudo apt-get install -y python3.11 python3.11-venv python3-pip # Create virtual environment python3.11 -m venv ~/.venvs/chaostoolkit source ~/.venvs/chaostoolkit/bin/activate # Install Chaos Toolkit pip install chaostoolkit chaostoolkit-kubernetes # Verify installation chaos --version

7.2 ติดตั้ง Kubernetes Extension

pip install chaostoolkit-kubernetes # Verify Kubernetes extension chaos extension list

Basic Chaos Experiment (Pod Kill)

{ "version": "1.0.0", "title": "Kill a user-service pod to test resilience", "description": "Force delete a pod and verify the service recovers automatically", "tags": ["resilience", "kubernetes", "pod"], "method": [ { "type": "action", "name": "kill-user-service-pod", "provider": { "type": "python", "module": "chaosk8s.actions", "func": "delete_pods", "arguments": { "label_selector": "app=user-service", "namespace": "default" }, "dependencies": { "config": { "type": "env", "key": "KUBERNETES_CONFIG" } } } }, { "type": "probe", "name": "verify-pod-deleted", "provider": { "type": "python", "module": "chaosk8s.probes", "func": "pods_running_count", "arguments": { "label_selector": "app=user-service", "namespace": "default", "minimum": 1 } }, "tolerance": true }, { "type": "probe", "name": "check-service-availability", "provider": { "type": "http", "url": "http://user-service:3000/health", "timeout": 30 }, "tolerance": { "type": "status", "status": [200] } } ], "rollbacks": [ { "type": "action", "name": "cleanup-resources", "provider": { "type": "python", "module": "chaosk8s.actions", "func": "delete_pods", "arguments": { "label_selector": "app=user-service", "namespace": "default" } } } ] }

Experiment นี้จะ: 1) ลบ pod ของ user-service, 2) ตรวจสอบว่า pod ถูกสร้างใหม่, 3) ตรวจสอบว่า service ยังตอบสนองได้

8 กรณีศึกษาและค่าใช้จ่ายในบริบทไทย

ตัวอย่างจริงจากบริษัทไทยที่ implement Microservices Testing Strategy และผลลัพธ์ที่ได้

Thai E-Commerce

Problem

Payment service failures ทำให้ order processing delays

Solution

Contract Testing + E2E Testing + Chaos Engineering

Results

  • Payment failure: 85% ↓
  • MTTR: 70% ↓
  • Order completion: 99.95%
Total Cost ฿170,000

Thai FinTech

Problem

Core banking service timeouts during concurrent users

Solution

Chaos Engineering (Network Latency) + Retry Logic

Results

  • API timeout: 80% ↓
  • SLA: 99.0% → 99.9%
  • No regulatory issues
Total Cost ฿255,000

Thai Healthcare

Problem

Patient record access delays during peak hours

Solution

Auto-scaling (HPA) + Sidecar Pattern + Chaos Resource Testing

Results

  • Wait time: 75% ↓
  • User satisfaction: +40%
  • Zero downtime
Total Cost ฿130,000

Cost Estimate Summary (Thai Development Teams)

Item Proxmox Setup AWS/GCP Monthly Recurring
Chaos Toolkit
Open Source Framework
Free Free Free
Testcontainers
Container-based Testing
Free Free Free
Pact Broker (Self-hosted)
Contract Storage
฿2,500 (VM) ฿3,000 (ECS) ฿2,500
Kubernetes Cluster
3-node Proxmox or Cloud
฿8,000-12,000 ฿15,000-30,000 ฿12,000-20,000
Monitoring
Prometheus + Grafana
฿3,000 ฿5,000 ฿4,000
Engineering Time
Setup + Documentation
฿80,000-200,000 ฿100,000-250,000 -
Total Setup Cost ฿93,500-211,500 ฿120,500-305,500 ฿18,500-25,000

คำแนะนำสำหรับ Thai Startup: เริ่มจาก Proxmox Setup ด้วยงบประมาณ ~฿100,000 หลังจากนั้น monthly operating cost อยู่ที่ ~฿20,000-25,000

9 คำถามที่พบบ่อย (FAQ)

Contract Testing ต่างจาก Unit Testing อย่างไร?

Unit Testing: ทดสอบ function/method เดียวใน isolation (ไม่มี network call)
Contract Testing: ทดสอบว่า service A กับ service B สื่อสารกันได้ถูกต้องตามข้อตกลง (มี network call)

pact broker คืออะไร? จำเป็นไหม?

Pact Broker เป็น server สำหรับเก็บ contract files และเปิดดู history ของ contract verification
จำเป็นไหม? ไม่จำเป็นสำหรับ start-up ขนาดเล็ก - ใช้ Git repository แทนได้

Testcontainers กับ Mock ต่างกันอย่างไร?

Mock: จำลอง service ด้วย fake data - รวดเร็วแต่ไม่จริง 100%
Testcontainers: ใช้ Docker container ที่มี service จริง - ช้ากว่าแต่แม่นยำกว่า

Chaos Engineering มีความเสี่ยงไหม?

ถ้าทำใน production แบบไม่ตั้งใจ: มีความเสี่ยงสูง
ถ้าทำใน staging/development แบบมีการวางแผน: ปลอดภัยและคุ้มค่ามาก
เริ่มจาก small experiment, แล้วค่อยขยาย scale

ควร test บ่อยแค่ไหน?

Contract Testing: ทุก PR/merge request
E2E Testing: ทุก merge ke main branch
Chaos Engineering: ทุกเดือน (production) หรือทุก quarter (staging)