Testing GitHub Actions DevOps

Automated Testing with GitHub — ทดสอบอัตโนมัติทุก Push 🧪

By Anirach Mingkhwan DevOps & Vibe Coding 2026
Automated Testing with GitHub

จากโพสต์ที่แล้วเราเรียน Code Quality & Review แต่ยังเป็น manual — วันนี้มาทำให้ ทุกอย่างอัตโนมัติ! ทุกครั้งที่ git push → ระบบทดสอบให้หมด ตั้งแต่ lint ไปจนถึง E2E 🧪🐕

📝 git push
Developer push code
⚡ 847 tests
GitHub Actions รัน
✅ All passed
2 นาที ไม่ใช่ 2 วัน

Testing Pyramid + Static Analysis

ก่อนจะดำน้ำลงไปในแต่ละ level มาเข้าใจ Testing Pyramid กันก่อน — แนวคิดที่บอกว่าเราควรเขียน test แต่ละประเภทมากน้อยแค่ไหน ฐานล่างสุด (Unit Tests) ควรเขียนเยอะที่สุดเพราะเร็วและถูก ยิ่งขึ้นไปด้านบน (E2E Tests) ยิ่งควรเขียนน้อยลงเพราะช้าและแพง แต่ทุก level มีความสำคัญและขาดไม่ได้

🖥️ E2E Tests (Playwright)
น้อย, ช้า, แพง — critical paths เท่านั้น
🔗 Integration Tests (API + DB)
กลาง, ปานกลาง — services ร่วมกัน
⚡ Unit Tests (Jest, pytest)
เยอะ, เร็ว, ถูก — ทดสอบทีละ function
🔍 Static Analysis (ESLint, CodeQL, mypy)
เร็วที่สุด — ไม่ต้องรัน code ก็เจอ bug

⚡ Level 1: Static Analysis — เจอ bug โดยไม่ต้องรัน

Static Analysis คือการวิเคราะห์ code โดยไม่ต้อง "รัน" มัน — เหมือนการตรวจสอบสะกดคำก่อนส่งอีเมล เครื่องมือพวกนี้จะสแกน code ของคุณและบอกว่ามีปัญหาตรงไหน ตั้งแต่ตัวแปรที่ประกาศแล้วไม่ได้ใช้ ไปจนถึงช่องโหว่ด้านความปลอดภัย ข้อดีคือเร็วมาก (ไม่กี่วินาที) และเจอ bug ได้ก่อนที่ code จะถูกรัน

ESLint + Prettier + TypeScript

สามเครื่องมือนี้ทำงานร่วมกันเพื่อรักษาคุณภาพ code ใน JavaScript/TypeScript projects — ESLint หา bugs และ enforce coding rules, Prettier จัด format ให้สวยเหมือนกันทั้ง team, และ TypeScript ตรวจสอบ type safety เมื่อ setup ใน GitHub Actions ทุก PR จะถูกตรวจสอบอัตโนมัติ:

# .github/workflows/static-analysis.yml
name: Static Analysis
on: pull_request:

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci

      # ESLint — หา bugs + enforce rules
      - name: ESLint
        run: npx eslint . --format=json --output-file=eslint-report.json
        continue-on-error: true

      # Prettier — format check
      - name: Prettier
        run: npx prettier --check .

      # TypeScript — type check
      - name: Type Check
        run: npx tsc --noEmit

      # แสดงผลใน PR เป็น annotations
      - name: Annotate ESLint results
        if: always()
        uses: ataylorme/eslint-annotate-action@v3
        with:
          report-json: eslint-report.json

Python — Ruff + mypy

สำหรับ Python มี Ruff ซึ่งเป็น linter ตัวใหม่ที่เร็วกว่า flake8 + isort + black รวมกัน 10-100 เท่า (เขียนด้วย Rust) ส่วน mypy ทำหน้าที่ type checking เหมือน TypeScript ช่วยเจอ bug ที่เกิดจากการส่ง type ผิด:

  python-lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      # Ruff — lint + format (แทน flake8 + isort + black)
      - name: Ruff lint
        uses: astral-sh/ruff-action@v1
      - name: Ruff format check
        uses: astral-sh/ruff-action@v1
        with:
          args: "format --check"

      # mypy — type check
      - run: pip install -r requirements.txt
      - name: mypy
        run: mypy src/ --strict

