Files
agent-unit-testing/docs/10-sonarqube-100-percent-coverage-guide.md
T
Pham-Tan-Thien 97f3833bf2 feat: enforce >90% SonarQube coverage rules and exclusions for Laravel
- Enhance laravel-unit-test skill with layer-by-layer branch coverage rules
- Expand Laravel boilerplate exclusions in sonar-project.properties template
- Update SonarQube guide with Laravel comprehensive exclusions
2026-09-14 10:33:35 +07:00

6.7 KiB

10. Chiến Lược Đạt > 90% Code Coverage Trên SonarQube (Laravel & CakePHP)

Tài liệu này giải thích chi tiết cách cấu hình và kỹ thuật viết Unit & Integration Test để đạt chỉ số Line & Branch Coverage > 90% khi quét qua hệ thống phân tích chất lượng mã nguồn SonarQube / SonarCloud (chạy Local hoặc CI/CD).


1. Công Thức Tính Độ Phủ Của SonarQube

SonarQube tính toán chỉ số Coverage tổng hợp dựa trên cả dòng lệnh (Lines) lẫn điều kiện rẽ nhánh (Branches / Conditions):

\text{Coverage} = \frac{\text{Lines Covered} + \text{Branches Covered}}{\text{Total Lines} + \text{Total Branches}} \times 100\%

⚠️ Lưu ý tối quan trọng: Nếu một dòng code chạy qua nhưng chỉ thỏa mãn 1 trong 2 nhánh của toán tử ternary (?:), null coalescing (??) hoặc biểu thức logic (&&, ||), SonarQube sẽ tính là Partially Covered (50% hoặc 75%) và làm tụt chỉ số Coverage tổng thể.


2. Cấu Hình Cốt Lõi: Scope & Exclusions

Tuyệt đối không đưa các file framework bootstrap, installer hoặc cấu hình vào phạm vi đo Coverage, vì chúng không chứa business logic và sẽ kéo tụt điểm số xuống dưới 80%.

A. Cấu hình SonarQube cho CakePHP (sonar-project.properties)

sonar.projectKey=cakephp-app
sonar.projectName=CakePHP Application
sonar.sources=src
sonar.tests=tests
sonar.php.coverage.reportPaths=coverage.xml
sonar.php.tests.reportPath=phpunit-report.xml

