feat: decouple Laravel and CakePHP installers, remove SonarQube artifacts, and enforce complete unit testing standards

- Decouple framework installation: add install-laravel.sh and install-cakephp.sh, isolate skills and docs per framework, and auto-clean leftover artifacts
- Remove all SonarQube configs, templates, and references
- Enforce comprehensive test completeness: 100% public methods, edge cases, full branch coverage, exception paths, and state assertions
- Replace all placeholder assertions in PHPUnit and Pest templates with full realistic implementations
- Rename and update 10-comprehensive-test-coverage-guide.md
This commit is contained in:
2026-09-17 14:25:02 +07:00
parent 97f3833bf2
commit da1bd48444
33 changed files with 1004 additions and 594 deletions
@@ -0,0 +1,189 @@
# 10. Chiến Lược Viết Test Đầy Đủ & Đạt Độ Phủ Toàn Diện (Laravel & CakePHP)
Tài liệu này cung cấp cẩm nang chi tiết về nguyên lý, phương pháp và kỹ thuật viết Unit & Integration Test **hoàn chỉnh, đầy đủ kịch bản, bao phủ 100% logic và mọi nhánh rẽ mã nguồn** cho cả hai framework Laravel và CakePHP.
---
## 1. Nguyên Tắc Cốt Lõi Của Việc Viết Test Đầy Đủ
Một file test được coi là **đầy đủ (comprehensive)** khi và chỉ khi:
1. **100% Public Methods** của class đều có bộ test tương ứng (không bỏ sót helper methods, hooks, getters/setters có xử lý logic).
2. **Line & Branch Coverage tối đa**: Chạy qua mọi dòng code và kích hoạt tất cả các nhánh rẽ điều kiện (`if/else`, `??`, `?:`, `switch`, `match`, `&&`, `||`).
3. **Đầy đủ 4 nhóm kịch bản cho mỗi phương thức**:
- **Happy Path**: Dữ liệu chuẩn, kết quả mong đợi, kiểu trả về đúng.
- **Boundary & Edge Cases**: Giá trị biên, `null`, chuỗi rỗng, số 0, số âm, int max, mảng rỗng.
- **Exception & Error Handling**: Ép mock ném ngoại lệ, kiểm tra xử lý trong khối `catch`, rollback dữ liệu.
- **State & Side Effects**: Xác minh thay đổi dữ liệu trong Database, Event được dispatch, Job được queue, Mail được gửi.
4. **Không viết test hình thức**: Tuyệt đối không dùng test rỗng (`assertTrue(true)`), không dùng `// TODO`, phải có strict assertion kiểm tra giá trị và trạng thái thật.
---
## 2. Cách Đo Độ Phủ (Code Coverage) Trực Tiếp Bằng PHPUnit & Pest
Không cần cấu hình phức tạp, bạn có thể đo trực tiếp độ phủ mã nguồn thông qua PHPUnit hoặc Pest Runner:
### A. Đo Coverage qua Smart Test Runner (Tự động):
```bash
# Hiển thị báo cáo độ phủ ngay trên Terminal
./test-runner.sh --coverage
# Xuất báo cáo trực quan dạng HTML (mở file html-coverage/index.html trong trình duyệt)
./test-runner.sh --coverage-html html-coverage
# Chặn test fail nếu độ phủ thấp hơn ngưỡng mong muốn (ví dụ 85%)
./test-runner.sh --coverage --min=85
```
### B. Chạy trực tiếp qua PHP CLI / Sail / Docker:
```bash
# Laravel (PHPUnit hoặc Pest)
php artisan test --coverage --min=85
./vendor/bin/pest --coverage --min=85
# CakePHP
bin/cake test app --coverage-text
./vendor/bin/phpunit --coverage-text
```
---
## 3. Kỹ Thuật Viết Test Bao Phủ 100% Nhánh Rẽ (Branch Coverage)
### A. Toán tử Null Coalescing (`??`)
Toán tử `??` luôn tạo ra 2 nhánh logic ngầm:
```php
$timeout = $config['timeout'] ?? 30;
```
- **Kịch bản 1 (Left branch - Có giá trị)**: Truyền `$config` có key `'timeout' => 60` -> Assert kết quả nhận được `60`.
- **Kịch bản 2 (Right branch - Null hoặc không tồn tại)**: Truyền `$config = []` -> Assert kết quả nhận được giá trị mặc định `30`.
### B. Toán tử Ternary (`?:`) và Guard Clauses
```php
return $user->isVip() ? 0.2 : 0.05;
```
- **Kịch bản 1 (Truthy branch)**: Giả lập `$user->isVip()` trả về `true` -> Assert kết quả `0.2`.
- **Kịch bản 2 (Falsy branch)**: Giả lập `$user->isVip()` trả về `false` -> Assert kết quả `0.05`.
### C. Khối `try - catch - finally`
```php
try {
return $this->gateway->charge($amount);
} catch (PaymentException $e) {
Log::error('Payment failed: ' . $e->getMessage());
return false;
}
```
- **Happy Path**: Mock `$this->gateway->charge()` trả về mã giao dịch thành công.
- **Exception Path (BẮT BUỘC)**: Giả lập Mock `$this->gateway->shouldReceive('charge')->andThrow(new PaymentException('Insufficient funds'))`.
- Assert kết quả trả về `false`.
- Assert log được ghi hoặc error event được kích hoạt.
### D. Điều Kiện Logic Phức Hợp (`&&`, `||`)
```php
if ($user->isActive() && $order->isPaid()) {
$this->shipOrder($order);
}
```
Cần tối thiểu 3 test cases để kiểm thử trọn vẹn:
1. `isActive = true` VÀ `isPaid = true` -> Thực hiện ship hàng thành công.
2. `isActive = false` -> Short-circuit, không ship hàng.
3. `isActive = true` NHƯNG `isPaid = false` -> Không ship hàng.
### E. Cấu Trúc `switch` và `match`
Mọi nhánh case và đặc biệt là nhánh `default` (hoặc `UnhandledMatchError`) đều phải có bài test kích hoạt tương ứng.
---
## 4. Kỹ Thuật Viết Test Đầy Đủ Cho Từng Tầng Laravel
### 1. FormRequest & Custom Rules (`app/Http/Requests/`, `app/Rules/`):
- **`authorize()`**: Test 2 trường hợp: User có quyền (`true`) và User không có quyền (`false`).
- **`rules()`**: Bắt buộc test dữ liệu hợp lệ VÀ test vi phạm cho **từng rule riêng lẻ**:
```php
// Ví dụ test từng rule vi phạm để không sót nhánh validator:
#[Test]
public function validation_fails_when_email_is_missing(): void { ... }
#[Test]
public function validation_fails_when_email_format_is_invalid(): void { ... }
#[Test]
public function validation_fails_when_password_is_shorter_than_8_chars(): void { ... }
```
- **Custom Rules**: Test cả 2 trường hợp `passes() === true` và `passes() === false` (assert đúng thông điệp lỗi `message()`).
### 2. Services & Actions (`app/Services/`, `app/Actions/`):
- Test kịch bản thành công với các loại đầu vào khác nhau (dùng `#[DataProvider]`).
- Test kịch bản Mock các dịch vụ bên ngoài (Payment, SMS, Mail) bị lỗi ném Exception.
- Test nghiệp vụ Database Transaction: Khi có lỗi phát sinh, dữ liệu phải được rollback sạch sẽ (`assertDatabaseMissing`).
### 3. Background Jobs (`app/Jobs/`):
- **BẮT BUỘC test phương thức `handle()`**: Mock toàn bộ dependencies ngoài và verify logic thực thi cốt lõi.
- **BẮT BUỘC test phương thức `failed(Throwable $e)`**: Kiểm tra hành vi dọn dẹp tài nguyên hoặc gửi thông báo cảnh báo khi job hết lượt retry.
### 4. Repositories & Query Builders (`app/Repositories/`):
- Bắt buộc dùng **SQLite In-Memory (`:memory:`)** kết hợp trait `RefreshDatabase`.
- Tạo dữ liệu mẫu bằng Factory và kiểm tra:
- Query trả về đúng các bản ghi thỏa mãn điều kiện lọc.
- Loại bỏ các bản ghi không thỏa mãn điều kiện.
- Đúng thứ tự sắp xếp (`orderBy`).
- Trả về Collection rỗng khi không có bản ghi nào khớp.
- Tuyệt đối CẤM chỉ assert `assertInstanceOf(Builder::class)`!
### 5. Policies (`app/Policies/`):
- Mọi action (`view`, `create`, `update`, `delete`, `restore`) đều phải test cả 2 kịch bản:
- User có quyền (trả về `true`).
- User không có quyền (trả về `false`).
- Trường hợp tài khoản Admin được bypass / super-admin check.
---
## 5. Kỹ Thuật Viết Test Đầy Đủ Cho Từng Tầng CakePHP
### 1. ORM Tables (`src/Model/Table/`):
- **`validationDefault()`**: 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`, `numeric`, `url`).
- **`buildRules()`**:
- Test dữ liệu thỏa mãn rule -> Lưu thành công.
- Test dữ liệu trùng lặp (`isUnique`) hoặc khóa ngoại không tồn tại (`existsIn`) -> Lưu thất bại, trả về rule errors.
- **Custom Finders (`findPublished`, `findAuth`)**:
- Test trả về đúng tập Entity cần tìm.
- Test không trả về các Entity ở trạng thái khác (draft, archived).
### 2. Controllers (`src/Controller/`):
- Bắt buộc dùng **`Cake\TestSuite\IntegrationTestTrait`**.
- Bật token bảo vệ khi test POST/PUT:
```php
$this->enableCsrfToken();
$this->enableSecurityToken();
```
- Phủ đầy đủ các kịch bản HTTP:
- **GET (200)**: Render view thành công, assert view variables (`assertResponseOk()`).
- **POST/PUT Success (302)**: Lưu thành công, assert chuyển hướng (`assertRedirect()`), flash message (`assertFlashMessage()`), và dữ liệu thực tế được lưu vào database.
- **POST/PUT Validation Error (200)**: Form không hợp lệ, không redirect, hiển thị flash lỗi.
- **Not Found (404)**: ID không tồn tại -> bắt `RecordNotFoundException`.
- **Unauthorized (403 hoặc 302 Redirect Login)**: Truy cập khi chưa xác thực hoặc không đủ quyền.
### 3. Console Commands (`src/Command/`):
- Bắt buộc dùng **`Cake\Console\TestSuite\ConsoleIntegrationTestTrait`**.
- Test chạy thành công: `assertExitSuccess()`.
- Test chạy lỗi (thiếu arguments, option sai): `assertExitError()`.
- Test nội dung output trên màn hình: `assertOutputContains()`.
### 4. Mailers (`src/Mailer/`):
- Bắt buộc dùng **`Cake\TestSuite\EmailTrait`**.
- Assert người nhận (`assertMailSentTo`), tiêu đề (`assertMailContainsSubject`), và nội dung email mà không gửi mail thật ra ngoài.
---
## 6. Checklist Tự Kiểm Tra File Test Trước Khi Hoàn Tất
- [ ] **100% public methods** của class đã có bài test riêng.
- [ ] Đầy đủ **Happy Path**, **Edge Cases** và **Exception Cases**.
- [ ] Mọi toán tử `??`, `?:`, `if/else`, `try/catch` đều có test case cho tất cả các nhánh.
- [ ] Không có bất kỳ dòng test nào dùng assertion rỗng hoặc `// TODO`.
- [ ] Dữ liệu thay đổi trong DB hoặc các side effect (Event, Job, Mail) được assert chặt chẽ.
- [ ] Test chạy PASS 100% khi thực thi `./test-runner.sh`.