GitHub CodeQL — Security Analysis (SAST)

CodeQL คือ SAST (Static Application Security Testing) tool ของ GitHub ที่วิเคราะห์ code เพื่อหาช่องโหว่ด้านความปลอดภัย เช่น SQL Injection, XSS, Path Traversal ฟรีสำหรับ public repos และแสดงผลลัพธ์เป็น annotation ตรง line ที่มีปัญหาใน PR เลย — เหมือนมี security expert review code ให้อัตโนมัติ:

# .github/workflows/codeql.yml
name: CodeQL Security Analysis
on:
  push:
    branches: [main]
  pull_request:
  schedule:
    - cron: '0 6 * * 1'                 # ทุกวันจันทร์

jobs:
  analyze:
    runs-on: ubuntu-latest
    permissions:
      security-events: write
    strategy:
      matrix:
        language: ['javascript-typescript', 'python']
    steps:
      - uses: actions/checkout@v4
      - uses: github/codeql-action/init@v3
        with:
          languages: ${{ matrix.language }}
          queries: +security-extended
      - uses: github/codeql-action/autobuild@v3
      - uses: github/codeql-action/analyze@v3
SeverityCodeQL ตรวจจับ
🔴 CriticalSQL Injection, XSS, Path Traversal, Command Injection
🟠 HighHard-coded credentials, Missing authentication, Input validation
🟡 MediumIncomplete URL validation, Weak cryptography, Missing CSRF
💡 CodeQL ฟรีสำหรับ public repos! ผลแสดงใน Security tab + annotation ใน PR

🧪 Level 2: Unit Tests — ทดสอบทีละ function

Unit Tests คือการทดสอบ function หรือ class ทีละตัวแบบแยกส่วน (isolated) โดยไม่พึ่ง database, API, หรือ external services เป้าหมายคือยืนยันว่า "ถ้าใส่ input นี้ ต้องได้ output นั้น" Unit tests เป็นฐานล่างสุดของ pyramid — เขียนเยอะ, รันเร็ว (ไม่กี่วินาที), และเจอ bug ได้ตั้งแต่เนิ่นๆ

Jest (TypeScript)

Jest เป็น testing framework ยอดนิยมสำหรับ JavaScript/TypeScript ที่ครบครัน — มี test runner, assertion, mocking built-in ตัวอย่างด้านล่างแสดงการเขียน unit tests ที่ดี ครอบคลุมทั้ง happy path, edge cases, และ error handling:

// src/utils/calculator.ts
export function calculateDiscount(price: number, percent: number): number {
  if (price < 0) throw new Error('Price cannot be negative');
  if (percent < 0 || percent > 100) throw new Error('Invalid discount');
  return price * (1 - percent / 100);
}

// src/utils/__tests__/calculator.test.ts
describe('calculateDiscount', () => {
  test('applies 20% discount', () => {
    expect(calculateDiscount(100, 20)).toBe(80);
  });

  test('0% discount returns full price', () => {
    expect(calculateDiscount(100, 0)).toBe(100);
  });

  test('100% discount returns 0', () => {
    expect(calculateDiscount(100, 100)).toBe(0);
  });

  test('rejects negative price', () => {
    expect(() => calculateDiscount(-100, 20)).toThrow('Price cannot be negative');
  });

  test('rejects invalid discount > 100', () => {
    expect(() => calculateDiscount(100, 150)).toThrow('Invalid discount');
  });
});

pytest (Python) — Parametrized Tests

สำหรับ Python, pytest เป็น framework ที่ทรงพลังด้วย fixtures (setup/teardown ที่ใช้ซ้ำได้) และ parametrized tests (ทดสอบหลาย cases ด้วย decorator ตัวเดียว) ช่วยลด code ซ้ำและครอบคลุม edge cases ได้ง่าย:

# tests/test_user_service.py
import pytest
from datetime import date
from src.services.user_service import User