# Inclusions: Tập trung đo các tầng nghiệp vụ cốt lõi
sonar.coverage.inclusions=src/Model/Table/**,src/Model/Entity/**,src/Controller/**,src/Service/**,src/Command/**,src/Controller/Component/**,src/Middleware/**,src/Mailer/**,src/Form/**,src/Model/Behavior/**,src/View/Helper/**

# Exclusions: Loại trừ framework boilerplate để đạt > 90%
sonar.coverage.exclusions=src/Console/Installer.php,src/Application.php,src/View/AppView.php,config/**,bin/**,plugins/**,tests/**,templates/**
sonar.cpd.exclusions=tests/**

B. Cấu hình SonarQube cho Laravel (sonar-project.properties)

sonar.projectKey=laravel-app
sonar.projectName=Laravel Application
sonar.sources=app
sonar.tests=tests
sonar.php.coverage.reportPaths=coverage.xml
sonar.php.tests.reportPath=phpunit-report.xml

# Inclusions:
sonar.coverage.inclusions=app/Services/**,app/Actions/**,app/Http/Controllers/**,app/Http/Requests/**,app/Rules/**,app/Jobs/**,app/Policies/**,app/Models/**,app/Repositories/**,app/Console/Commands/**,app/Listeners/**,app/Notifications/**,app/Mail/**

# Exclusions: Loại trừ toàn bộ boilerplate, configs, migrations để đảm bảo Coverage > 90%
sonar.coverage.exclusions=app/Providers/**,app/Console/Kernel.php,app/Exceptions/Handler.php,app/Http/Kernel.php,app/Http/Middleware/TrustHosts.php,app/Http/Middleware/TrustProxies.php,app/Http/Middleware/VerifyCsrfToken.php,app/Http/Middleware/EncryptCookies.php,app/Http/Middleware/TrimStrings.php,app/Http/Middleware/PreventRequestsDuringMaintenance.php,bootstrap/**,config/**,database/**,tests/**
sonar.cpd.exclusions=tests/**

3. Cách Xuất Báo Cáo Coverage Cho SonarQube

Trong phpunit.xml (hoặc phpunit.xml.dist), kích hoạt xuất báo cáo định dạng Clover XML:

<coverage>
    <report>
        <clover outputFile="coverage.xml"/>
    </report>
</coverage>

Chạy qua Smart Test Runner để tự động đo và xuất file:

./test-runner.sh --coverage-clover coverage.xml

Sau khi có file coverage.xml, quét SonarQube cục bộ bằng lệnh:

sonar-scanner
# Hoặc chạy container sonarsource/sonar-scanner-cli nếu chưa cài đặt CLI

4. Kỹ Thuật Đạt > 90% Branch Coverage Cho Mọi Dự Án PHP

A. Toán tử Null Coalescing (??)

$timeout = $config['timeout'] ?? 30;
  • Nhánh 1 (Left branch): Truyền $config có key 'timeout' => 60.
  • Nhánh 2 (Right branch): Truyền $config = [] (không có key 'timeout').

B. Toán tử Ternary (?:) và Guard Clauses

return $user->isVip() ? 0.2 : 0.05;
  • Nhánh 1: $user->isVip() === true.
  • Nhánh 2: $user->isVip() === false.

C. Khối try - catch - finally

try {
    return $this->gateway->charge($amount);
} catch (PaymentException $e) {
    Log::error($e->getMessage());
    return false;
}
  • Bắt buộc: Phải có ít nhất 1 test case ép mock ném PaymentException để kích hoạt toàn bộ khối catch.

D. Điều kiện logic phức hợp (&&, ||)

if ($user->isActive() && $order->isPaid()) { ... }
  • Cần tối thiểu 3 test cases:
    1. isActive = true và isPaid = true (Vào if).
    2. isActive = false (Short-circuit).
    3. isActive = true nhưng isPaid = false.

5. Chiến Lược Đạt > 90% Riêng Cho Từng Tầng CakePHP

1. ORM Table (validationDefault):

  • Sai lầm phổ biến: Chỉ test submit dữ liệu hợp lệ -> Bỏ sót 60% nhánh validation lỗi.
  • Kỹ thuật đạt > 90%: Viết test case vi phạm cho từng rule:
    • Thiếu trường bắt buộc (requirePresence).
    • Chuỗi rỗng (notEmptyString).
    • Quá độ dài tối đa (maxLength).
    • Sai định dạng (email, integer, url).

2. ORM Table (buildRules):

  • Viết 1 test case lưu dữ liệu thỏa mãn rule.
  • Viết 1 test case cố tình trùng lặp (đối với isUnique) hoặc ID không tồn tại (đối với existsIn) để kích hoạt nhánh rule validation failure.

3. Controller Actions:

  • Viết GET request: Render view thành công (200).
  • Viết POST/PUT request:
    • Happy Path: Save thành công -> Redirect 302 + Flash Success.
    • Failure Path: Save thất bại (dữ liệu thiếu/lỗi) -> Render 200 + Flash Error.
    • Not Found Path: ID không tồn tại -> Bắt RecordNotFoundException (404).
    • Unauthorized Path: Không có session -> Redirect Login.

4. Console Commands:

  • Test chạy thành công: assertExitSuccess().
  • Test chạy lỗi (thiếu đối số bắt buộc hoặc option sai): assertExitError().
  • Test thông báo output: assertOutputContains().

6. Checklist Kiểm Soát Chất Lượng Trước Khi Quét SonarQube

  • File sonar-project.properties đã cấu hình đúng sonar.coverage.inclusions và sonar.coverage.exclusions.
  • Báo cáo coverage.xml đã được sinh và không bị rỗng.
  • Mọi phương thức public đều có ít nhất 1 test case Happy Path và 1 test case Failure/Edge.
  • Tất cả các khối try/catch đều được chạy qua.
  • Không có file test nào bị SKIP hoặc FAIL.