#การตรวจสอบคุณภาพโค้ดด้วย SonarQube: ตัวอย่าง Express และ Laravel

การตรวจสอบคุณภาพโค้ดไม่ควรเกิดขึ้นเฉพาะตอนใกล้นำระบบขึ้น Production เพราะปัญหาอย่างโค้ดซ้ำ เงื่อนไขที่ซับซ้อน ช่องโหว่ และ Technical Debt จะสะสมจนแก้ไขยากขึ้นเรื่อย ๆ

SonarQube เป็นแพลตฟอร์ม Static Code Analysis ที่ช่วยวิเคราะห์ซอร์สโค้ดโดยอัตโนมัติ พร้อมแสดงผลด้าน Reliability, Security และ Maintainability รวมถึง Code Duplication, Test Coverage และ Security Hotspots ผ่าน Dashboard และ Quality Gate

บทความนี้สาธิตการใช้งานกับ 2 โครงการที่พบได้บ่อย ได้แก่

  • Express.js ใช้ Jest สร้างรายงาน Coverage รูปแบบ LCOV
  • Laravel ใช้ PHPUnit สร้างรายงาน Coverage รูปแบบ Clover XML

SonarQube ไม่ได้รัน Unit Test และไม่ได้สร้าง Coverage Report ให้เรา หน้าที่นี้เป็นของ Jest, PHPUnit หรือเครื่องมือทดสอบอื่น จากนั้น SonarScanner จึงอ่านรายงานและส่งผลไปยัง SonarQube


#1. ภาพรวมกระบวนการทำงาน

flowchart LR
    A[Developer Commit Code] --> B[Run Unit Tests]
    B --> C[Generate Coverage Report]
    C --> D[Run SonarScanner]
    D --> E[SonarQube Analysis]
    E --> F{Quality Gate}
    F -->|Passed| G[Build / Deploy ต่อ]
    F -->|Failed| H[แก้ Bug, Vulnerability, Code Smell]

กระบวนการมาตรฐานควรเป็นดังนี้

  1. นักพัฒนาส่งโค้ดขึ้น Repository
  2. CI ติดตั้ง Dependency
  3. CI รัน Unit Test และสร้าง Coverage Report
  4. SonarScanner วิเคราะห์ซอร์สโค้ดและนำเข้า Coverage Report
  5. SonarQube ประเมินผลตาม Quality Profile
  6. Quality Gate ตัดสินว่าโครงการผ่านเกณฑ์หรือไม่
  7. Pipeline หยุดทันทีเมื่อคุณภาพไม่ผ่านเกณฑ์ที่ทีมกำหนด

#2. SonarQube ตรวจสอบอะไรบ้าง

#2.1 Reliability

ค้นหาปัญหาที่อาจทำให้โปรแกรมทำงานผิดพลาด เช่น

  • Null หรือ Undefined ที่ไม่ได้ตรวจสอบ
  • เงื่อนไขที่ไม่มีทางเกิดขึ้น
  • Resource ที่ไม่ได้ปิด
  • การจัดการ Exception ไม่เหมาะสม
  • Logic ที่อาจให้ผลลัพธ์ผิด

#2.2 Security

ตรวจหาช่องโหว่หรือรูปแบบโค้ดที่เสี่ยง เช่น

  • SQL Injection
  • Path Traversal
  • Hard-coded Credentials
  • Weak Cryptography
  • การรับข้อมูลจากผู้ใช้โดยไม่ Validate
  • การตั้งค่า CORS หรือ Cookie ที่ไม่ปลอดภัย

#2.3 Maintainability

ตรวจสอบ Code Smell ที่ทำให้โค้ดดูแลยาก เช่น

  • Method ยาวเกินไป
  • Cyclomatic Complexity สูง
  • Nested Condition หลายชั้น
  • ชื่อตัวแปรไม่สื่อความหมาย
  • Dead Code
  • Duplicate Code

#2.4 Test Coverage