class TestUser:
    @pytest.fixture
    def admin(self):
        return User("Alice", "[email protected]", date(1990, 5, 15), role="admin")

    def test_admin_can_access_dashboard(self, admin):
        assert admin.can_access("dashboard") is True

    def test_admin_can_access_settings(self, admin):
        assert admin.can_access("settings") is True

    # ทดสอบหลาย cases ด้วย parametrize
    @pytest.mark.parametrize("role,resource,expected", [
        ("admin", "dashboard", True),
        ("admin", "settings", True),
        ("editor", "dashboard", True),
        ("editor", "settings", False),
        ("member", "dashboard", True),
        ("member", "settings", False),
        ("guest", "dashboard", False),
    ])
    def test_role_permissions(self, role, resource, expected):
        user = User("Test", "[email protected]", date(2000, 1, 1), role=role)
        assert user.can_access(resource) is expected

GitHub Actions — Unit Tests + Coverage

เมื่อเขียน unit tests แล้ว ขั้นตอนถัดไปคือให้ GitHub Actions รันอัตโนมัติทุก PR พร้อมรายงาน coverage ว่า code ส่วนไหนยังไม่มี test ครอบคลุม สิ่งสำคัญคือการ setup ให้ test results แสดงเป็น annotation ใน PR และ coverage report แสดงเป็น comment:

# .github/workflows/unit-tests.yml
name: Unit Tests
on: pull_request:

jobs:
  test-js:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20', cache: 'npm' }
      - run: npm ci
      - run: npm test -- --coverage --reporters=default --reporters=jest-junit
        env:
          JEST_JUNIT_OUTPUT_DIR: reports/

      # แสดง test results ใน PR
      - name: Test Report
        if: always()
        uses: dorny/test-reporter@v1
        with:
          name: Jest Results
          path: reports/jest-results.xml
          reporter: jest-junit

      # Coverage comment ใน PR
      - uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

🔗 Level 3: Integration Tests — หลาย components ร่วมกัน

Integration Tests ทดสอบว่าหลาย components ทำงานร่วมกันได้จริง — เช่น API endpoint → business logic → database → response ต่างจาก unit tests ตรงที่ต้องมี "ของจริง" มาเกี่ยวข้อง (database, Redis, etc.) GitHub Actions รองรับ services ที่สร้าง PostgreSQL, Redis, MongoDB containers ให้อัตโนมัติ ทำให้เขียน integration tests ใน CI ได้ง่ายมาก:

# .github/workflows/integration-tests.yml
name: Integration Tests
on: pull_request:

jobs:
  api-tests:
    runs-on: ubuntu-latest
    # Services — DB + Cache พร้อมใช้ใน CI เลย!
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_DB: testdb
          POSTGRES_USER: test
          POSTGRES_PASSWORD: testpass
        ports: ['5432:5432']
        options: --health-cmd pg_isready --health-interval 10s --health-retries 5
      redis:
        image: redis:7-alpine
        ports: ['6379:6379']
        options: --health-cmd "redis-cli ping" --health-interval 10s --health-retries 5

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20', cache: 'npm' }
      - run: npm ci
      - run: npm run db:migrate
        env: { DATABASE_URL: 'postgresql://test:testpass@localhost:5432/testdb' }
      - run: npm run db:seed:test
        env: { DATABASE_URL: 'postgresql://test:testpass@localhost:5432/testdb' }
      - run: npm run test:integration
        env:
          DATABASE_URL: postgresql://test:testpass@localhost:5432/testdb
          REDIS_URL: redis://localhost:6379
          JWT_SECRET: test-secret-key
        timeout-minutes: 10

API Integration Test Example

ตัวอย่างด้านล่างแสดง integration test ที่ทดสอบ Users API จริงๆ — สร้าง user, ดึงข้อมูล, ตรวจสอบ validation, ทดสอบ authentication สังเกตว่าใช้ supertest ส่ง HTTP request จริงไปที่ app และใช้ beforeAll/afterAll จัดการ database setup/teardown:

// tests/integration/api/users.test.ts
import request from 'supertest';
import { app } from '../../../src/app';

