feat: enforce >90% SonarQube coverage standards for CakePHP and Laravel
- Add sonar-project.properties templates for CakePHP and Laravel - Update install.sh to automatically provision SonarQube config - Enhance cakephp-unit-test skill with exhaustive branch coverage rules - Update SonarQube guide with CakePHP specific rules and exclusion patterns - Enhance TableTest template with buildRules and validation failure cases
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# 10. Chiến Lược Đạt 100% Code Coverage Trên SonarQube
|
||||
# 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 Test để đạt điểm tối đa (Line & Branch Coverage) khi quét qua hệ thống phân tích chất lượng mã nguồn **SonarQube / SonarCloud**.
|
||||
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).
|
||||
|
||||
---
|
||||
|
||||
@@ -10,70 +10,92 @@ SonarQube tính toán chỉ số Coverage tổng hợp dựa trên cả dòng l
|
||||
|
||||
$$\text{Coverage} = \frac{\text{Lines Covered} + \text{Branches Covered}}{\text{Total Lines} + \text{Total Branches}} \times 100\%$$
|
||||
|
||||
> ⚠️ **Lưu ý 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 (`?:`) hoặc null coalescing (`??`), SonarQube sẽ tính là **Partially Covered** và trừ điểm độ phủ của dòng đó.
|
||||
> ⚠️ **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
|
||||
|
||||
Không bao giờ đưa các file cấu hình hoặc framework boilerplate vào phạm vi đo Coverage. Trong file cấu hình `sonar-project.properties` tại thư mục gốc dự án:
|
||||
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`)
|
||||
```properties
|
||||
# 1. Khai báo đường dẫn file báo cáo Coverage sinh ra từ PHPUnit
|
||||
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
|
||||
|
||||
# 2. Phạm vi đo Coverage: CHỈ ĐO CÁC TẦNG CHỨA LOGIC NGHIỆP VỤ
|
||||
sonar.coverage.inclusions=app/Services/**,app/Actions/**,app/Rules/**,app/Jobs/**,app/Policies/**,app/Http/Requests/**,app/Models/**
|
||||
# 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/**
|
||||
|
||||
# 3. Loại trừ các file framework, boilerplate không cần viết Unit Test
|
||||
# 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`)
|
||||
```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:
|
||||
sonar.coverage.exclusions=app/Providers/**,app/Console/Kernel.php,app/Http/Middleware/TrustProxies.php,bootstrap/**,config/**,database/**,tests/**
|
||||
sonar.cpd.exclusions=tests/**
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Cách Xuất Báo Cáo Coverage Chuẩn SonarQube (`coverage.xml`)
|
||||
|
||||
Trong file `phpunit.xml` (hoặc `phpunit.xml.dist`), bổ sung cấu hình xuất định dạng Clover XML:
|
||||
## 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:
|
||||
```xml
|
||||
<coverage>
|
||||
<report>
|
||||
<clover outputFile="coverage.xml"/>
|
||||
<html outputDirectory="coverage-html"/>
|
||||
</report>
|
||||
</coverage>
|
||||
```
|
||||
|
||||
Hoặc khi chạy lệnh thông qua `test-runner.sh`:
|
||||
Chạy qua Smart Test Runner để tự động đo và xuất file:
|
||||
```bash
|
||||
./test-runner.sh --coverage-clover coverage.xml
|
||||
```
|
||||
|
||||
Sau khi có file `coverage.xml`, quét SonarQube cục bộ bằng lệnh:
|
||||
```bash
|
||||
sonar-scanner
|
||||
# Hoặc chạy container sonarsource/sonar-scanner-cli nếu chưa cài đặt CLI
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Checklist Đánh Bay Các Lỗi "Uncovered Branches" Của SonarQube
|
||||
|
||||
Khi AI hoặc dev viết test, cần kiểm tra 5 bẫy rẽ nhánh sau để chạm mốc 100%:
|
||||
## 4. Kỹ Thuật Đạt > 90% Branch Coverage Cho Mọi Dự Án PHP
|
||||
|
||||
### A. Toán tử Null Coalescing (`??`)
|
||||
```php
|
||||
// Code nguồn:
|
||||
$timeout = $config['timeout'] ?? 30;
|
||||
```
|
||||
- **Test Case 1 (Left branch)**: Truyền `$config` có chứa key `'timeout' => 60`.
|
||||
- **Test Case 2 (Right branch)**: Truyền `$config = []` (không có key `'timeout'`).
|
||||
- **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
|
||||
```php
|
||||
// Code nguồn:
|
||||
return $user->isVip() ? 0.2 : 0.05;
|
||||
```
|
||||
- **Test Case 1**: Truyền user với `isVip() === true`.
|
||||
- **Test Case 2**: Truyền user với `isVip() === false`.
|
||||
- **Nhánh 1**: `$user->isVip() === true`.
|
||||
- **Nhánh 2**: `$user->isVip() === false`.
|
||||
|
||||
### C. Khối `try - catch - finally`
|
||||
```php
|
||||
// Code nguồn:
|
||||
try {
|
||||
return $this->gateway->charge($amount);
|
||||
} catch (PaymentException $e) {
|
||||
@@ -81,7 +103,52 @@ try {
|
||||
return false;
|
||||
}
|
||||
```
|
||||
- **Bắt buộc**: Phải có 1 bài test giả lập mock `->andThrow(new PaymentException('Lỗi cổng thanh toán'))` để ép luồng chạy vào trong khối `catch`.
|
||||
- **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. Switch / Match Expression
|
||||
Mọi case trong `match` hoặc `switch` (kể cả nhánh `default`) đều phải có ít nhất 1 bài test kích hoạt.
|
||||
### D. Điều kiện logic phức hợp (`&&`, `||`)
|
||||
```php
|
||||
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`.
|
||||
|
||||
Reference in New Issue
Block a user