SonarQube นำเข้ารายงาน Coverage จากเครื่องมือทดสอบ เพื่อแสดงว่าโค้ดส่วนใดถูกทดสอบแล้ว แต่ Coverage สูงไม่ได้รับประกันว่า Test มีคุณภาพ จึงควรประเมินร่วมกับ Assertion, Test Scenario และ Mutation Testing

#2.5 Security Hotspots

Security Hotspot คือโค้ดที่ต้องให้ผู้พัฒนาหรือผู้ตรวจสอบพิจารณาว่าใช้งานอย่างปลอดภัยหรือไม่ เช่น การสร้าง Token, การตั้งค่า Cookie, การเข้ารหัส หรือการเปิดใช้ CORS


#3. ติดตั้ง SonarQube ด้วย Docker Compose

ตัวอย่างนี้ใช้ SonarQube Community Build และ PostgreSQL โดย Pin เวอร์ชัน SonarQube เพื่อให้ผลการติดตั้งทำซ้ำได้

สร้างไฟล์ compose.yaml

services:
  postgres:
    image: postgres:17-alpine
    container_name: sonar-postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: sonar
      POSTGRES_PASSWORD: sonar_password
      POSTGRES_DB: sonarqube
    volumes:
      - sonar_postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U sonar -d sonarqube"]
      interval: 10s
      timeout: 5s
      retries: 10
    networks:
      - sonar-network

  sonarqube:
    image: sonarqube:26.7.0.124771-community
    container_name: sonarqube
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      SONAR_JDBC_URL: jdbc:postgresql://postgres:5432/sonarqube
      SONAR_JDBC_USERNAME: sonar
      SONAR_JDBC_PASSWORD: sonar_password
    ports:
      - "9000:9000"
    volumes:
      - sonar_data:/opt/sonarqube/data
      - sonar_logs:/opt/sonarqube/logs
      - sonar_extensions:/opt/sonarqube/extensions
    networks:
      - sonar-network

networks:
  sonar-network:
    name: sonar-network

volumes:
  sonar_postgres_data:
  sonar_data:
  sonar_logs:
  sonar_extensions:

เริ่มระบบ

docker compose up -d

ตรวจสอบสถานะ

docker compose ps
docker compose logs -f sonarqube

เปิดหน้าเว็บ

http://localhost:9000

บัญชีเริ่มต้นสำหรับการติดตั้งใหม่โดยทั่วไปคือ

Username: admin
Password: admin

ระบบจะบังคับให้เปลี่ยนรหัสผ่านหลังเข้าสู่ระบบครั้งแรก

สำหรับระบบจริง ควรเปลี่ยน Password ใน Compose ไปใช้ Docker Secrets หรือ Secret Manager และไม่ควร Commit Secret ลง Git

#การตั้งค่า Linux Host

SonarQube ใช้ Elasticsearch ภายใน จึงอาจต้องตั้งค่า Kernel และ File Descriptor บน Linux

sudo sysctl -w vm.max_map_count=524288
sudo sysctl -w fs.file-max=131072
ulimit -n 131072
ulimit -u 8192

เมื่อต้องการให้คงอยู่หลัง Restart ให้เพิ่มค่า Kernel ลงใน /etc/sysctl.conf หรือไฟล์ภายใต้ /etc/sysctl.d/


#4. สร้าง Project และ Token ใน SonarQube

  1. เข้าสู่ระบบ SonarQube
  2. เลือก Create Project
  3. เลือกสร้างโครงการแบบ Local
  4. กำหนด Project Key เช่น express-api หรือ laravel-api
  5. สร้าง Analysis Token
  6. บันทึก Token ไว้ใน Environment Variable

ตัวอย่างบน Linux หรือ macOS

export SONAR_TOKEN="ค่าของ-token"

PowerShell

$env:SONAR_TOKEN="ค่าของ-token"

ไม่ควรใส่ Token ลงใน sonar-project.properties หรือ Commit ลง Repository


#ส่วนที่ 1: วิเคราะห์โครงการ Express.js

#5. โครงสร้างตัวอย่าง Express

express-api/
├── src/
│   ├── app.js
│   └── services/
│       └── user-service.js
├── tests/
│   └── app.test.js
├── coverage/
├── jest.config.js
├── package.json
└── sonar-project.properties