describe('POST /api/users', () => {
  test('creates a new user', async () => {
    const res = await request(app)
      .post('/api/users')
      .set('Authorization', `Bearer ${authToken}`)
      .send({
        name: 'Test User',
        email: '[email protected]',
        password: 'SecurePass123!',
      })
      .expect(201);

    expect(res.body.data).toMatchObject({
      name: 'Test User',
      email: '[email protected]',
    });
    // password ต้องไม่ return กลับมา
    expect(res.body.data).not.toHaveProperty('password');
  });

  test('rejects duplicate email', async () => {
    await request(app)
      .post('/api/users')
      .set('Authorization', `Bearer ${authToken}`)
      .send({ name: 'Dup', email: '[email protected]', password: 'Pass123!' })
      .expect(409);
  });

  test('validates required fields', async () => {
    const res = await request(app)
      .post('/api/users')
      .set('Authorization', `Bearer ${authToken}`)
      .send({ name: 'No Email' })
      .expect(400);

    expect(res.body.errors).toContainEqual(
      expect.objectContaining({ field: 'email' })
    );
  });
});

🌐 Level 4: E2E Tests — Playwright

End-to-End (E2E) Tests ทดสอบจากมุมมองผู้ใช้จริง — เปิด browser, กดปุ่ม, กรอกฟอร์ม, ตรวจว่าหน้าจอแสดงผลถูกต้อง Playwright เป็น E2E framework ที่ดีที่สุดในปี 2026 — เร็วกว่า Cypress, รองรับ multi-browser (Chrome, Firefox, Safari), มี auto-wait ที่ฉลาด, และถ่าย screenshot/video เมื่อ test fail อัตโนมัติ

⚠️ E2E tests อยู่บนสุดของ pyramid — ควรเขียนเฉพาะ critical paths เท่านั้น (login, checkout, registration) เพราะรันช้า (10-20 นาที) และ maintain ยาก ถ้าเขียนเยอะเกินไปจะ flaky และเสียเวลาแก้ test มากกว่าแก้ code จริง

# .github/workflows/e2e-tests.yml
name: E2E Tests
on:
  pull_request:
  push:
    branches: [main]

jobs:
  e2e:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    services:
      postgres:
        image: postgres:16
        env: { POSTGRES_DB: e2e_test, POSTGRES_USER: test, POSTGRES_PASSWORD: test }
        ports: ['5432:5432']
        options: --health-cmd pg_isready --health-interval 10s

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20', cache: 'npm' }
      - run: npm ci
      - run: npx playwright install --with-deps chromium firefox
      - run: npm run start:test &
        env: { DATABASE_URL: 'postgresql://test:test@localhost:5432/e2e_test' }
      - run: npx wait-on http://localhost:3000 --timeout 30000
      - run: npx playwright test
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: playwright-report/

Playwright Test — Login + Shopping Cart

ตัวอย่างด้านล่างแสดง E2E tests สำหรับ 2 critical paths ที่สำคัญที่สุด — Authentication Flow (login, registration) และ Shopping Cart Flow (เพิ่มสินค้า, อัพเดทจำนวน, checkout) สังเกตว่าใช้ data-testid attributes สำหรับ selectors แทน CSS classes — เพราะ classes อาจเปลี่ยนได้ แต่ test IDs ตั้งมาเพื่อ testing โดยเฉพาะ:

// e2e/tests/auth.spec.ts
import { test, expect } from '@playwright/test';

test('successful login flow', async ({ page }) => {
  await page.goto('/login');
  await page.fill('[data-testid="email"]', '[email protected]');
  await page.fill('[data-testid="password"]', 'test123');
  await page.click('[data-testid="login-button"]');

  await expect(page).toHaveURL('/dashboard');
  await expect(page.locator('[data-testid="welcome-message"]'))
    .toContainText('Welcome, Admin');
});

test('shows error for invalid credentials', async ({ page }) => {
  await page.goto('/login');
  await page.fill('[data-testid="email"]', '[email protected]');
  await page.fill('[data-testid="password"]', 'wrong');
  await page.click('[data-testid="login-button"]');

  await expect(page.locator('[data-testid="error-message"]'))
    .toContainText('Invalid email or password');
});
// e2e/tests/shopping-cart.spec.ts
test('add items → update quantity → checkout', async ({ page }) => {
  await page.goto('/products');

  // Add item to cart
  await page.locator('.product-card').first()
    .getByRole('button', { name: 'Add to Cart' }).click();
  await expect(page.locator('[data-testid="cart-badge"]')).toHaveText('1');

  // Go to cart → update quantity
  await page.click('[data-testid="cart-icon"]');
  await page.fill('[data-testid="quantity-input"]', '3');
  await page.click('[data-testid="update-cart"]');

  // Checkout
  await page.click('[data-testid="checkout-button"]');
  await page.fill('#address', '123 Test St');
  await page.click('[data-testid="place-order"]');

  await expect(page.locator('[data-testid="order-confirmation"]')).toBeVisible();
  await expect(page.locator('[data-testid="order-id"]')).toHaveText(/^ORD-\d+$/);
});

