From 8f7212ef658813c90838e33723308606a52b342a Mon Sep 17 00:00:00 2001 From: thienpt Date: Mon, 14 Sep 2026 10:31:56 +0700 Subject: [PATCH] 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 --- .claude/skills/cakephp-unit-test/skill.md | 36 ++++-- ...10-sonarqube-100-percent-coverage-guide.md | 119 ++++++++++++++---- install.sh | 10 ++ .../configs/sonar-project.properties.template | 22 ++++ .../cakephp/phpunit/TableTest.template.php | 27 ++++ .../configs/sonar-project.properties.template | 22 ++++ 6 files changed, 201 insertions(+), 35 deletions(-) create mode 100644 templates/cakephp/configs/sonar-project.properties.template create mode 100644 templates/configs/sonar-project.properties.template diff --git a/.claude/skills/cakephp-unit-test/skill.md b/.claude/skills/cakephp-unit-test/skill.md index 8881187..654bc8d 100644 --- a/.claude/skills/cakephp-unit-test/skill.md +++ b/.claude/skills/cakephp-unit-test/skill.md @@ -105,15 +105,33 @@ Skill này là công cụ chính để tự động tạo ra file Test cho bất $this->enableSecurityToken(); ``` -### Bước 4: Lập Ma Trận Test Scenarios (Bao Phủ 100% Nhánh SonarQube) -- **100% Public Methods**: Mọi phương thức public đều phải có test. - - Với Table: bắt buộc test custom finders, validation failures, save thành công & thất bại. - - Với Controller: bắt buộc test thành công (200/302), validation failure (422/render form), unauthorized (403/redirect login), not found (404). - - Với Command: bắt buộc test `execute()` với input đúng, input thiếu, exit code. -- **Bao phủ nhánh rẽ**: - - Kịch bản Null Coalescing `??` và Ternary `?:`. - - Kịch bản `try - catch`: ép mock/dữ liệu ném Exception để chạy qua khối `catch`. - - Mọi `if`, `else if`, `else`, `switch`, `match`. +### Bước 4: Lập Ma Trận Test Scenarios (Đảm Bảo Coverage > 90% Trên SonarQube) +Mục tiêu cốt lõi: SonarQube tính cả Line Coverage và Branch Coverage. Để đạt **Coverage > 90%**, Agent phải tuân thủ nghiêm ngặt ma trận sau: + +1. **100% Public Methods**: Mọi phương thức public của class đều phải có test. +2. **Quét 100% Nhánh Rẽ (Branch / Condition Coverage)**: + - **Toán tử `??` (Null Coalescing)**: Bắt buộc 2 test case (có giá trị & null/empty). + - **Toán tử `?:` (Ternary)**: Bắt buộc 2 test case (true & false). + - **Toán tử logic `&&` và `||`**: Phải test các kịch bản kết hợp để thỏa mãn điều kiện rẽ nhánh SonarQube. + - **Khối `try - catch`**: BẮT BUỘC có test case ép mock/dữ liệu ném Exception để chạy qua khối `catch`. + - **Mọi `if`, `else if`, `else`, `switch`, `match`** (kể cả nhánh default). +3. **Chiến Lược Riêng Cho Từng Thành Phần CakePHP Để Đạt > 90%**: + - **ORM Table (`validationDefault`)**: Bắt buộc test dữ liệu hợp lệ VÀ các trường hợp vi phạm cho **từng rule**: + - Thiếu trường (`requirePresence`) + - Chuỗi rỗng (`notEmptyString`) + - Quá độ dài (`maxLength`) + - Sai định dạng (`email`, `integer`, `url`, số âm). + - **ORM Table (`buildRules`)**: Test cả 2 trường hợp: rule hợp lệ và rule bị vi phạm (`isUnique`, `existsIn`). + - **Controller (IntegrationTestTrait)**: + - GET request: Render view thành công (200). + - POST/PUT save thành công: Redirect 302 + Flash Success. + - POST/PUT save thất bại: Render view 200 + Flash Error. + - Unauthorized / Unauthenticated: Redirect login (hoặc 403). + - Not Found: Bắt RecordNotFoundException (404). + - **Console Command (ConsoleIntegrationTestTrait)**: + - Test chạy thành công: `assertExitSuccess()`. + - Test chạy lỗi hoặc thiếu options: `assertExitError()`. + - Test stdout chứa thông báo mong đợi: `assertOutputContains()`. ### Bước 5: Sinh Mã Nguồn Test Chuẩn Mirror 1. Xác định đường dẫn file test theo chuẩn CakePHP: diff --git a/docs/10-sonarqube-100-percent-coverage-guide.md b/docs/10-sonarqube-100-percent-coverage-guide.md index d271d5f..7a50cbb 100644 --- a/docs/10-sonarqube-100-percent-coverage-guide.md +++ b/docs/10-sonarqube-100-percent-coverage-guide.md @@ -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 - ``` -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`. diff --git a/install.sh b/install.sh index 3391bea..4911361 100755 --- a/install.sh +++ b/install.sh @@ -297,6 +297,16 @@ if [ -d "$TARGET_DIR/.git" ]; then fi fi +# F. Tạo file cấu hình SonarQube chuẩn (sonar-project.properties) nếu chưa có +if [ ! -f "$TARGET_DIR/sonar-project.properties" ]; then + if [ "$FRAMEWORK" = "cakephp" ]; then + cp "$PACKAGE_ROOT/templates/cakephp/configs/sonar-project.properties.template" "$TARGET_DIR/sonar-project.properties" + else + cp "$PACKAGE_ROOT/templates/configs/sonar-project.properties.template" "$TARGET_DIR/sonar-project.properties" + fi + echo -e " ${GREEN}✓${NC} Đã tạo cấu hình SonarQube (sonar-project.properties) nhắm mục tiêu Coverage > 90%" +fi + echo -e "\n${GREEN}${BOLD}🎉 CÀI ĐẶT HOÀN TẤT THÀNH CÔNG VÀO SOURCE CODE!${NC}" echo -e "\n${YELLOW}${BOLD}BÂY GIỜ BẠN CÓ THỂ VIẾT UNIT TEST NGAY:${NC}" echo -e "1. Mở Claude Code tại source code này:" diff --git a/templates/cakephp/configs/sonar-project.properties.template b/templates/cakephp/configs/sonar-project.properties.template new file mode 100644 index 0000000..56fdab8 --- /dev/null +++ b/templates/cakephp/configs/sonar-project.properties.template @@ -0,0 +1,22 @@ +# ============================================================================== +# SonarQube Configuration for CakePHP Unit Testing (> 90% Coverage Target) +# ============================================================================== + +sonar.projectKey=cakephp-app +sonar.projectName=CakePHP Application +sonar.projectVersion=1.0 + +# Đường dẫn nguồn và test +sonar.sources=src +sonar.tests=tests + +# Báo cáo Code Coverage sinh ra từ PHPUnit / bin/cake test app +sonar.php.coverage.reportPaths=coverage.xml +sonar.php.tests.reportPath=phpunit-report.xml + +# Inclusions: Tập trung đo độ phủ 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, console installer, configs để đảm bảo Coverage > 90% +sonar.coverage.exclusions=src/Console/Installer.php,src/Application.php,src/View/AppView.php,config/**,bin/**,plugins/**,tests/**,templates/** +sonar.cpd.exclusions=tests/** diff --git a/templates/cakephp/phpunit/TableTest.template.php b/templates/cakephp/phpunit/TableTest.template.php index f547143..1c02603 100644 --- a/templates/cakephp/phpunit/TableTest.template.php +++ b/templates/cakephp/phpunit/TableTest.template.php @@ -101,4 +101,31 @@ class {{TABLE_NAME}}TableTest extends TestCase $saved = $this->{{VARIABLE_NAME}}->get($result->id); $this->assertSame('Sample Record', $saved->name); } + + /** + * Test buildRules method for uniqueness + */ + public function testBuildRulesUnique(): void + { + // Arrange + $entity1 = $this->{{VARIABLE_NAME}}->newEntity([ + 'name' => 'Unique Item', + 'email' => 'unique@example.com', + ]); + $this->{{VARIABLE_NAME}}->save($entity1); + + // Act - Attempt to save duplicate + $entity2 = $this->{{VARIABLE_NAME}}->newEntity([ + 'name' => 'Duplicate Item', + 'email' => 'unique@example.com', + ]); + $result = $this->{{VARIABLE_NAME}}->save($entity2); + + // Assert - Rule should fail if unique rule exists + if (!empty($entity2->getErrors())) { + $this->assertFalse($result); + } else { + $this->assertTrue(true); + } + } } diff --git a/templates/configs/sonar-project.properties.template b/templates/configs/sonar-project.properties.template new file mode 100644 index 0000000..832dca2 --- /dev/null +++ b/templates/configs/sonar-project.properties.template @@ -0,0 +1,22 @@ +# ============================================================================== +# SonarQube Configuration for Laravel Unit Testing (> 90% Coverage Target) +# ============================================================================== + +sonar.projectKey=laravel-app +sonar.projectName=Laravel Application +sonar.projectVersion=1.0 + +# Đường dẫn nguồn và test +sonar.sources=app +sonar.tests=tests + +# Báo cáo Code Coverage sinh ra từ PHPUnit / Artisan test +sonar.php.coverage.reportPaths=coverage.xml +sonar.php.tests.reportPath=phpunit-report.xml + +# Inclusions: Tập trung đo độ phủ các tầng nghiệp vụ cốt lõi +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ừ boilerplate, migrations, seeders, config để đảm bảo Coverage > 90% +sonar.coverage.exclusions=app/Providers/**,app/Console/Kernel.php,app/Http/Middleware/TrustProxies.php,bootstrap/**,config/**,database/**,tests/** +sonar.cpd.exclusions=tests/**