#6. ติดตั้ง Jest และ Supertest

npm install --save-dev jest supertest

ปรับ package.json

{
  "scripts": {
    "start": "node src/app.js",
    "test": "jest",
    "test:coverage": "jest --coverage"
  }
}

สร้าง jest.config.js

/** @type {import('jest').Config} */
module.exports = {
  testEnvironment: 'node',
  testMatch: ['**/tests/**/*.test.js'],
  collectCoverageFrom: [
    'src/**/*.js',
    '!src/**/*.config.js'
  ],
  coverageDirectory: 'coverage',
  coverageReporters: ['text', 'lcov'],
};

เมื่อรันคำสั่งต่อไปนี้ Jest จะสร้างไฟล์ coverage/lcov.info

npm run test:coverage

#7. ตั้งค่า sonar-project.properties สำหรับ Express

สร้างไฟล์ sonar-project.properties ที่ Root ของโครงการ

sonar.projectKey=express-api
sonar.projectName=Express API
sonar.projectVersion=1.0.0

sonar.sources=src
sonar.tests=tests
sonar.test.inclusions=tests/**/*.test.js

sonar.exclusions=node_modules/**,coverage/**,dist/**
sonar.coverage.exclusions=src/**/*.config.js,src/server.js

sonar.javascript.lcov.reportPaths=coverage/lcov.info
sonar.sourceEncoding=UTF-8

#ความหมายของค่าหลัก

Property หน้าที่
sonar.projectKey รหัสโครงการ ต้องตรงกับที่สร้างใน SonarQube
sonar.sources ตำแหน่ง Source Code
sonar.tests ตำแหน่ง Test Code
sonar.test.inclusions Pattern สำหรับระบุไฟล์ Test
sonar.exclusions ไฟล์ที่ไม่ต้องการวิเคราะห์
sonar.coverage.exclusions ไฟล์ที่วิเคราะห์ได้ แต่ไม่ต้องนำมาคำนวณ Coverage
sonar.javascript.lcov.reportPaths ตำแหน่งรายงาน LCOV

#8. รัน SonarScanner สำหรับ Express

#วิธีที่ 1: ใช้ SonarScanner CLI ที่ติดตั้งในเครื่อง

npm ci
npm run test:coverage
sonar-scanner \
  -Dsonar.host.url=http://localhost:9000 \
  -Dsonar.token="$SONAR_TOKEN"

#วิธีที่ 2: ใช้ SonarScanner Docker Image

ให้รัน Unit Test บนเครื่องก่อน เพื่อสร้าง coverage/lcov.info

npm ci
npm run test:coverage

จากนั้นรัน Scanner ภายใน Network เดียวกับ SonarQube

docker run --rm \
  --network sonar-network \
  -e SONAR_HOST_URL="http://sonarqube:9000" \
  -e SONAR_TOKEN="$SONAR_TOKEN" \
  -v "$PWD:/usr/src" \
  sonarsource/sonar-scanner-cli

สำหรับ PowerShell