Playwright Config — Multi-browser

Playwright config ด้านล่างแสดงการตั้งค่าที่สำคัญ — retries: 2 ใน CI ป้องกัน flaky tests, trace: 'on-first-retry' บันทึก trace สำหรับ debug, และ projects กำหนด browsers + devices ที่จะทดสอบ รวมถึง mobile viewport:

// playwright.config.ts
export default defineConfig({
  testDir: './e2e/tests',
  fullyParallel: true,
  retries: process.env.CI ? 2 : 0,       // retry 2 ครั้งใน CI
  reporter: [['html'], ['junit', { outputFile: 'reports/e2e-results.xml' }]],
  use: {
    baseURL: process.env.BASE_URL || 'http://localhost:3000',
    trace: 'on-first-retry',              // trace เมื่อ retry
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
    { name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
    { name: 'mobile-safari', use: { ...devices['iPhone 13'] } },
  ],
});

🛡️ Level 5: Security Testing

นอกจาก functional testing แล้ว Security Testing ก็สำคัญไม่แพ้กัน — dependencies ที่ใช้อาจมีช่องโหว่, code อาจมี hardcoded secrets, Docker images อาจมี vulnerabilities การ setup security scanning อัตโนมัติใน CI จะช่วยเจอปัญหาเหล่านี้ก่อนที่จะถึง production

Dependabot — Auto-update Dependencies

Dependabot เป็น built-in feature ของ GitHub ที่สแกน dependencies และสร้าง PR อัตโนมัติเมื่อมี security patch หรือ version ใหม่ ช่วยให้ไม่ต้องตรวจสอบ dependencies ด้วยมือ:

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
      day: "monday"
    open-pull-requests-limit: 10
    labels: ["dependencies", "automated"]
    groups:
      dev-dependencies:
        dependency-type: "development"

  - package-ecosystem: "docker"
    directory: "/"
    schedule:
      interval: "weekly"

  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"

Trivy + Gitleaks + License Check

นอกจาก Dependabot แล้ว ยังมี security tools อื่นที่ควร setup — Trivy สแกน vulnerabilities ใน source code และ Docker images, Gitleaks ตรวจจับ secrets ที่ถูก commit เผลอ (API keys, passwords), และ license-checker ตรวจว่า dependencies ไม่มี GPL license ที่อาจกระทบ commercial use:

# .github/workflows/security-scan.yml
jobs:
  source-scan:
    steps:
      - uses: actions/checkout@v4
      - uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          severity: 'CRITICAL,HIGH'
          exit-code: '1'                   # fail ถ้าเจอ CRITICAL

  secret-scan:
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - uses: gitleaks/gitleaks-action@v2
        env: { GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' }

  license-check:
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx license-checker --failOn "GPL-3.0;AGPL-3.0"

📊 Coverage Gates — บังคับ Coverage ขั้นต่ำ

Coverage Gates คือการตั้งกฎว่า "ถ้า test coverage ต่ำกว่า X% ห้าม merge" — ช่วยป้องกันไม่ให้ทีมเขียน code ใหม่โดยไม่มี tests Coverage ที่แนะนำคือ 80-90% สำหรับ code ทั่วไป และ 90-95% สำหรับ critical code เช่น auth, payments:

// jest.config.js
module.exports = {
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 85,
      statements: 85,
    },
    // auth code ต้อง coverage สูงพิเศษ!
    './src/auth/': {
      branches: 90,
      functions: 90,
      lines: 95,
    },
  },
};

🔄 Complete Testing Pipeline