docker run --rm `
  --network sonar-network `
  -e SONAR_HOST_URL="http://sonarqube:9000" `
  -e SONAR_TOKEN="$env:SONAR_TOKEN" `
  -v "${PWD}:/usr/src" `
  sonarsource/sonar-scanner-cli

#ส่วนที่ 2: วิเคราะห์โครงการ Laravel

#9. โครงสร้างที่เกี่ยวข้องใน Laravel

laravel-api/
├── app/
├── config/
├── database/
├── routes/
├── tests/
│   ├── Feature/
│   └── Unit/
├── build/
│   └── logs/
├── phpunit.xml
└── sonar-project.properties

#10. เตรียม PHPUnit Coverage

PHPUnit ต้องใช้ Coverage Driver เช่น Xdebug หรือ PCOV

ตรวจสอบ Xdebug

php -m | grep xdebug

ตัวอย่างติดตั้ง Xdebug บน Ubuntu

sudo apt update
sudo apt install php-xdebug

ตรวจสอบ PHPUnit

php artisan test

สร้าง Coverage Report รูปแบบ Clover XML

mkdir -p build/logs
XDEBUG_MODE=coverage vendor/bin/phpunit \
  --coverage-text \
  --coverage-clover=build/logs/clover.xml

กรณีรัน Laravel ผ่าน Docker Compose

docker compose exec -e XDEBUG_MODE=coverage app \
  vendor/bin/phpunit \
  --coverage-text \
  --coverage-clover=build/logs/clover.xml

ตรวจสอบว่าไฟล์ถูกสร้างแล้ว

ls -lh build/logs/clover.xml

#11. ตั้งค่า phpunit.xml

ตัวอย่างส่วนสำคัญใน phpunit.xml

<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
         bootstrap="vendor/autoload.php"
         colors="true">

    <testsuites>
        <testsuite name="Unit">
            <directory>tests/Unit</directory>
        </testsuite>
        <testsuite name="Feature">
            <directory>tests/Feature</directory>
        </testsuite>
    </testsuites>

    <source>
        <include>
            <directory suffix=".php">app</directory>
        </include>
    </source>

    <php>
        <env name="APP_ENV" value="testing"/>
        <env name="CACHE_STORE" value="array"/>
        <env name="SESSION_DRIVER" value="array"/>
        <env name="QUEUE_CONNECTION" value="sync"/>
    </php>
</phpunit>

#12. ตั้งค่า sonar-project.properties สำหรับ Laravel

sonar.projectKey=laravel-api
sonar.projectName=Laravel API
sonar.projectVersion=1.0.0

sonar.sources=app,routes,config,database
sonar.tests=tests
sonar.test.inclusions=tests/**/*.php

sonar.exclusions=vendor/**,storage/**,bootstrap/cache/**,public/build/**,node_modules/**
sonar.coverage.exclusions=app/Providers/**,app/Console/**,database/migrations/**,routes/**,config/**

sonar.php.coverage.reportPaths=build/logs/clover.xml
sonar.sourceEncoding=UTF-8

#ควร Exclude อะไรบ้าง

ไม่ควร Exclude มากเกินไป เพราะจะทำให้ค่าคุณภาพดูดีเกินจริง โดยทั่วไปอาจ Exclude

  • vendor/**
  • Generated Files
  • Cache และ Log
  • Frontend Build Output
  • Migration ที่ไม่ต้องการวัด Coverage
  • Service Provider ที่มีเพียง Wiring Code

ไม่ควร Exclude Controller, Service, Repository หรือ Domain Logic เพียงเพราะเขียน Test ยาก

#13. รัน SonarScanner สำหรับ Laravel

สร้าง Coverage ก่อน

composer install --no-interaction --prefer-dist
mkdir -p build/logs
XDEBUG_MODE=coverage vendor/bin/phpunit \
  --coverage-clover=build/logs/clover.xml

รัน Scanner

sonar-scanner \
  -Dsonar.host.url=http://localhost:9000 \
  -Dsonar.token="$SONAR_TOKEN"

หรือใช้ Docker

docker run --rm \
  --network sonar-network \
  -e SONAR_HOST_URL="http://sonarqube:9000" \
  -e SONAR_TOKEN="$SONAR_TOKEN" \
  -v "$PWD:/usr/src" \
  sonarsource/sonar-scanner-cli

#14. ตัวอย่าง GitHub Actions สำหรับ Express

สร้าง .github/workflows/sonarqube-express.yml

name: Express Quality Check

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  test-and-analyze:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Setup Node.js
        uses: actions/setup-node@v6
        with:
          node-version: 24
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Run tests with coverage
        run: npm run test:coverage

      - name: SonarQube Scan
        uses: SonarSource/sonarqube-scan-action@v8.1.0
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }}

ตั้งค่าใน GitHub Repository

  • Secret: SONAR_TOKEN
  • Variable: SONAR_HOST_URL

SonarQube แบบ Self-hosted ต้องเข้าถึงได้จาก GitHub-hosted Runner หาก Server อยู่ในเครือข่ายภายใน ให้ใช้ Self-hosted Runner, VPN หรือ Network Architecture ที่เหมาะสม


#15. ตัวอย่าง GitHub Actions สำหรับ Laravel

สร้าง .github/workflows/sonarqube-laravel.yml

name: Laravel Quality Check

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  test-and-analyze:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.4'
          coverage: xdebug
          tools: composer:v2

      - name: Install dependencies
        run: composer install --no-interaction --prefer-dist --no-progress

      - name: Prepare environment
        run: |
          cp .env.example .env
          php artisan key:generate
          mkdir -p build/logs

      - name: Run tests with coverage
        env:
          XDEBUG_MODE: coverage
        run: vendor/bin/phpunit --coverage-clover=build/logs/clover.xml

      - name: SonarQube Scan
        uses: SonarSource/sonarqube-scan-action@v8.1.0
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }}

ในโครงการจริง อาจต้องเพิ่ม Service Container สำหรับ PostgreSQL, MySQL หรือ Redis ตาม Dependency ของ Test Suite


#16. ตัวอย่าง Quality Gate สำหรับทีมพัฒนา

Quality Gate คือชุดเงื่อนไขที่ใช้ตัดสินว่าผลวิเคราะห์ผ่านหรือไม่ ควรเน้น New Code เพื่อป้องกันไม่ให้ทีมสร้าง Technical Debt เพิ่ม

ตัวอย่างเกณฑ์เริ่มต้น

Metric บน New Code เกณฑ์แนะนำ
Reliability Rating A
Security Rating A
Maintainability Rating A
Coverage อย่างน้อย 80%
Duplicated Lines ไม่เกิน 3%
Security Hotspots Reviewed 100%
Blocker/Critical Issues 0

ค่าดังกล่าวเป็นตัวอย่างนโยบายของทีม ไม่จำเป็นต้องเหมือนกันทุกโครงการ ระบบ Legacy อาจเริ่มจากเกณฑ์ที่ทำได้จริง แล้วเพิ่มความเข้มงวดเฉพาะ New Code


#17. วิธีอ่านผลบน Dashboard

#Overview

ใช้ดูภาพรวมของโครงการ เช่น Quality Gate, Issue Count, Coverage และ Duplication

#Issues

กรองตาม

  • Severity
  • Issue Type
  • Rule
  • File
  • Assignee
  • Status

ควรอ่านคำอธิบายของ Rule และแก้ Root Cause แทนการกด False Positive โดยไม่มีเหตุผล

#Measures

ใช้วิเคราะห์ Metric เชิงลึก เช่น

  • Cognitive Complexity
  • Cyclomatic Complexity
  • Duplicated Blocks
  • Lines to Cover
  • Uncovered Lines
  • Technical Debt

#Security Hotspots

ต้องมีผู้รับผิดชอบ Review และกำหนดสถานะว่า Safe หรือ Fixed พร้อมเหตุผลที่ตรวจสอบได้


#18. ปัญหาที่พบบ่อย

#18.1 Coverage เป็น 0%

ตรวจสอบตามลำดับ

# Express
ls -lh coverage/lcov.info

# Laravel
ls -lh build/logs/clover.xml

สาเหตุที่พบบ่อย

  • ยังไม่ได้รัน Test ก่อน Scanner
  • Report Path ไม่ตรง
  • Coverage Report ไม่ได้ถูก Mount เข้า Scanner Container
  • Path ภายใน Report ไม่ตรงกับ Working Directory
  • Xdebug หรือ PCOV ไม่ทำงาน

#18.2 Scanner เชื่อมต่อ SonarQube ไม่ได้

หาก Scanner รันใน Container ห้ามใช้ localhost:9000 เพื่ออ้างถึง SonarQube Container ให้ใช้ชื่อ Service เช่น

http://sonarqube:9000

และต้องอยู่ใน Docker Network เดียวกัน

#18.3 SonarQube เริ่มระบบไม่ได้บน Linux

ตรวจสอบ

sysctl vm.max_map_count
ulimit -n
docker compose logs sonarqube

#18.4 Permission Denied บน Volume

ตรวจสอบสิทธิ์ของ Volume หรือ Bind Mount และไม่ควรแชร์ SonarQube Volume ชุดเดียวกันระหว่างหลาย Instance

#18.5 วิเคราะห์ไฟล์มากเกินไป

Exclude Dependency และ Generated Files เช่น

sonar.exclusions=node_modules/**,vendor/**,coverage/**,storage/**,public/build/**

#18.6 Quality Gate ไม่ทำให้ Pipeline ล้มเหลว

ตั้งค่าให้ Scanner รอผล Quality Gate

sonar-scanner \
  -Dsonar.host.url=http://localhost:9000 \
  -Dsonar.token="$SONAR_TOKEN" \
  -Dsonar.qualitygate.wait=true

การรอผลอาจทำให้ Pipeline ใช้เวลานานขึ้น จึงควรเลือกวิธีที่เหมาะกับ CI Platform และ Edition ที่ใช้งาน


#19. แนวปฏิบัติที่แนะนำ

  1. รัน Test ก่อน SonarScanner เสมอ เพื่อให้ Coverage Report พร้อมใช้งาน
  2. เน้นคุณภาพของ New Code มากกว่าบังคับแก้ Legacy Code ทั้งหมดทันที
  3. ไม่เก็บ Token ใน Repository ให้ใช้ Secret Management
  4. Pin Version ในระบบจริง ทั้ง SonarQube, Scanner และ GitHub Action
  5. ใช้ fetch-depth: 0 ใน CI เพื่อให้ข้อมูล SCM และประวัติการเปลี่ยนแปลงสมบูรณ์ขึ้น
  6. ตรวจสอบ Security Hotspots โดยมนุษย์ ไม่ใช่เพียงดูจำนวน Issue
  7. อย่า Exclude โค้ดธุรกิจเพื่อทำให้ Coverage สูงขึ้น
  8. กำหนด Owner ของ Issue และติดตาม Technical Debt ใน Sprint
  9. ใช้ SonarQube for IDE เพื่อพบปัญหาตั้งแต่ตอนเขียนโค้ด
  10. ทบทวน Quality Profile และ Quality Gate เป็นระยะ ให้สอดคล้องกับความเสี่ยงของระบบ

#20. Checklist สำหรับนำไปใช้จริง

  • [ ] SonarQube และ PostgreSQL ทำงานปกติ
  • [ ] เปลี่ยนรหัสผ่าน Administrator แล้ว
  • [ ] สร้าง Project Key
  • [ ] สร้าง Analysis Token
  • [ ] เก็บ Token ใน Secret Manager
  • [ ] กำหนด sonar-project.properties
  • [ ] Unit Test ผ่าน
  • [ ] Coverage Report ถูกสร้าง
  • [ ] SonarScanner วิเคราะห์สำเร็จ
  • [ ] Dashboard แสดง Coverage ถูกต้อง
  • [ ] กำหนด Quality Gate
  • [ ] เชื่อมต่อเข้ากับ CI/CD
  • [ ] Pipeline หยุดเมื่อ Quality Gate ไม่ผ่าน
  • [ ] ทีมมีขั้นตอน Review Security Hotspots

#สรุป

SonarQube ช่วยเปลี่ยนการตรวจสอบคุณภาพโค้ดจากกิจกรรมที่ทำเป็นครั้งคราวให้กลายเป็นกระบวนการอัตโนมัติใน Software Development Lifecycle

สำหรับ Express.js ให้ Jest สร้าง coverage/lcov.info แล้วกำหนด sonar.javascript.lcov.reportPaths ส่วน Laravel ให้ PHPUnit สร้าง build/logs/clover.xml แล้วกำหนด sonar.php.coverage.reportPaths

หัวใจสำคัญไม่ใช่การทำให้ Dashboard เป็นสีเขียวเพียงอย่างเดียว แต่คือการใช้ผลวิเคราะห์เพื่อป้องกัน Defect และ Technical Debt ใหม่ พร้อมสร้างมาตรฐานคุณภาพที่ทีมสามารถรักษาได้อย่างต่อเนื่อง


#เอกสารอ้างอิง