ถึงเวลารวมทุกอย่างเข้าด้วยกัน! Pipeline ด้านล่างแสดง complete testing workflow ที่ใช้ได้กับโปรเจกต์จริง — เริ่มจาก static analysis (เร็วที่สุด) แล้วค่อยๆ ไล่ขึ้นไปถึง E2E tests (ช้าที่สุด) โดยใช้ parallelism ให้เต็มที่เพื่อลดเวลารอ จาก 45 นาทีแบบ sequential เหลือแค่ ~15 นาทีแบบ parallel:

Stage 1
🔍 Lint
📐 Format
🔤 TypeCheck
~30s
Stage 2
🧪 Unit 1/4
🧪 Unit 2/4
🧪 Unit 3/4
🧪 Unit 4/4
~2min (parallel)
Stage 3
🔗 Integration (DB+Redis)
🛡️ Security (CodeQL+Trivy)
~5min (parallel)
Stage 4
🌐 E2E Chromium
🌐 E2E Firefox
~10min
Gate
✅ All Passed → Ready to merge
Total: ~15min (parallel)
# Complete pipeline — key parts
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true          # cancel old runs ถ้า push ใหม่

jobs:
  static:   ...                      # lint + format + typecheck
  unit:
    needs: static
    strategy:
      matrix:
        shard: [1, 2, 3, 4]          # split tests 4 shards!
    steps:
      - run: npm test -- --shard=${{ matrix.shard }}/4

  integration:
    needs: static
    services:                         # Postgres + Redis
      postgres: ...
      redis: ...

  e2e:
    needs: [unit, integration]
    strategy:
      matrix:
        browser: [chromium, firefox]  # multi-browser parallel

  all-tests-pass:
    needs: [coverage, integration, e2e, security]
    if: always()                      # Gate: ทุก job ต้อง pass

Pipeline configuration ข้างต้นแสดงให้เห็นถึงการออกแบบ CI/CD ที่คำนึงถึง efficiency และ parallelism การใช้ concurrency groups ด้วย cancel-in-progress: true ป้องกันการ waste resources จาก outdated runs การใช้ needs dependencies เพื่อ control execution order และ matrix strategies เพื่อ parallelize workloads ช่วยให้ได้ optimal performance

การใช้ if: always() ใน gate job แสดงถึงการ implement proper quality gates ที่ require ทุก test stages ต้อง pass ก่อนที่จะ allow merge การ design pipeline เป็น stages ที่ clear dependencies ช่วยให้เข้าใจ flow และ debug issues ได้ง่าย การ balance ระหว่างความเร็วและความครอบคลุมเป็น art ที่ต้องปรับแต่งตาม project needs


⚡ Performance Tips

การ optimize performance ของ CI/CD pipeline เป็นการลงทุนที่คุ้มค่ามากเพราะช่วยลดเวลารอของ developer และค่าใช้จ่ายของ CI minutes การใช้ techniques ที่หลากหลายร่วมกันจะให้ผลลัพธ์ที่ดีที่สุด โดยแต่ละเทคนิคมี impact และความยากในการ implement ที่แตกต่างกัน การ prioritize optimizations ตาม cost-benefit ratio จะให้ ROI ที่ดีที่สุด

TechniqueEffectHow
Test Sharding3-4x faster--shard=1/4 (Jest/Playwright)
Caching10x faster installcache: 'npm' in setup-node
ConcurrencyCancel old runscancel-in-progress: true
Path FilteringSkip unnecessarydorny/paths-filter@v3
fail-fast: falseSee all failuresDon't stop on first fail

ตารางข้างต้นแสดงให้เห็นถึงผลกระทบที่สำคัญของ performance optimization techniques แต่ละเทคนิคมี impact ที่แตกต่างกัน โดย test sharding และ caching มักให้ผลลัพธ์ที่ดีที่สุด เพราะช่วยลดเวลาที่ใช้จริงในการ execution

สิ่งสำคัญคือการเลือก optimization techniques ให้เหมาะสมกับ pipeline ของตนเอง การใช้ fail-fast: false อาจช้ากว่า แต่ให้ข้อมูลที่ครอบคลุมกว่าเมื่อมีปัญหาหลายจุดพร้อมกัน การ balance ระหว่างความเร็วและความครอบคลุมของข้อมูลเป็นสิ่งที่ต้องปรับให้เหมาะสมกับทีม

# Path filtering — รันเฉพาะ tests ที่เกี่ยวข้อง
- uses: dorny/paths-filter@v3
  id: filter
  with:
    filters: |
      backend:
        - 'src/api/**'
        - 'src/models/**'
      frontend:
        - 'src/components/**'
        - 'src/pages/**'

test-backend:
  needs: changes
  if: needs.changes.outputs.backend == 'true'
  # → ไม่รัน backend tests ถ้าแก้แค่ frontend!

Path filtering เป็นเทคนิคที่ทรงพลังมากในการลดเวลา CI โดยการรัน tests เฉพาะส่วนที่เกี่ยวข้องกับการเปลี่ยนแปลงจริง การใช้ dorny/paths-filter action ทำให้สามารถ detect file changes และตัดสินใจได้อัตโนมัติว่าควรรัน test suite ไหน การ configure path patterns ให้ครอบคลุมทั้ง direct dependencies และ indirect dependencies เป็นสิ่งสำคัญสำหรับความถูกต้อง

ประโยชน์ของ path filtering มีมากกว่าแค่ความเร็ว ยังช่วยลด noise ใน CI/CD logs และทำให้ developers มีความมั่นใจมากขึ้นว่าการเปลี่ยนแปลงของพวกเขาไม่ได้กระทบส่วนอื่นๆ ของระบบ แต่ต้องระวังการตั้งค่า dependency paths ให้ถูกต้อง เพื่อไม่ให้พลาด tests ที่สำคัญ การ balance ระหว่าง precision และ safety ใน path filtering ต้องปรับแต่งตาม architecture ของ codebase

เทคนิคอื่นๆ เช่น test sharding ช่วยลดเวลาโดยการแบ่ง test suite ออกเป็นส่วนย่อยที่รันแบบ parallel การใช้ caching อย่างมีประสิทธิภาพช่วยลดเวลาใน dependency installation และ build steps การ combine หลายเทคนิคร่วมกันมักให้ performance improvements ที่ significant กว่าการใช้เทคนิคเดียว


🔑 Key Takeaways

การสรุปสาระสำคัญของ automated testing ช่วยให้เห็นภาพรวมของความรู้ที่ได้รับและสามารถนำไปประยุกต์ใช้ได้อย่างมีประสิทธิภาพ แต่ละจุดที่กล่าวถึงล้วนมาจากประสบการณ์จริงและ lessons learned จาการ implement automated testing ในโปรเจกต์จริง

สิ่งสำคัญที่สุดคือการเริ่มต้นจากพื้นฐานที่แข็งแกร่ง แล้วค่อยๆ เพิ่มความซับซ้อน การมี testing pyramid ที่สมดุล static analysis ที่ครอบคลุม และ CI/CD pipeline ที่เร็วและเสถียรจะเป็น foundation ที่ดีสำหรับการ scale ระบบในอนาคต การลงทุนเวลาและความพยายามในการ setup เหล่านี้จะคืนผลกลับมาในรูปของ productivity และ code quality ที่เพิ่มขึ้น

  1. Testing Pyramid → Unit (เยอะ) > Integration (กลาง) > E2E (น้อย)
  2. Static analysis ก่อนเสมอ → เร็ว, เจอ bugs ตั้งแต่ก่อนรัน test
  3. CodeQL → free SAST สำหรับ public repos
  4. Services ใน Actions → Postgres/Redis/Mongo พร้อมใช้ใน CI เลย
  5. Playwright → E2E testing ที่ดีที่สุด — เร็ว, reliable, multi-browser
  6. Test sharding → แบ่ง tests รัน parallel → เร็วขึ้น 3-4x
  7. Coverage gates → บังคับ 80%+ coverage ห้ามลดลง
  8. Dependabot → auto-update dependencies + security patches
  9. Path filtering → ไม่ต้องรัน backend tests ถ้าแก้แค่ frontend
  10. concurrency + cancel-in-progress → ประหยัด CI minutes 🧪🐕
บทความจากซีรีส์ DevOps & Vibe Coding 2026
← Previous
Code Quality & Review — เขียน Code ดีๆ ที่ทีมอยากอ่าน
Next →
Database Design & SQL — ออกแบบฐานข้อมูลที่ดีและเขียน SQL อย่างมืออาชีพ