feat: add comprehensive unit testing support for CakePHP

- Add 4 CakePHP Claude Code skills: cakephp-unit-test, cakephp-test-setup, cakephp-test-review, cakephp-mock-helper
- Add 11 CakePHP PHPUnit component templates (Table, Entity, Controller, Service, Command, Component, Middleware, Mailer, Form, Behavior, Helper)
- Add CakePHP configs (phpunit.xml.dist, app_local.test.php for SQLite in-memory)
- Add comprehensive CakePHP documentation and component guides
- Update test-runner.sh and installer to auto-detect Laravel and CakePHP
- Add CakePHP CI/CD templates for GitHub Actions and GitLab CI
- Update system prompt, verification checklist, and README with dedicated guides
This commit is contained in:
2026-09-14 10:19:40 +07:00
parent 0dce2ef812
commit 7425b263d5
28 changed files with 2210 additions and 356 deletions
@@ -0,0 +1,54 @@
---
name: cakephp-mock-helper
description: Phân tích một Class, Interface hoặc Table CakePHP và tự động sinh khối mã thiết lập Mock (PHPUnit Mock Double / Mockery) chuẩn cú pháp.
---
# Skill: CakePHP Mock Helper
Skill này hỗ trợ lập trình viên sinh nhanh mã giả lập (Mocking code) cho một CakePHP Component, Table, Service hoặc External Client.
## Cách kích hoạt
```text
/cakephp-mock-helper <tên_class_hoặc_đường_dẫn>
```
*Ví dụ*: `/cakephp-mock-helper App\Service\PaymentGatewayService`
*Ví dụ*: `/cakephp-mock-helper App\Controller\Component\StripeComponent`
---
## Quy trình xử lý của Agent:
1. Đọc nội dung file class/interface được cung cấp.
2. Liệt kê toàn bộ các `public methods`, tham số và kiểu trả về.
3. Sinh ra 2 phiên bản mã Mock:
- **Phiên bản PHPUnit Native Mock** (`$this->createMock(...)` hoặc `$this->getMockBuilder(...)`).
- **Phiên bản Mockery** (`Mockery::mock(...)`).
4. Kèm theo cả Happy Path (trả về kết quả hợp lệ) và Failure/Exception case.
## Mẫu kết quả trả về:
```php
// ==========================================
// 1. DÙNG PHPUNIT NATIVE MOCK (Khuyên dùng cho CakePHP)
// ==========================================
$serviceMock = $this->createMock(\App\Service\PaymentGatewayService::class);
// Kịch bản Happy Path:
$serviceMock->expects($this->once())
->method('charge')
->with(150.00, 'USD')
->willReturn(['status' => 'success', 'transaction_id' => 'TXN_999']);
// Kịch bản Exception:
$serviceMock->expects($this->once())
->method('charge')
->willThrowException(new \RuntimeException('Payment gateway timeout'));
// ==========================================
// 2. DÙNG MOCKERY (Nếu dự án có cài Mockery)
// ==========================================
$serviceMock = Mockery::mock(\App\Service\PaymentGatewayService::class);
$serviceMock->shouldReceive('charge')
->once()
->with(150.00, 'USD')
->andReturn(['status' => 'success', 'transaction_id' => 'TXN_999']);
```
@@ -0,0 +1,65 @@
---
name: cakephp-test-review
description: Đánh giá, chấm điểm chất lượng và phát hiện các lỗi anti-patterns trong file Unit / Integration Test CakePHP có sẵn theo thang điểm 100.
---
# Skill: CakePHP Test Reviewer
Skill này chịu trách nhiệm kiểm tra chất lượng và phát hiện lỗi anti-patterns trong một file test CakePHP đã viết sẵn.
## Cách kích hoạt
```text
/cakephp-test-review <đường_dẫn_file_test>
```
*Ví dụ*: `/cakephp-test-review tests/TestCase/Model/Table/ArticlesTableTest.php`
*Ví dụ*: `/cakephp-test-review tests/TestCase/Controller/ArticlesControllerTest.php`
---
## Tiêu Chí Đánh Giá (Scoring Criteria - Thang Điểm 100)
Agent sẽ quét file test qua 5 tiêu chí chuẩn:
### 1. Tính Cô Lập & An Toàn Dữ Liệu (Isolation & Database Safety - 25đ)
- Test có dùng đúng test datasource (`Datasources.test`) hoặc fixtures không, hay vô tình tác động lên database dev thật?
- Có bị gọi HTTP ra API ngoài thực tế mà quên mock không?
- Fixture có được khai báo tối giản chỉ những bảng cần thiết (tránh nạp thừa hàng chục bảng gây chậm)?
### 2. Sử Dụng Đúng Trait & Chuẩn CakePHP (CakePHP Conventions - 20đ)
- Controller test có dùng `Cake\TestSuite\IntegrationTestTrait` không (hay tạo `new Controller` thủ công - Anti-pattern)?
- Command test có dùng `Cake\Console\TestSuite\ConsoleIntegrationTestTrait` không?
- Mailer test có dùng `Cake\TestSuite\EmailTrait` không?
- Có bật `enableCsrfToken()` và `enableSecurityToken()` khi test POST/PUT không?
### 3. Chiến Lược Giả Lập & Fixtures (Mocking vs Fixtures - 20đ)
- Có bị over-mocking (Mock cả Entity, Table query cơ bản thay vì dùng Fixture) không?
- Các service bên thứ 3 (Payment, Notification, Cloud Storage) có được mock rõ ràng với số lần gọi (`->once()`, `->never()`) không?
### 4. Cấu Trúc & Assertions (Structure & Assertions - 20đ)
- Có tuân thủ mô hình **AAA** (`// Arrange`, `// Act`, `// Assert`) rõ ràng không?
- Dùng assertion đặc thù của CakePHP: `$this->assertResponseOk()`, `$this->assertResponseCode(200)`, `$this->assertRedirect()`, `$this->assertFlashMessage()` thay vì assert thủ công yếu ớt?
- Dùng strict assertions: `$this->assertSame()` thay vì `$this->assertEquals()`.
### 5. Độ Phủ Kịch Bản & Nhánh Biên (Scenario & Branch Coverage - 15đ)
- Đã kiểm tra cả Happy Path lẫn Unhappy Path (validation lỗi, entity không tìm thấy 404, không có quyền 403)?
- Các giá trị biên (`null`, mảng rỗng, chuỗi trắng) đã được kiểm thử đầy đủ chưa?
---
## Báo Cáo Đầu Ra Mẫu:
```markdown
### 🔍 BÁO CÁO REVIEW TEST CAKEPHP: `ArticlesControllerTest.php`
**Điểm tổng kết: 65/100 (Cần cải thiện)**
#### ⚠️ Các vấn đề phát hiện (Anti-Patterns):
1. **Thiếu CSRF/Security Token tại line 32**: Gọi POST `/articles/add` nhưng chưa kích hoạt `$this->enableCsrfToken()`, dễ dẫn đến test fail khi bật middleware CSRF.
2. **Assertion lỏng lẻo tại line 45**: Dùng `$this->assertTrue(true)` thay vì `$this->assertResponseSuccess()` hoặc kiểm tra dữ liệu thật được lưu vào DB.
3. **Chưa có kịch bản Validation Failure**: Chưa kiểm thử trường hợp người dùng submit form thiếu trường `title` bắt buộc.
#### 💡 Đề xuất Refactor:
```php
// Đoạn code được sửa lại chuẩn AAA và tận dụng đầy đủ IntegrationTestTrait...
```
```
+109
View File
@@ -0,0 +1,109 @@
---
name: cakephp-test-setup
description: Tự động quét kiểm tra môi trường, dependencies, database config (Datasources.test) và docker để chuẩn bị viết Unit Test cho bất kỳ dự án CakePHP nào.
---
# Skill: CakePHP Test Environment Setup & Health Check
Skill này chịu trách nhiệm chuẩn bị "Bước 0" cho dự án CakePHP trước khi bắt đầu viết bất kỳ Unit/Integration Test nào.
## Cách kích hoạt
- **Chế độ kiểm tra (Dry-run / Health-Check)**:
```text
/cakephp-test-setup
```
- **Chế độ tự động cài đặt gói thiếu (Auto-Install Mode)**:
```text
/cakephp-test-setup --install
```
## Quy trình thực hiện (Execution Workflow)
Khi người dùng kích hoạt lệnh `/cakephp-test-setup`, Agent sẽ tuần tự thực hiện các bước sau:
### Bước 1: Quét `composer.json` & Nhận Diện CakePHP Version
1. Đọc file `composer.json` tại thư mục gốc của dự án.
2. Xác định:
- **Phiên bản CakePHP**:
- `cakephp/cakephp: ^5.0`: Yêu cầu PHP >= 8.1
- `cakephp/cakephp: ^4.0`: Yêu cầu PHP >= 7.4
- `cakephp/cakephp: ^3.0`: Legacy CakePHP 3.x
- **Test Runner & Dependencies**:
- `phpunit/phpunit`
- `cakephp/cakephp-codesniffer` (nếu có)
- `mockery/mockery` (hỗ trợ nâng cao)
- `fakerphp/faker`
3. Nếu phát hiện thiếu thư viện hoặc cờ `--install`:
- Kích hoạt lệnh cài đặt qua môi trường tương ứng:
```bash
# Nếu dùng Docker Compose (service app / web / php):
docker compose exec -T app composer require --dev mockery/mockery fakerphp/faker --with-all-dependencies
# Nếu chạy Local:
composer require --dev mockery/mockery fakerphp/faker --with-all-dependencies
```
### Bước 2: Kiểm tra Cấu hình Testing Database (`Datasources.test`)
1. Kiểm tra file `config/app_local.php` hoặc `config/app.php`.
2. Xác thực cấu hình `Datasources.test`:
- Đảm bảo kết nối `test` được tách biệt với `default` (production/dev).
- Khuyên dùng SQLite In-Memory hoặc SQLite file test để đảm bảo **tốc độ cao và an toàn tuyệt đối 100% cho dữ liệu dev**:
```php
'test' => [
'className' => 'Cake\Database\Connection',
'driver' => 'Cake\Database\Driver\Sqlite',
'database' => ':memory:', // hoặc TMP . 'test.sqlite'
'encoding' => 'utf8',
'timezone' => 'UTC',
'cacheMetadata' => true,
'quoteIdentifiers' => false,
],
```
3. Kiểm tra file cấu hình PHPUnit (`phpunit.xml` hoặc `phpunit.xml.dist`):
- Đảm bảo có suite `<testsuite name="app">` trỏ vào `tests/TestCase`.
- Có extension / listener fixture (`Cake\TestSuite\Fixture\FixtureExtension` cho CakePHP 4.3+ / 5.x).
### Bước 3: Phát hiện Môi trường Docker / Runtime
1. Kiểm tra `bin/cake` -> Đảm bảo có quyền thực thi (`chmod +x bin/cake`).
2. Kiểm tra `docker-compose.yml` -> Dò tìm service chạy CakePHP (`app`, `web`, `php`, `backend`) -> Lệnh chuẩn:
```bash
docker compose exec -T app bin/cake test app
# Hoặc:
docker compose exec -T app vendor/bin/phpunit
```
3. Nếu chạy Local: Kiểm tra PHP CLI có đủ extension `intl` (`php -m | grep intl`) và `pdo_sqlite` (`php -m | grep sqlite`).
### Bước 4: Chạy Smoke Test (Kiểm tra thử nghiệm)
1. Chạy thử nghiệm một test case mẫu sẵn có hoặc test mặc định:
```bash
./test-runner.sh
# hoặc bin/cake test app
```
2. Nếu test PASS: Báo cáo môi trường đã sẵn sàng 100%.
3. Nếu test FAIL: Đưa ra chẩn đoán chính xác nguyên nhân (thiếu driver SQLite, sai database credentials, thiếu fixture strategy,...).
---
## Mẫu báo cáo đầu ra cho người dùng:
```markdown
### 📋 BÁO CÁO MÔI TRƯỜNG TEST CAKEPHP (HEALTH CHECK)
1. **Framework & Dependencies**:
- CakePHP: 4.5.3 (hoặc 5.0.x) | PHP: 8.2
- Test Runner: PHPUnit 10.5
- Mocking: Mockery 1.6 (Đã cài)
- Fixture Strategy: TransactionStrategy / FixtureExtension [OK]
2. **Cấu hình Môi trường Test**:
- Datasource Test: SQLite (:memory:) [OK - An toàn, không đụng dữ liệu dev]
- phpunit.xml: Đã cấu hình suite `app` trỏ tới `tests/TestCase`
3. **Môi trường thực thi**:
- Docker Service: `app` (hoặc `web`) đang hoạt động
- Lệnh chạy test chuẩn: `docker compose exec -T app bin/cake test app`
4. **Kết quả Smoke Test**:
- Trạng thái: ✅ PASSED
🎉 **Môi trường CakePHP đã hoàn toàn sẵn sàng cho việc sinh Unit Test!**
```
+138
View File
@@ -0,0 +1,138 @@
---
name: cakephp-unit-test
description: Phân tích một class mã nguồn PHP CakePHP (Table, Entity, Controller, Service, Command, Component, Middleware, Mailer, Form, Behavior) và tự động sinh file Unit / Integration Test hoàn chỉnh đạt chuẩn AAA, cô lập cao và bao phủ biên.
---
# Skill: CakePHP Unit & Integration Test Generator
Skill này là công cụ chính để tự động tạo ra file Test cho bất kỳ class nào trong dự án CakePHP (từ CakePHP 3.x, 4.x đến 5.x+).
## Cách kích hoạt
### 🏆 QUY TRÌNH CHUẨN PRO CHO DỰ ÁN LỚN (KHUYÊN DÙNG NHẤT):
Để tránh bị tràn context token và đảm bảo viết sâu 100% từng nhánh SonarQube, hãy tách biệt 2 pha: **PHA VIẾT (Generation)** và **PHA CHẠY (Execution)**:
1. **Bước 1: Quét bản đồ dự án (`--untested`)**:
```text
/cakephp-unit-test --untested
```
*Liệt kê danh sách class trong `src/` chưa có test trong `tests/TestCase/` theo độ ưu tiên: Tables/Entities -> Services -> Controllers -> Commands -> Components/Middlewares.*
2. **Bước 2: Viết test tập trung theo từng tầng (CHỈ VIẾT, CHƯA CHẠY TEST)**:
```text
/cakephp-unit-test src/Model/Table/
# Sau khi xong, viết tiếp:
/cakephp-unit-test src/Service/
/cakephp-unit-test src/Controller/
/cakephp-unit-test src/Command/
```
*AI dồn 100% token tập trung sinh test sâu, đầy đủ Happy Path, Edge Cases, Exception và Branch Coverage.*
3. **Bước 3: Chạy test đồng loạt ngoài Terminal & Nghiệm thu**:
```bash
./test-runner.sh --coverage
```
*Chạy 1 lần duy nhất bằng `bin/cake test app` hoặc `phpunit` trên In-memory SQLite siêu tốc, đo độ phủ và xuất file báo cáo.*
---
### ⚡ CÁC CHẾ ĐỘ TIỆN ÍCH KHÁC:
4. **Chế độ Autopilot Khép Kín: Viết & Chạy Test Ngay (`--all --run` hoặc `--autopilot`)**:
```text
/cakephp-unit-test --all --run
```
*Dành cho dự án vừa và nhỏ: AI tự lập manifest, viết đến đâu chạy `./test-runner.sh` ngay đến đó, tự động sửa lỗi (Auto-Healing) nếu test fail.*
5. **Chế độ Viết Cho 1 File & Test Luôn (`--run`)**:
```text
/cakephp-unit-test src/Model/Table/UsersTable.php --run
```
6. **Chế độ Theo Git Diff (Dành Cho Pull Request)**:
```text
/cakephp-unit-test --changed
```
7. **Chế độ File đơn lẻ**:
```text
/cakephp-unit-test src/Controller/ArticlesController.php
```
---
## Quy trình 6 Bước Tự Động của Agent
### Bước 1: Khảo sát Môi trường & Phiên bản CakePHP
1. Đọc `composer.json` để xác định:
- **Phiên bản CakePHP**:
- CakePHP 5.x: PHP >= 8.1, kiểu khai báo kiểu nghiêm ngặt (strict types), Attributes PHP 8 `#[Test]`.
- CakePHP 4.x: PHP 7.4 - 8.2, `Cake\TestSuite\TestCase`, dùng `fetchTable()`.
- CakePHP 3.x: `TableRegistry::get()`.
- **PHPUnit**: PHPUnit 9 / 10 / 11.
### Bước 2: Phân tích File Nguồn & Xác Định Loại Component
1. Đọc toàn bộ nội dung file class nguồn được chỉ định.
2. Xác định cấu trúc Component CakePHP:
- **Table (`src/Model/Table/*Table.php`)**: Test validation rules, custom finder methods (`findTagged()`, `findAuth()`), buildRules, save/delete logic.
- **Entity (`src/Model/Entity/*.php`)**: Test virtual fields, mutators (`_setPassword()`), accessors, hidden/accessible properties.
- **Controller (`src/Controller/*Controller.php`)**: Dùng `Cake\TestSuite\IntegrationTestTrait`. Test endpoints HTTP (GET, POST, PUT, DELETE), flash messages, redirect, view vars, auth/session.
- **Service / Action (`src/Service/*.php`)**: Test logic nghiệp vụ thuần túy, mock các dependency ngoài hoặc Table locator.
- **Command (`src/Command/*Command.php`)**: Dùng `Cake\Console\TestSuite\ConsoleIntegrationTestTrait`. Test arguments, options, output text, exit code (`CODE_SUCCESS`, `CODE_ERROR`).
- **Component (`src/Controller/Component/*.php`)**: Test logic tái sử dụng của Controller với mocked Controller/Request/Response.
- **Middleware (`src/Middleware/*.php`)**: Test PSR-15 / CakePHP middleware với `ServerRequest` và `ResponseHandler`.
- **Mailer (`src/Mailer/*Mailer.php`)**: Dùng `Cake\TestSuite\EmailTrait`. Test subject, to, template, view vars.
- **Form (`src/Form/*Form.php`)**: Test `validationDefault()`, `_buildSchema()`, `_execute()`.
- **Behavior (`src/Model/Behavior/*.php`)**: Test ORM hooks (`beforeSave`, `beforeFind`,...).
- **Helper (`src/View/Helper/*.php`)**: Test render HTML, tags, view outputs.
### Bước 3: Xác Định Fixtures & Chiến Lược Giả Lập (Mocking)
- **Database & Fixtures**:
- Đối với Table & Controller test: Xác định các bảng liên quan để khai báo fixture:
```php
protected array $fixtures = [
'app.Users',
'app.Articles',
];
```
- **External Dependencies & APIs**:
- Dùng Mockery hoặc PHPUnit Mock (`$this->createMock()`) cho các HTTP Client ngoài, 3rd party SDKs, Payment Gateway.
- **Session & Auth**:
- Đối với Controller: `$this->session(['Auth.User.id' => 1])` hoặc Token-based Auth.
- Kích hoạt CSRF & Security token khi test POST:
```php
$this->enableCsrfToken();
$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 5: Sinh Mã Nguồn Test Chuẩn Mirror
1. Xác định đường dẫn file test theo chuẩn CakePHP:
- Nguồn: `src/Model/Table/ArticlesTable.php`
- Đích: `tests/TestCase/Model/Table/ArticlesTableTest.php`
- Nguồn: `src/Controller/ArticlesController.php`
- Đích: `tests/TestCase/Controller/ArticlesControllerTest.php`
- Nguồn: `src/Command/CleanCacheCommand.php`
- Đích: `tests/TestCase/Command/CleanCacheCommandTest.php`
2. Sử dụng đúng base class:
- `Cake\TestSuite\TestCase` (kèm các Trait tương ứng: `IntegrationTestTrait`, `ConsoleIntegrationTestTrait`, `EmailTrait`).
3. Viết code theo mô hình **AAA** (`// Arrange`, `// Act`, `// Assert`).
### Bước 6: Thẩm định & Lưu File
1. Tự kiểm tra file test qua checklist quy chuẩn.
2. Ghi file test vào thư mục `tests/TestCase/...`.
3. In ra lệnh chạy test gợi ý:
```bash
bin/cake test app tests/TestCase/Model/Table/ArticlesTableTest.php
# Hoặc:
./test-runner.sh --filter=ArticlesTableTest
```
+314 -213
View File
@@ -1,6 +1,6 @@
# 🚀 Laravel Unit Testing Toolkit for Claude Code # 🚀 PHP Unit Testing Toolkit for Claude Code
> **Bộ công cụ tự động hóa toàn diện cho việc thiết lập môi trường, sinh Unit Test chuẩn AAA và kiểm thử tự động cho bất kỳ dự án PHP & Laravel nào (từ Laravel 8 đến 13+, PHP 8.0 đến 8.4+).** > **Bộ công cụ tự động hóa toàn diện cho việc thiết lập môi trường, sinh Unit & Integration Test chuẩn AAA và kiểm thử tự động cho bất kỳ dự án PHP nào. Hỗ trợ chuyên sâu và tách biệt cho cả hai framework phổ biến: Laravel (8 đến 13+) và CakePHP (3.x đến 5.x+), tương thích PHP 7.4 đến 8.4+.**
Được phát triển dưới dạng mã nguồn mở (Open Source) dành cho mọi lập trình viên, đội ngũ kỹ thuật và cộng đồng phát triển phần mềm. Được phát triển dưới dạng mã nguồn mở (Open Source) dành cho mọi lập trình viên, đội ngũ kỹ thuật và cộng đồng phát triển phần mềm.
@@ -9,38 +9,35 @@
## 📑 MỤC LỤC ## 📑 MỤC LỤC
1. [Điểm Nổi Bật & Tính Năng Cốt Lõi](#1-điểm-nổi-bật--tính-năng-cốt-lõi) 1. [Điểm Nổi Bật & Tính Năng Cốt Lõi](#1-điểm-nổi-bật--tính-năng-cốt-lõi)
2. [Cấu Trúc Thư Mục Repository](#2-cấu-trúc-thư-mục-repository) 2. [Cấu Trúc Thư Mục Repository](#2-cấu-trúc-thư-mục-repository)
3. [Hướng Dẫn Cài Đặt Lần Đầu](#3-hướng-dẫn-cài-đặt-lần-đầu) 3. [Cài Đặt Nhanh (1 Dòng Lệnh Duy Nhất)](#3-cài-đặt-nhanh-1-dòng-lệnh-duy-nhất)
4. [Khởi Tạo Cho Một Dự Án Laravel Bất Kỳ](#4-khởi-tạo-cho-một-dự-án-laravel-bất-kỳ) 4. [🔴 PHẦN 1: HƯỚNG DẪN DÀNH RIÊNG CHO LARAVEL](#4-phần-1-hướng-dẫn-dành-riêng-cho-laravel)
5. [Quy Trình 5 Bước Sinh & Chạy Unit Test](#5-quy-trình-5-bước-sinh--chạy-unit-test) - [4.1. Kiến Trúc & 13 Tầng Hỗ Trợ](#41-kiến-trúc--13-tầng-hỗ-trợ-laravel)
- [Bước 1: Quét Môi Trường & Cài Packages (`/laravel-test-setup`)](#bước-1-quét-môi-trường--cài-packages-laravel-test-setup) - [4.2. Quy Trình Thực Chiến 5 Bước Cho Laravel](#42-quy-trình-thực-chiến-5-bước-cho-laravel)
- [Bước 2: Sinh Unit Test Tự Động (`/laravel-unit-test`)](#bước-2-sinh-unit-test-tự-động-laravel-unit-test) - [4.3. Bảng Tra Cứu Lệnh Laravel (Cheat Sheet)](#43-bảng-tra-cứu-lệnh-laravel-cheat-sheet)
- [Bước 3: Chạy Test Thông Minh (`test-runner.sh`)](#bước-3-chạy-test-thông-minh-test-runnersh) 5. [🔵 PHẦN 2: HƯỚNG DẪN DÀNH RIÊNG CHO CAKEPHP](#5-phần-2-hướng-dẫn-dành-riêng-cho-cakephp)
- [Bước 4: Thẩm Định & Chấm Điểm Test (`/laravel-test-review`)](#bước-4-thẩm-định--chấm-điểm-test-laravel-test-review) - [5.1. Kiến Trúc & 10 Tầng Hỗ Trợ](#51-kiến-trúc--10-tầng-hỗ-trợ-cakephp)
- [Bước 5: Sinh Mã Giả Lập Nhanh (`/laravel-mock-helper`)](#bước-5-sinh-mã-giả-lập-nhanh-laravel-mock-helper) - [5.2. Quy Trình Thực Chiến 5 Bước Cho CakePHP](#52-quy-trình-thực-chiến-5-bước-cho-cakephp)
6. [Cách Cập Nhật Bộ Công Cụ Khi Có Nâng Cấp](#6-cách-cập-nhật-bộ-công-cụ-khi-có-nâng-cấp) - [5.3. Bảng Tra Cứu Lệnh CakePHP (Cheat Sheet)](#53-bảng-tra-cứu-lệnh-cakephp-cheat-sheet)
7. [Giải Đáp Thắc Mắc & Xử Lý Lỗi (FAQ & Troubleshooting)](#7-giải-đáp-thắc-mắc--xử-lý-lỗi-faq--troubleshooting) 6. [Cập Nhật Bộ Công Cụ](#6-cập-nhật-bộ-công-cụ)
8. [Bảng Tra Cứu Toàn Bộ Lệnh (Full Cheat Sheet)](#8-bảng-tra-cứu-toàn-bộ-lệnh-full-cheat-sheet) 7. [Giải Đáp Thắc Mắc & Xử Lý Lỗi (FAQ)](#7-giải-đáp-thắc-mắc--xử-lý-lỗi-faq)
9. [Ma Trận Tương Thích & Bản Quyền](#9-ma-trận-tương-thích--bản-quyền) 8. [Ma Trận Tương Thích & Bản Quyền](#8-ma-trận-tương-thích--bản-quyền)
--- ---
## 1. Điểm Nổi Bật & Tính Năng Cốt Lõi ## 1. Điểm Nổi Bật & Tính Năng Cốt Lõi
1. **Chuẩn Hóa Môi Trường Tự Động (Bước 0)**: 1. **Chuẩn Hóa Môi Trường Tự Động (Bước 0)**:
- Tự động nhận diện runtime: **Laravel Sail**, **Docker Compose** (service `app`/`php`), hoặc **PHP CLI cục bộ**. - Tự động nhận diện Framework (**Laravel** hay **CakePHP**) và runtime: **Docker Compose**, **Laravel Sail**, hoặc **PHP CLI cục bộ**.
- Bảo vệ an toàn database: thiết lập SQLite In-Memory (`:memory:`), tuyệt đối không chạm hay làm mất dữ liệu dev/local. - Bảo vệ an toàn database: thiết lập SQLite In-Memory (`:memory:`), tuyệt đối không chạm hay làm bẩn/mất dữ liệu dev/local.
- Tự động cài đặt các package còn thiếu (`mockery`, `faker`, `paratest`) thẳng vào đúng container đang chạy. - Tự động cài đặt các package còn thiếu (`mockery`, `faker`, `paratest`) thẳng vào đúng container đang chạy.
2. **Triết Lý Kiểm Thử Đỉnh Cao (AAA & Isolation)**: 2. **Triết Lý Kiểm Thử Đỉnh Cao (AAA & Isolation)**:
- Đảm bảo tính cô lập tuyệt đối (Zero Leakage): mock toàn bộ external services, APIs, queues, mails. - Đảm bảo tính cô lập tuyệt đối (Zero Leakage): mock toàn bộ external services, 3rd party APIs, queues, mails.
- Không over-mocking: giữ nguyên thực thể DTO, Value Object, Collection thật. - Không over-mocking: giữ nguyên thực thể DTO, Value Object, Collection, Entity thật.
- Chặn đứng lỗi anti-pattern mock chuỗi Eloquent Query Builder dễ vỡ. 3. **Chế Độ Autopilot Khép Kín (`--all --run`)**:
3. **Bao Phủ Trọn Vẹn 13 Tầng Kiến Trúc Laravel**: - Tự động lập manifest quét toàn bộ dự án, viết test tuần tự không sót file, **viết đến đâu tự chạy test đến đó**, và **tự động sửa lỗi (Auto-Healing)** cho đến khi PASS 100%.
- Services, Actions, Controllers, FormRequests, Custom Rules, Repositories, Query Builders, Models, Jobs, Events, Listeners, Middlewares, Policies, Notifications, Mailables, Artisan Commands, View Components, Observers, Exceptions. 4. **Sẵn Sàng Cho Tương Lai & Architecture Testing**:
4. **Chế Độ Autopilot Khép Kín (`/laravel-unit-test --all --run`)**:
- Chỉ với 1 lệnh duy nhất: tự động lập manifest quét toàn bộ dự án, viết test tuần tự không sót file, **viết đến đâu tự chạy test đến đó**, và **tự động sửa lỗi (Auto-Healing)** cho đến khi PASS 100%.
5. **Sẵn Sàng Cho Tương Lai & Architecture Testing**:
- Hỗ trợ đầy đủ tính năng mới nhất của **PHP 8.4+** (Property Hooks, Asymmetric Visibility). - Hỗ trợ đầy đủ tính năng mới nhất của **PHP 8.4+** (Property Hooks, Asymmetric Visibility).
- Tích hợp bộ kiểm thử kiến trúc toàn dự án **Architecture Test** bằng Pest v3 / PHPUnit (`arch()`). - Tích hợp kiểm thử kiến trúc toàn dự án bằng Pest v3 / PHPUnit (`arch()`).
- Cung cấp sẵn file cấu hình CI/CD Quality Gate (GitHub Actions, GitLab CI) chặn merge PR nếu code coverage < 80%. - Cung cấp sẵn file cấu hình CI/CD Quality Gate (GitHub Actions, GitLab CI) chặn merge PR nếu code coverage < 80%.
--- ---
@@ -51,303 +48,407 @@
agent-unit-testing/ agent-unit-testing/
├── install.sh # Script cài đặt chính (hỗ trợ Toàn cục & Theo dự án) ├── install.sh # Script cài đặt chính (hỗ trợ Toàn cục & Theo dự án)
├── .claude/ ├── .claude/
│ └── skills/ # Bộ 4 Skills tích hợp cho Claude Code │ └── skills/ # Bộ Skills tích hợp cho Claude Code
│ ├── laravel-test-setup/ # Kiểm tra môi trường & tự cài đặt dependencies │ │── # Skills Dành Cho CakePHP:
│ ├── laravel-unit-test/ # Sinh Unit Test tự động (hỗ trợ Autopilot --all --run) │ ├── cakephp-unit-test/ # Sinh Test tự động cho CakePHP (Table, Controller, Service...)
│ ├── laravel-test-review/ # Chấm điểm chất lượng test theo thang 100 & bắt lỗi anti-patterns │ ├── cakephp-test-setup/ # Quét môi trường & cấu hình Datasources.test cho CakePHP
│ ├── cakephp-test-review/ # Chấm điểm chất lượng test CakePHP theo thang 100
│ ├── cakephp-mock-helper/ # Sinh nhanh mã Mock PHPUnit/Mockery cho CakePHP
│ │── # Skills Dành Cho Laravel:
│ ├── laravel-test-setup/ # Kiểm tra môi trường & tự cài đặt dependencies cho Laravel
│ ├── laravel-unit-test/ # Sinh Unit Test tự động cho Laravel
│ ├── laravel-test-review/ # Chấm điểm chất lượng test Laravel
│ └── laravel-mock-helper/ # Sinh mã Mockery & Laravel Fakes nhanh │ └── laravel-mock-helper/ # Sinh mã Mockery & Laravel Fakes nhanh
│ │
├── docs/ # 11 Cẩm Nang Kiến Thức & Quy Chuẩn Cốt Lõi ├── docs/ # Cẩm Nang Kiến Thức & Quy Chuẩn Cốt Lõi
│ ├── 00-environment-and-setup.md # Hướng dẫn setup dependencies, SQLite in-memory & Docker │ ├── cakephp/ # Cẩm nang chuyên sâu CakePHP (Testing Guide & 10 Components)
│ ├── 01-overview-and-principles.md # Triết lý Unit Test: AAA, cách ly, ranh giới Feature test │ ├── components/ # Cẩm nang 13 tầng component Laravel
│ ├── 02-test-frameworks.md # So sánh & cú pháp PHPUnit 10/11 vs Pest PHP v2/v3 │ ├── 00-environment-and-setup.md # Setup dependencies, SQLite in-memory & Docker
│ ├── 03-mocking-strategies.md # Toàn tập Mockery vs Laravel Fakes vs Static Facades │ ├── 01-overview-and-principles.md # Triết lý Unit Test: AAA, cách ly
│ ├── 04-naming-and-structure.md # Quy chuẩn đặt tên file, namespace, test method │ ├── 02-test-frameworks.md # So sánh PHPUnit vs Pest PHP
│ ├── 05-anti-patterns.md # 12 lỗi cấm kỵ khi viết Unit Test Laravel │ ├── 03-mocking-strategies.md # Toàn tập Mockery vs Fakes
│ ├── 06-future-php84-laravel-latest.md # Hướng dẫn chuyên sâu PHP 8.4+ & Laravel 12/13 │ ├── 05-anti-patterns.md # 12 lỗi cấm kỵ khi viết Unit Test
│ ├── 07-troubleshooting-and-debugging.md # Cẩm nang sửa 10 lỗi thường gặp nhất khi chạy test │ └── 10-sonarqube-100-percent-coverage.md # Chiến lược đạt 100% Coverage trên SonarQube
│ ├── 08-test-data-factories-and-builders.md# Quản lý fixture: Model::make(), States & DTO Builders
│ ├── 09-advanced-edge-cases.md # DB::transaction, Traits, File uploads, Serialization
│ ├── 10-sonarqube-100-percent-coverage-guide.md # Chiến lược đạt 100% Coverage trên SonarQube
│ │
│ └── components/ # 13 Cẩm Nang Chi Tiết Cho Từng Component Laravel
│ ├── 01-services-and-actions.md # Service Layer, Action classes, DTOs
│ ├── 02-controllers.md # API & Web Controllers
│ ├── 03-form-requests-and-rules.md # FormRequests & Custom Validation Rules
│ ├── 04-repositories-and-queries.md # Repositories & Custom Query Builders
│ ├── 05-models.md # Accessors, Mutators, Custom Casts
│ ├── 06-jobs-and-queues.md # Queueable Jobs, retry logic, failed()
│ ├── 07-events-and-listeners.md # Event payloads & Listener handlers
│ ├── 08-middlewares.md # Request interception, Auth, Role, Tenant
│ ├── 09-policies-and-gates.md # Policies authorization logic (can/cannot)
│ ├── 10-notifications-and-mail.md # Mailables & Notification channels
│ ├── 11-artisan-commands.md # Console arguments, options, exit codes
│ ├── 12-view-components-and-blade.md # Blade View Components & directives
│ └── 13-observers-and-exceptions.md # Model Observers & Custom Exceptions
│ │
├── templates/ # Templates Mẫu Chuẩn ├── templates/ # Templates Mẫu Chuẩn
│ ├── configs/ # phpunit.xml.dist, .env.testing, Pest.php │ ├── cakephp/ # Templates PHPUnit & Config cho CakePHP
│ ├── docker/ # Smart test-runner.sh & docker-compose.test.yml │ │ ├── phpunit/ # 10 Component templates (Table, Entity, Controller, Command...)
│ ├── ci/ # GitHub Actions & GitLab CI quality gates │ │ └── configs/ # phpunit.xml.dist & app_local.test.php (:memory:)
│ ├── phpunit/ # 8 Component templates + ArchitectureTest │ ├── phpunit/ # Templates PHPUnit cho Laravel (8 component templates)
│ └── pest/ # 8 Component templates + ArchitectureTest │ ├── pest/ # Templates Pest PHP cho Laravel
│ └── docker/ # Smart test-runner.sh (tự nhận diện Laravel & CakePHP)
│ │
├── rules/ # Bộ Quy Tắc & Tiêu Chí Đánh Giá ├── rules/ # Bộ Quy Tắc & Tiêu Chí Đánh Giá
│ ├── system-prompt-unit-test.md # System prompt cốt lõi cho AI
│ ├── edge-cases-matrix.md # Ma trận kịch bản biên (Null, Boundary, Errors)
│ └── verification-checklist.md # Checklist 10 bước tự kiểm tra trước khi xuất code
│
├── LICENSE # Giấy phép mã nguồn mở MIT ├── LICENSE # Giấy phép mã nguồn mở MIT
└── README.md # Tài liệu hướng dẫn chính └── README.md # Tài liệu hướng dẫn chính
``` ```
--- ---
## 3. Cài Đặt Trực Tiếp Vào Source Code (Không Cần Clone Repo) ## 3. Cài Đặt Nhanh (1 Dòng Lệnh Duy Nhất)
Bất kỳ ai muốn sử dụng bộ công cụ này (dù trong hay ngoài team, trên bất kỳ máy tính nào) **KHÔNG CẦN phải clone repository công cụ về máy**. Bất kỳ ai muốn sử dụng bộ công cụ này (trên máy cá nhân, server hay container) **KHÔNG CẦN phải clone repository về máy**.
Chỉ cần mở Terminal, `cd` vào thư mục dự án Laravel của bạn và chạy **đúng 1 dòng lệnh duy nhất**: ### 🌟 Cách 1: Cài trực tiếp vào Source Code của bạn (Khuyên dùng)
Mở Terminal, `cd` vào thư mục dự án của bạn (dù là **Laravel** hay **CakePHP**) và chạy:
```bash ```bash
cd /duong-dan/toi/source-code-laravel-cua-ban cd /duong-dan/toi/source-code-cua-ban
# 🌟 Cài đặt trực tiếp qua HTTPS công khai (không cần tài khoản, không cần SSH key): # Cài đặt trực tiếp qua HTTPS công khai (không cần SSH key, không cần tài khoản):
curl -fsSL https://git.smartgift.io.vn/Smartgift-AI/agent-unit-testing/raw/branch/main/install.sh | bash curl -fsSL https://git.smartgift.io.vn/Smartgift-AI/agent-unit-testing/raw/branch/main/install.sh | bash
``` ```
*(Nếu thư mục đang trống hoặc chưa có file `composer.json`, thêm cờ `--force` vào cuối: `... | bash -s -- --force`)*. *(Nếu thư mục đang trống hoặc chưa có `composer.json`, thêm cờ `--force` vào cuối: `... | bash -s -- --force`)*.
🎯 **Cơ chế tự động:**
- Script tự động phát hiện dự án là **Laravel** hay **CakePHP**.
- Tự động cấy bộ Skills tương ứng vào `.claude/skills/`.
- Tự động đồng bộ tài liệu vào `.claude/testing-docs/`.
- Tự động tạo Smart Runner `./test-runner.sh` (tự nhận diện runner và Docker).
- Tự động thiết lập kết nối SQLite In-Memory an toàn (không làm mất dữ liệu dev).
--- ---
### 🎯 Quy Trình Tự Động Diễn Ra Ngầm: ### ⚡ Cách 2: Cài Đặt Toàn Cục Cho Máy Tính (Global Mode)
1. Script sẽ tải các tài nguyên cần thiết từ xa qua HTTPS vào thư mục tạm `/tmp/`. Nếu muốn cài 1 lần duy nhất để dùng cho mọi dự án trên máy:
2. Tự động cấy trực tiếp vào source code của bạn:
- Thư mục `.claude/skills/` (với trọn bộ 4 Skills viết test tự động).
- Thư mục `.claude/testing-docs/` (cẩm nang quy chuẩn 13 tầng Laravel).
- Script chạy test thông minh `./test-runner.sh` (tự bắt Docker/Sail/Local).
- Script cài package test `./setup-test-deps.sh`.
- Tự động cấu hình SQLite In-Memory `:memory:` an toàn vào `phpunit.xml`.
- Tự động tạo GitHub Actions CI `.github/workflows/unit-tests.yml`.
3. Tự động xóa sạch thư mục tạm, **hoàn toàn không lưu lại rác hay repository công cụ trên máy của bạn**!
---
## 4. Tùy Chọn: Cài Đặt Toàn Cục Cho Toàn Máy Tính (Global Mode)
Nếu bạn muốn cài 1 lần duy nhất cho toàn bộ máy tính (để mọi source code mở Claude Code lên đều tự có sẵn các lệnh):
```bash ```bash
curl -fsSL https://git.smartgift.io.vn/Smartgift-AI/agent-unit-testing/raw/branch/main/install.sh | bash -s -- --global curl -fsSL https://git.smartgift.io.vn/Smartgift-AI/agent-unit-testing/raw/branch/main/install.sh | bash -s -- --global
``` ```
🎯 **Lợi ích:** 🎯 **Lợi ích:**
- Tự động cài đặt 4 Skills vào `~/.claude/skills/`. - Đăng ký sẵn các lệnh tiện ích trong Terminal: `laravel-test-init`, `cakephp-test-init`, `sg-test-init`.
- Đăng ký lệnh `laravel-test-init` (hoặc `sg-test-init`) vào Terminal để gõ ở bất kỳ thư mục nào cũng tự tạo `test-runner.sh`. - Mọi thư mục khi mở Claude Code đều nhận diện sẵn trọn bộ skills của cả 2 framework.
🎯 **Lợi ích:**
- Tự động cài đặt 4 Skills vào `~/.claude/skills/`.
- Đăng ký lệnh `sg-test-init` (hoặc `laravel-test-init`) vào Terminal để gõ ở bất kỳ thư mục nào cũng tự tạo `test-runner.sh`.
--- ---
## 5. Quy Trình Trọn Vẹn: Từ Bắt Đầu Đến Hoàn Thành Viết Unit Test ## 4. 🔴 PHẦN 1: HƯỚNG DẪN DÀNH RIÊNG CHO LARAVEL
Dưới đây là **kịch bản thực chiến trọn vẹn từ A đến Z** khi bạn áp dụng vào một source code Laravel thực tế: ### 4.1. Kiến Trúc & 13 Tầng Hỗ Trợ Laravel
Bộ công cụ sinh test chuẩn xác cho 13 tầng kiến trúc Laravel:
1. **Services & Actions** (`app/Services/`, `app/Actions/`)
2. **Controllers** (`app/Http/Controllers/`)
3. **Form Requests & Rules** (`app/Http/Requests/`, `app/Rules/`)
4. **Repositories & Query Builders** (`app/Repositories/`)
5. **Models** (`app/Models/` - Accessors, Mutators, Custom Casts)
6. **Jobs & Queues** (`app/Jobs/` - kiểm thử bắt buộc `handle()` và `failed()`)
7. **Events & Listeners** (`app/Events/`, `app/Listeners/`)
8. **Middlewares** (`app/Http/Middleware/`)
9. **Policies & Gates** (`app/Policies/`)
10. **Notifications & Mail** (`app/Notifications/`, `app/Mail/`)
11. **Artisan Commands** (`app/Console/Commands/`)
12. **View Components** (`app/View/Components/`)
13. **Observers & Exceptions** (`app/Observers/`, `app/Exceptions/`)
---
### 4.2. Quy Trình Thực Chiến 5 Bước Cho Laravel
```text ```text
┌────────────────┐ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ 1. CÀI ĐẶT │ ──> │ 2. SETUP ENV │ ──> │ 3. AUTOPILOT │ ──> │ 4. NGHIỆM THU │ │ 1. CÀI ĐẶT │ ──> │ 2. SETUP ENV │ ──> │ 3. SINH TEST │ ──> │ 4. CHẠY & REVIEW│
│ curl | bash │ │ test-setup │ │ unit-test --all│ │ Coverage > 80% │ │ curl | bash │ │ laravel-setup │ │ laravel-test │ │ ./test-runner │
└────────────────┘ └────────────────┘ └────────────────┘ └────────────────┘ └────────────────┘ └────────────────┘ └────────────────┘ └────────────────┘
``` ```
--- #### 🔹 Bước 1: Khởi động Claude Code & Setup môi trường
Đứng tại thư mục Laravel và gõ:
### 🔹 Giai đoạn 1: Cài đặt công cụ vào Source Code (Chỉ mất 5 giây)
Mở terminal, `cd` vào thư mục source code Laravel của bạn và chạy 1 dòng lệnh:
```bash
cd /duong-dan/toi/source-code-laravel
curl -fsSL https://raw.githubusercontent.com/<OWNER>/agent-unit-testing/main/install.sh | bash
```
✅ **Kết quả**: Source code đã được tích hợp bộ Skills, cẩm nang rules, script `./test-runner.sh` và cấu hình SQLite `:memory:` an toàn.
---
### 🔹 Giai đoạn 2: Khởi động Claude Code & Chuẩn hóa môi trường
Mở Claude Code ngay tại thư mục source code:
```bash ```bash
claude claude
``` ```
Chạy lệnh kiểm tra và tự động cài đặt thư viện kiểm thử còn thiếu: Chạy lệnh kiểm tra môi trường:
```text ```text
/laravel-test-setup --install /laravel-test-setup --install
``` ```
✅ **Kết quả**: AI tự động kiểm tra `composer.json`, nếu thiếu `mockery`, `faker`, `paratest` -> AI tự động kích hoạt lệnh cài đặt vào container Docker hoặc môi trường Local của bạn và xác nhận môi trường chuyển sang trạng thái "Xanh" (100% Sẵn sàng). AI sẽ tự động kiểm tra `composer.json`, thiết lập SQLite `:memory:` trong `phpunit.xml`, kiểm tra Sail / Docker và tự động cài `mockery`, `faker`, `paratest` nếu thiếu.
--- ---
### 🔹 Giai đoạn 3: Viết Unit Test (Tách bạch pha Viết và pha Chạy) #### 🔹 Bước 2: Sinh Unit Test Tự Động
Để đạt **hiệu suất cao nhất và độ phủ 100% nhánh SonarQube**, bạn nên áp dụng quy trình chuẩn Pro: ##### 🏆 Cách Khuyên Dùng Cho Dự Án Lớn: Viết Tập Trung Theo Từng Tầng (Batch Mode)
Để tiết kiệm token và đạt độ phủ 100% từng nhánh SonarQube, hãy để AI viết sâu từng tầng:
#### 🏆 Cách Khuyên Dùng (Cho Dự Án Lớn): Viết Tập Trung Theo Từng Tầng (Batch Mode)
Tránh việc vừa viết vừa chạy test làm AI bị phân tâm và tràn context. Hãy để AI dồn 100% tài nguyên viết sâu từng tầng:
```text ```text
# 1. Viết toàn bộ tầng Services: # 1. Viết toàn bộ tầng Services:
/laravel-unit-test app/Services/ /laravel-unit-test app/Services/
# 2. Sau khi xong, viết tiếp tầng Actions & Requests: # 2. Viết tiếp tầng Actions:
/laravel-unit-test app/Actions/ /laravel-unit-test app/Actions/
# 3. Viết tiếp FormRequests & Custom Rules:
/laravel-unit-test app/Http/Requests/ /laravel-unit-test app/Http/Requests/
/laravel-unit-test app/Rules/
# 4. Viết tiếp Jobs, Listeners, Policies:
/laravel-unit-test app/Jobs/
/laravel-unit-test app/Policies/
``` ```
*AI sẽ tạo trọn bộ các file test với đầy đủ kịch bản Happy Path, Edge Cases, Exception và Branch Coverage mà không bị gián đoạn.*
--- ##### 🌟 Cách Dành Cho Dự Án Vừa & Nhỏ: Autopilot Khép Kín
#### 🌟 Cách Thay Thế: Autopilot Khép Kín (Cho Dự Án Vừa & Nhỏ)
Nếu dự án có ít file (< 15 files) và bạn muốn AI tự động viết và chạy test thử nghiệm luôn:
```text ```text
/laravel-unit-test --all --run /laravel-unit-test --all --run
``` ```
AI sẽ tự lập manifest, viết đến đâu gọi `./test-runner.sh` đến đó và tự sửa lỗi nếu test fail. AI tự động quét toàn bộ `app/`, viết test tuần tự, viết đến đâu tự gọi `./test-runner.sh` đến đó và tự sửa lỗi nếu test fail.
##### ⚡ Các chế độ sinh test khác:
- **Viết cho 1 file & test luôn**:
```text
/laravel-unit-test app/Services/PaymentService.php --run
```
- **Viết test cho các file vừa sửa đổi trên Git**:
```text
/laravel-unit-test --changed
```
- **Quét danh sách class thiếu test**:
```text
/laravel-unit-test --untested
```
--- ---
### 🔹 Giai đoạn 4: Chạy Test Đồng Loạt Ngoài Terminal & Đo Độ Phủ SonarQube #### 🔹 Bước 3: Chạy Test Ngoài Terminal & Đo Độ Phủ SonarQube
Sau khi tất cả các file test đã được AI viết xong, bạn quay ra Terminal và chạy 1 lệnh duy nhất: Quay ra terminal và chạy:
```bash
./test-runner.sh --coverage
```
*Tốc độ siêu nhanh vì chạy trên SQLite In-Memory. Nếu muốn xuất file báo cáo cho SonarQube:*
```bash ```bash
# Chạy toàn bộ test:
./test-runner.sh
# Chạy 1 test cụ thể:
./test-runner.sh --filter=PaymentServiceTest
# Đo độ phủ code và xuất file cho SonarQube:
./test-runner.sh --coverage-clover coverage.xml ./test-runner.sh --coverage-clover coverage.xml
``` ```
Bảng báo cáo nghiệm thu sẽ xuất hiện: ---
```markdown
🎉 HOÀN TẤT VIẾT VÀ TEST TOÀN BỘ DỰ ÁN:
✓ Tổng số class đã phủ test: 36/36 files (100%)
✓ Tổng số test cases được sinh: 142 test cases
✓ Kết quả thực thi: 142 PASSED (0 FAILED)
✓ Code Coverage: 95.8% (Đạt tiêu chuẩn Quality Gate SonarQube > 80%)
```
Nếu phát hiện 1 file test nào bị đỏ, bạn chỉ cần yêu cầu AI sửa đúng file đó: #### 🔹 Bước 4: Thẩm Định & Chấm Điểm Test (Reviewer)
Nếu muốn kiểm tra xem 1 file test có đạt chuẩn AAA và không bị anti-pattern:
```text ```text
/laravel-test-review tests/Unit/Services/PaymentServiceTest.php /laravel-test-review tests/Unit/Services/PaymentServiceTest.php
``` ```
--- ---
### 🔹 Giai đoạn 5: Các chế độ chạy linh hoạt khác #### 🔹 Bước 5: Sinh Nhanh Mã Mock Cho Interface / Service
- **Chỉ viết cho 1 class & test luôn**: ```text
/laravel-mock-helper App\Contracts\PaymentGatewayInterface
```
---
### 4.3. Bảng Tra Cứu Lệnh Laravel (Cheat Sheet)
| Nhiệm Vụ | Lệnh / Cú Pháp | Mô Tả |
| :--- | :--- | :--- |
| **Setup môi trường** | `/laravel-test-setup` | Kiểm tra cấu hình testing & database |
| **Cài packages thiếu** | `/laravel-test-setup --install` | Tự động cài `mockery`, `faker`, `paratest` |
| **Autopilot toàn dự án** | `/laravel-unit-test --all --run` | Quét toàn bộ `app/`, viết test & tự sửa lỗi |
| **Sinh test cả thư mục** | `/laravel-unit-test app/Services/` | Viết hàng loạt cho 1 folder |
| **Sinh test 1 file & chạy test** | `/laravel-unit-test app/.../File.php --run` | Viết cho 1 class và kiểm tra ngay |
| **Quét class thiếu test** | `/laravel-unit-test --untested` | Liệt kê các class chưa có file test |
| **Sinh test cho Git Diff** | `/laravel-unit-test --changed --run` | Viết test cho code vừa sửa trước khi tạo PR |
| **Chấm điểm file test** | `/laravel-test-review tests/.../Test.php` | Chấm điểm thang 100, bắt lỗi anti-patterns |
| **Sinh nhanh mã Mockery** | `/laravel-mock-helper MyInterface` | Tạo code mock sẵn dùng |
| **Chạy test terminal** | `./test-runner.sh` | Tự bắt Sail / Docker / Local PHP |
---
## 5. 🔵 PHẦN 2: HƯỚNG DẪN DÀNH RIÊNG CHO CAKEPHP
### 5.1. Kiến Trúc & 10 Tầng Hỗ Trợ CakePHP
Bộ công cụ hỗ trợ toàn diện các phiên bản **CakePHP 3.x, 4.x và 5.x+ (PHP 7.4 - 8.4+)**:
1. **Table Classes (ORM)** (`src/Model/Table/*Table.php`): Test validationDefault, buildRules, custom finders, save/delete với Fixtures.
2. **Entity Classes** (`src/Model/Entity/*.php`): Test password hashing, accessible fields, virtual fields (`_getFullName()`).
3. **Controller Classes** (`src/Controller/*Controller.php`): Dùng `Cake\TestSuite\IntegrationTestTrait`, test HTTP status (200/302/403/404/422), session, auth, CSRF & Security tokens.
4. **Domain Services** (`src/Service/*.php`): Test logic nghiệp vụ thuần túy với mocked dependencies.
5. **Console Commands** (`src/Command/*Command.php`): Dùng `Cake\Console\TestSuite\ConsoleIntegrationTestTrait`, test exit codes (`CODE_SUCCESS`), arguments, options, output.
6. **Controller Components** (`src/Controller/Component/*.php`): Test logic dùng chung của Controllers qua `ComponentRegistry`.
7. **Middlewares (PSR-15)** (`src/Middleware/*.php`): Test `ServerRequest` & `Response` flow.
8. **Mailers** (`src/Mailer/*Mailer.php`): Dùng `Cake\TestSuite\EmailTrait`, assert recipient, subject, template body.
9. **Forms** (`src/Form/*Form.php`): Test standalone forms (`validationDefault()`, `_execute()`).
10. **ORM Behaviors** (`src/Model/Behavior/*.php`): Test lifecycle hooks (`beforeSave`, `beforeFind`).
---
### 5.2. Quy Trình Thực Chiến 5 Bước Cho CakePHP
```text
┌────────────────┐ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ 1. CÀI ĐẶT │ ──> │ 2. SETUP ENV │ ──> │ 3. SINH TEST │ ──> │ 4. CHẠY & REVIEW│
│ curl | bash │ │ cakephp-setup │ │ cakephp-test │ │ ./test-runner │
└────────────────┘ └────────────────┘ └────────────────┘ └────────────────┘
```
#### 🔹 Bước 1: Khởi động Claude Code & Setup môi trường CakePHP
Đứng tại thư mục CakePHP và gõ:
```bash
claude
```
Chạy lệnh kiểm tra môi trường:
```text
/cakephp-test-setup --install
```
AI sẽ tự động:
- Kiểm tra phiên bản CakePHP (3.x / 4.x / 5.x) & PHP.
- Đảm bảo kết nối `Datasources.test` trong `config/app_local.php` hoặc `config/app.php` trỏ vào SQLite `:memory:` an toàn (tuyệt đối không đụng database dev thật).
- Kiểm tra testsuite `app` trong `phpunit.xml` và cài đặt packages cần thiết.
---
#### 🔹 Bước 2: Sinh Unit & Integration Test Cho CakePHP
##### 🏆 Cách Khuyên Dùng Cho Dự Án Lớn: Viết Tập Trung Theo Từng Tầng (Batch Mode)
```text
# 1. Viết tầng ORM Table (dùng Fixtures & fetchTable):
/cakephp-unit-test src/Model/Table/
# 2. Viết tầng Entities:
/cakephp-unit-test src/Model/Entity/
# 3. Viết tầng Domain Services:
/cakephp-unit-test src/Service/
# 4. Viết tầng Controllers (IntegrationTestTrait với CSRF/Auth):
/cakephp-unit-test src/Controller/
# 5. Viết tầng Console Commands (ConsoleIntegrationTestTrait):
/cakephp-unit-test src/Command/
# 6. Viết tiếp Components, Middlewares, Mailers:
/cakephp-unit-test src/Controller/Component/
/cakephp-unit-test src/Middleware/
/cakephp-unit-test src/Mailer/
```
##### 🌟 Cách Dành Cho Dự Án Vừa & Nhỏ: Autopilot Khép Kín
```text
/cakephp-unit-test --all --run
```
AI tự động quét toàn bộ `src/`, sinh test vào `tests/TestCase/` tương ứng, gọi `bin/cake test app` và tự fix lỗi cho đến khi toàn bộ test pass.
##### ⚡ Các chế độ sinh test CakePHP khác:
- **Viết cho 1 file & test luôn**:
```text ```text
/laravel-unit-test app/Services/PaymentService.php --run /cakephp-unit-test src/Model/Table/ArticlesTable.php --run
``` ```
- **Chỉ viết cho các file vừa sửa đổi trên Git trước khi tạo PR**: - **Viết test cho các file vừa sửa đổi trên Git**:
```text ```text
/laravel-unit-test --changed /cakephp-unit-test --changed
``` ```
- **Quét danh sách class chưa có test theo độ ưu tiên**: - **Quét danh sách class CakePHP thiếu test**:
```text ```text
/laravel-unit-test --untested /cakephp-unit-test --untested
```
- **Thẩm định & Chấm điểm 1 file test có sẵn (thang 100)**:
```text
/laravel-test-review tests/Unit/Services/PaymentServiceTest.php
```
- **Sinh nhanh khối mã Mockery cho một Interface**:
```text
/laravel-mock-helper App\Contracts\PaymentGatewayInterface
``` ```
--- ---
## 6. Cách Cập Nhật Bộ Công Cụ Khi Có Nâng Cấp #### 🔹 Bước 3: Chạy Test Ngoài Terminal & Đo Độ Phủ
Quay ra terminal và chạy:
```bash
# Chạy toàn bộ test CakePHP (tự nhận diện Docker hoặc Local):
./test-runner.sh
Khi repository công cụ trên Git có tài liệu mới hoặc nâng cấp rules: # Hoặc chạy trực tiếp qua CakePHP CLI:
bin/cake test app
# Chạy 1 test cụ thể:
./test-runner.sh --filter=ArticlesTableTest
# Đo độ phủ code SonarQube:
./test-runner.sh --coverage-clover coverage.xml
```
---
#### 🔹 Bước 4: Thẩm Định & Chấm Điểm Test CakePHP
Kiểm tra chất lượng và bắt các lỗi anti-patterns (quên CSRF token, tạo new Controller thủ công, truy vấn DB thật thay vì fixture):
```text
/cakephp-test-review tests/TestCase/Controller/ArticlesControllerTest.php
```
---
#### 🔹 Bước 5: Sinh Nhanh Mã Mock Cho CakePHP
Sinh nhanh mã PHPUnit Native Mock hoặc Mockery cho CakePHP Service hoặc Component:
```text
/cakephp-mock-helper App\Service\PaymentProcessingService
```
---
### 5.3. Bảng Tra Cứu Lệnh CakePHP (Cheat Sheet)
| Nhiệm Vụ | Lệnh / Cú Pháp | Mô Tả |
| :--- | :--- | :--- |
| **Setup môi trường** | `/cakephp-test-setup` | Kiểm tra Datasources.test & dependencies |
| **Cài packages thiếu** | `/cakephp-test-setup --install` | Cài đặt `phpunit`, `mockery`, `faker` |
| **Autopilot toàn dự án** | `/cakephp-unit-test --all --run` | Quét toàn bộ `src/`, sinh test & tự sửa lỗi |
| **Sinh test cả thư mục** | `/cakephp-unit-test src/Model/Table/` | Viết hàng loạt cho Table / Service / Controller |
| **Sinh test 1 file & chạy test** | `/cakephp-unit-test src/.../File.php --run` | Viết cho 1 class và chạy kiểm tra ngay |
| **Quét class thiếu test** | `/cakephp-unit-test --untested` | Liệt kê các class trong `src/` chưa có test |
| **Sinh test cho Git Diff** | `/cakephp-unit-test --changed --run` | Viết test cho code vừa thay đổi trên Git |
| **Chấm điểm file test** | `/cakephp-test-review tests/TestCase/...` | Chấm điểm thang 100, kiểm tra chuẩn CakePHP |
| **Sinh nhanh mã Mock** | `/cakephp-mock-helper MyService` | Tạo code mock PHPUnit / Mockery sẵn dùng |
| **Chạy test terminal** | `./test-runner.sh` | Tự bắt Docker (`bin/cake test app`) hoặc Local |
---
## 6. Cập Nhật Bộ Công Cụ
Khi repository công cụ trên Git có nâng cấp tài liệu hoặc bổ sung tính năng mới:
### 🌟 Cách 1: Cập nhật trực tiếp vào source code hiện tại ### 🌟 Cách 1: Cập nhật trực tiếp vào source code hiện tại
Đứng tại thư mục source code Laravel của bạn và chạy lại dòng lệnh cài đặt: Đứng tại thư mục source code của bạn và chạy lại dòng lệnh cài đặt:
```bash ```bash
curl -fsSL https://git.smartgift.io.vn/Smartgift-AI/agent-unit-testing/raw/branch/main/install.sh | bash curl -fsSL https://git.smartgift.io.vn/Smartgift-AI/agent-unit-testing/raw/branch/main/install.sh | bash
``` ```
*(Script sẽ tự động kéo bản mới nhất và ghi đè cập nhật vào `.claude/skills/` và runner của source code).*
### ⚡ Cách 2: Nếu đã cài đặt chế độ Toàn Cục (Global)
Gõ lệnh cập nhật ở bất kỳ đâu trong Terminal:
```bash
cakephp-test-update # Dành cho CakePHP
laravel-test-update # Dành cho Laravel
sg-test-update # Lệnh chung
```
--- ---
### ⚡ Cách 2: Nếu bạn đã cài đặt chế độ Toàn Cục (Global) ## 7. Giải Đáp Thắc Mắc & Xử Lý Lỗi (FAQ)
Bất kể bạn đang đứng ở thư mục nào trong Terminal, chỉ cần gõ:
```bash
laravel-test-update
```
*(hoặc alias: `sg-test-update`)*
Lệnh sẽ tự động kéo bản cập nhật mới nhất từ Git về máy bạn trong 3 giây.
--- ### Q1: Tại sao gõ `./install.sh` bị báo `zsh: no such file or directory`?
👉 **Trả lời**: Ký hiệu `./` tìm file trong thư mục hiện tại. Hãy dùng lệnh toàn cục: `laravel-test-init` hoặc `cakephp-test-init`.
## 7. Giải Đáp Thắc Mắc & Xử Lý Lỗi (FAQ & Troubleshooting) ### Q2: Cài vào thư mục mới/trống có được không?
👉 **Trả lời**: Được! Thêm cờ `--force`:
### Q1: Tại sao tôi gõ `./install.sh` thì bị báo `zsh: no such file or directory`?
👉 **Trả lời**: Ký hiệu `./` nghĩa là tìm file ngay trong thư mục hiện tại. Khi bạn đã `cd` sang một dự án khác, thư mục đó không có file này.
**Cách sửa**: Bỏ dấu `./` đi, chỉ cần gõ thẳng tên lệnh toàn cục đã được đăng ký:
```bash
laravel-test-init
# Hoặc:
laravel-unit-test-setup
```
### Q2: Tôi cài đặt vào một thư mục trống nhưng bị báo chưa có `artisan` hoặc `composer.json`?
👉 **Trả lời**: Mặc định script sẽ kiểm tra để tránh cài nhầm vào thư mục không phải Laravel. Nếu bạn muốn cài đặt trước vào thư mục trống, hãy thêm cờ `--force`:
```bash ```bash
install.sh --force install.sh --force
# Hoặc: # Hoặc:
cakephp-test-init --force
laravel-test-init --force laravel-test-init --force
``` ```
### Q3: Chạy test trên SQLite `:memory:` bị lỗi hàm MySQL (`JSON_CONTAINS`, `DATE_FORMAT`)? ### Q3: Chạy test có làm mất hoặc thay đổi dữ liệu database dev không?
👉 **Trả lời**: Mở tài liệu `docs/07-troubleshooting-and-debugging.md` (mục 5) để xem cách đăng ký hàm giả lập trong `setUp()`, hoặc chuyển kết nối test sang một container MySQL test riêng. 👉 **Trả lời**: **Tuyệt đối không!**
- Với Laravel: Cấu hình `DB_CONNECTION=sqlite` và `DB_DATABASE=:memory:`.
- Với CakePHP: Cấu hình `Datasources.test` dùng SQLite `:memory:` hoặc Fixture Transaction Strategy tự động rollback dữ liệu sau mỗi test.
### Q4: Làm sao chặn merge Pull Request nếu Code Coverage dưới 80%? ### Q4: Làm sao chặn merge Pull Request nếu Code Coverage dưới 80%?
👉 **Trả lời**: Bộ công cụ đã tạo sẵn file `.github/workflows/unit-tests.yml` (hoặc `.gitlab-ci.yml`). Pipeline CI sẽ tự động chạy song song trên PHP 8.2, 8.3, 8.4 và tự động từ chối merge nếu độ phủ < 80%. 👉 **Trả lời**: Bộ công cụ đã tạo sẵn file `.github/workflows/unit-tests.yml`. CI sẽ tự động chạy song song trên các phiên bản PHP và từ chối merge nếu độ phủ < 80%.
--- ---
## 8. Bảng Tra Cứu Toàn Bộ Lệnh (Full Cheat Sheet) ## 8. Ma Trận Tương Thích & Bản Quyền
| Nhiệm Vụ | Lệnh Terminal / Claude Code | Ngữ Cảnh Sử Dụng |
| :--- | :--- | :--- |
| **Cài đặt từ xa (Không cần clone)** | `curl -fsSL https://git.smartgift.io.vn/Smartgift-AI/agent-unit-testing/raw/branch/main/install.sh \| bash` | Đứng tại source code Laravel và chạy 1 dòng duy nhất |
| **Cài đặt toàn cục (Global Mode)** | `curl -fsSL https://git.smartgift.io.vn/Smartgift-AI/agent-unit-testing/raw/branch/main/install.sh \| bash -s -- --global` | Cài vào máy dùng cho mọi folder |
| **Cài đặt vào thư mục trống** | `curl -fsSL https://git.smartgift.io.vn/Smartgift-AI/agent-unit-testing/raw/branch/main/install.sh \| bash -s -- . --force` | Dùng khi thư mục chưa có `composer.json` |
| **Khởi tạo test runner nhanh** | `laravel-test-init` (hoặc `sg-test-init`) | Gõ tại terminal của bất kỳ dự án nào |
| **Cập nhật bộ công cụ (1-Click)** | `laravel-test-update` (hoặc `sg-test-update`) | Kéo bản mới nhất từ Git về máy mọi lúc |
| **Quét & Setup môi trường test** | `/laravel-test-setup` | Chạy trong Claude Code để kiểm tra cấu hình |
| **Tự động cài gói composer thiếu**| `/laravel-test-setup --install` | Cài `mockery`, `faker`, `paratest` vào container |
| **Autopilot: Viết xong test luôn**| `/laravel-unit-test --all --run` | 1 lệnh tự setup, viết test cả dự án, chạy test & tự fix |
| **Sinh test cho 1 file & test luôn**| `/laravel-unit-test app/.../Service.php --run`| Sinh test cho 1 class và chạy test kiểm tra ngay |
| **Sinh test cả thư mục** | `/laravel-unit-test app/Services/ --run` | Khi cần phủ test cho cả 1 folder / module |
| **Quét dự án lớn chưa có test** | `/laravel-unit-test --untested` | Tìm các class thiếu test để sinh hàng loạt |
| **Sinh test cho Git Diff** | `/laravel-unit-test --changed --run` | Trước khi tạo Pull Request trên Git |
| **Review chất lượng file test** | `/laravel-test-review tests/.../Test.php`| Chấm điểm thang 100 & bắt lỗi anti-patterns |
| **Sinh code Mockery nhanh** | `/laravel-mock-helper MyInterface` | Sinh code mock sẵn dùng |
| **Chạy test tự nhận diện** | `./test-runner.sh --filter=MyTest` | Tự bắt Docker / Sail / Local |
| **Tra cứu sửa lỗi** | Đọc `docs/07-troubleshooting-and-debugging.md` | Sửa 10 lỗi thường gặp nhất |
| **Tra cứu 7 ca khó** | Đọc `docs/09-advanced-edge-cases.md` | `DB::transaction()`, Trait, Upload file, Queue |
---
## 9. Ma Trận Tương Thích & Bản Quyền
| Thành Phần | Phiên Bản Hỗ Trợ | | Thành Phần | Phiên Bản Hỗ Trợ |
| :--- | :--- | | :--- | :--- |
| **PHP** | 8.0, 8.1, 8.2, 8.3, **8.4+** (Property Hooks, Asymmetric Visibility) | | **PHP** | 7.4, 8.0, 8.1, 8.2, 8.3, **8.4+** (Property Hooks, Asymmetric Visibility) |
| **Laravel** | 8.x, 9.x, 10.x, 11.x, **12.x & 13.x (Kiến trúc hiện đại)** | | **CakePHP** | **3.x, 4.x, 5.x+ (Toàn bộ kiến trúc Table, Entity, Controller, Command, Mailer...)** |
| **PHPUnit** | 9.x (DocBlock fallback), 10.x, **11.x & 12.x** (PHP 8 Attributes `#[Test]`) | | **Laravel** | **8.x, 9.x, 10.x, 11.x, 12.x & 13.x (Kiến trúc hiện đại)** |
| **PHPUnit** | 9.x (DocBlock), 10.x, **11.x & 12.x** (PHP 8 Attributes `#[Test]`) |
| **Pest PHP** | v2.x & **v3.x** (Architecture Testing `arch()`, Mutation testing) | | **Pest PHP** | v2.x & **v3.x** (Architecture Testing `arch()`, Mutation testing) |
| **Mocking** | Mockery, PHPUnit Test Doubles, Laravel Built-in Fakes, `Concurrency::fake()`, `Context::fake()` | | **Mocking** | CakePHP Fixtures, PHPUnit Test Doubles, Mockery, Laravel Built-in Fakes |
| **Database** | SQLite In-Memory (`:memory:`), MySQL Test DB | | **Database** | SQLite In-Memory (`:memory:`), MySQL Test DB, PostgreSQL Test DB |
| **Hệ Điều Hành** | macOS, Linux, WSL2 (Windows), Docker, CI/CD Environments | | **Hệ Điều Hành** | macOS, Linux, WSL2 (Windows), Docker, CI/CD Environments |
### Giấy Phép (License) ### Giấy Phép (License)
+161
View File
@@ -0,0 +1,161 @@
# 🍰 CakePHP Unit & Integration Testing Guide
> **Cẩm nang toàn diện về quy chuẩn, kiến trúc và phương pháp kiểm thử tự động cho dự án PHP CakePHP (3.x, 4.x, 5.x+, PHP 7.4 đến 8.4+).**
---
## 1. Triết Lý & Nguyên Tắc Cốt Lõi
1. **Mô hình AAA (Arrange - Act - Assert)**:
- Mọi phương thức test bắt buộc phải có phân tách rõ ràng 3 bước:
- `// Arrange`: Chuẩn bị payload, cấu hình session/auth, mock dependency.
- `// Act`: Thực thi phương thức (hoặc gọi HTTP endpoint, hoặc gọi console command).
- `// Assert`: So sánh kết quả, kiểm tra HTTP code, kiểm tra dữ liệu thay đổi trong DB.
2. **Bảo Vệ Database Tuyệt Đối (Zero Leakage)**:
- Mọi test tương tác cơ sở dữ liệu **PHẢI sử dụng kết nối `test`** (tốt nhất là SQLite in-memory `:memory:`).
- **Tuyệt đối không chạy test trên kết nối `default`** (tránh xóa nhầm hoặc làm bẩn dữ liệu dev/local).
- Sử dụng cơ chế Transaction Strategy hoặc Fixture Strategy của CakePHP để rollback dữ liệu tự động sau mỗi test.
3. **Phân Biệt Rõ Mocking vs Fixtures**:
- **Dùng Fixtures**: Khi kiểm thử Table, Custom Finder, ORM query phức tạp.
- **Dùng Mocks**: Khi gọi Web service bên ngoài (Stripe, Twilio, AWS S3, External API) hoặc các Service Interface trừu tượng.
- **Không Mock**: Entity (dùng Entity thật), Array, DTOs, Value Objects.
---
## 2. Các Trait Kiểm Thử Quan Trọng của CakePHP
CakePHP cung cấp sẵn các Trait vô cùng mạnh mẽ trong `Cake\TestSuite`:
### 1. `Cake\TestSuite\IntegrationTestTrait` (Dành cho Controllers / HTTP Endpoints)
Cho phép gửi request HTTP ảo vào ứng dụng CakePHP mà không cần mở web server thật:
```php
use Cake\TestSuite\IntegrationTestTrait;
use Cake\TestSuite\TestCase;
class ArticlesControllerTest extends TestCase
{
use IntegrationTestTrait;
public function testIndex(): void
{
$this->get('/articles');
$this->assertResponseOk();
$this->assertResponseContains('Latest Articles');
}
public function testAddPostWithAuthAndCsrf(): void
{
// Giả lập đăng nhập
$this->session(['Auth.User.id' => 1, 'Auth.User.role' => 'admin']);
// Bật CSRF & Security token
$this->enableCsrfToken();
$this->enableSecurityToken();
$this->post('/articles/add', [
'title' => 'CakePHP Testing Pro',
'body' => 'High quality tests.',
]);
$this->assertResponseSuccess();
$this->assertRedirect(['action' => 'index']);
}
}
```
### 2. `Cake\Console\TestSuite\ConsoleIntegrationTestTrait` (Dành cho Commands / Shells)
Giúp test CLI Command một cách trực quan:
```php
use Cake\Console\TestSuite\ConsoleIntegrationTestTrait;
use Cake\TestSuite\TestCase;
class SendReportCommandTest extends TestCase
{
use ConsoleIntegrationTestTrait;
public function setUp(): void
{
parent::setUp();
$this->useCommandRunner();
}
public function testExecuteSuccess(): void
{
$this->exec('send_report --daily');
$this->assertExitSuccess();
$this->assertOutputContains('Reports sent successfully');
}
}
```
### 3. `Cake\TestSuite\EmailTrait` (Dành cho Mailers)
Kiểm tra email được gửi đi mà không thực sự gửi qua SMTP:
```php
use Cake\TestSuite\EmailTrait;
use Cake\TestSuite\TestCase;
class UserMailerTest extends TestCase
{
use EmailTrait;
public function testWelcomeEmail(): void
{
$mailer = new UserMailer();
$mailer->send('welcome', ['user@example.com', 'Alex']);
$this->assertMailSentTo('user@example.com');
$this->assertMailContainsText('Welcome Alex');
}
}
```
---
## 3. Quản Lý Test Database & Fixtures
### Cấu hình SQLite In-Memory trong `config/app_local.php`:
```php
'Datasources' => [
'test' => [
'className' => 'Cake\Database\Connection',
'driver' => 'Cake\Database\Driver\Sqlite',
'database' => ':memory:',
'encoding' => 'utf8',
'timezone' => 'UTC',
'cacheMetadata' => true,
],
],
```
### Khai báo Fixtures trong Test Case:
```php
protected array $fixtures = [
'app.Users',
'app.Articles',
];
```
---
## 4. Cách Chạy Test Tối Ưu
1. **Chạy qua Smart Test Runner**:
```bash
./test-runner.sh
# Chạy 1 file test cụ thể:
./test-runner.sh --filter=ArticlesControllerTest
```
2. **Chạy qua CakePHP CLI**:
```bash
bin/cake test app tests/TestCase/Model/Table/ArticlesTableTest.php
```
3. **Chạy qua PHPUnit trực tiếp**:
```bash
vendor/bin/phpunit --filter=ArticlesTableTest
```
4. **Đo Code Coverage cho SonarQube**:
```bash
./test-runner.sh --coverage-clover coverage.xml
```
+211
View File
@@ -0,0 +1,211 @@
# 🧩 CakePHP Components Unit Testing Reference
> **Hướng dẫn chi tiết cách viết test cho từng tầng thành phần trong CakePHP (Table, Entity, Controller, Service, Command, Component, Middleware, Mailer, Form, Behavior).**
---
## 1. Table Classes (`src/Model/Table/*Table.php`)
- **Base Class**: `Cake\TestSuite\TestCase`
- **Các điểm cần test**:
- `validationDefault()`: Đầu vào hợp lệ, thiếu trường bắt buộc, sai format (email, regex, số âm).
- `buildRules()`: Quy tắc toàn vẹn dữ liệu (`isUnique`, `existsIn`).
- Custom Finders (`findPublished()`, `findAuth()`): Truy vấn trả về đúng bản ghi mong muốn.
- Custom methods: Xử lý nghiệp vụ lưu dữ liệu.
```php
namespace App\Test\TestCase\Model\Table;
use App\Model\Table\ArticlesTable;
use Cake\TestSuite\TestCase;
class ArticlesTableTest extends TestCase
{
protected ArticlesTable $Articles;
protected array $fixtures = ['app.Articles'];
protected function setUp(): void
{
parent::setUp();
$this->Articles = $this->getTableLocator()->get('Articles');
}
public function testFindPublished(): void
{
$query = $this->Articles->find('published');
$this->assertInstanceOf(\Cake\ORM\Query::class, $query);
$result = $query->toArray();
$this->assertNotEmpty($result);
foreach ($result as $article) {
$this->assertTrue($article->published);
}
}
}
```
---
## 2. Entity Classes (`src/Model/Entity/*.php`)
- **Base Class**: `Cake\TestSuite\TestCase`
- **Các điểm cần test**:
- `_setPassword()` (Hashing logic).
- Virtual Fields (`_getFullName()`).
- `$_accessible` properties (Bảo vệ Mass Assignment).
```php
namespace App\Test\TestCase\Model\Entity;
use App\Model\Entity\User;
use Cake\TestSuite\TestCase;
class UserTest extends TestCase
{
public function testPasswordIsHashed(): void
{
$user = new User(['password' => 'secret123']);
$this->assertNotEquals('secret123', $user->password);
$this->assertTrue((new \Authentication\PasswordHasher\DefaultPasswordHasher())->check('secret123', $user->password));
}
public function testVirtualFieldFullName(): void
{
$user = new User(['first_name' => 'John', 'last_name' => 'Doe']);
$this->assertSame('John Doe', $user->full_name);
}
}
```
---
## 3. Controller Classes (`src/Controller/*Controller.php`)
- **Base Class**: `Cake\TestSuite\TestCase` với `Cake\TestSuite\IntegrationTestTrait`
- **Các điểm cần test**:
- HTTP Status: `200` OK, `302` Redirect, `403` Forbidden, `404` Not Found, `422` Unprocessable Entity.
- Flash messages: `$this->assertFlashMessage()`.
- Session & Auth: Giả lập đăng nhập qua `$this->session()`.
- CSRF & Security Tokens: Bắt buộc dùng `$this->enableCsrfToken()` và `$this->enableSecurityToken()` khi test POST/PUT/DELETE.
```php
namespace App\Test\TestCase\Controller;
use Cake\TestSuite\IntegrationTestTrait;
use Cake\TestSuite\TestCase;
class ArticlesControllerTest extends TestCase
{
use IntegrationTestTrait;
protected array $fixtures = ['app.Articles', 'app.Users'];
public function testAddWithoutAuthRedirectsToLogin(): void
{
$this->get('/articles/add');
$this->assertRedirectContains('/login');
}
public function testAddSuccess(): void
{
$this->session(['Auth.User.id' => 1]);
$this->enableCsrfToken();
$this->enableSecurityToken();
$this->post('/articles/add', [
'title' => 'Sample Article',
'body' => 'Body text',
'published' => true,
]);
$this->assertRedirect(['action' => 'index']);
$this->assertFlashMessage('The article has been saved.');
}
}
```
---
## 4. Service Classes (`src/Service/*.php`)
- **Base Class**: `Cake\TestSuite\TestCase`
- **Các điểm cần test**:
- Các dependency được mock bằng PHPUnit Mock hoặc Mockery.
- Kiểm thử toàn bộ các nhánh rẽ điều kiện (Success, Invalid Data, 3rd-Party Failure).
```php
namespace App\Test\TestCase\Service;
use App\Service\PaymentProcessingService;
use Cake\TestSuite\TestCase;
class PaymentProcessingServiceTest extends TestCase
{
public function testChargeSuccess(): void
{
$clientMock = $this->createMock(\App\External\StripeClient::class);
$clientMock->expects($this->once())
->method('createCharge')
->willReturn(['id' => 'ch_123', 'status' => 'succeeded']);
$service = new PaymentProcessingService($clientMock);
$result = $service->processOrder(100.0, 'tok_visa');
$this->assertTrue($result->isSuccess());
}
}
```
---
## 5. Console Command Classes (`src/Command/*Command.php`)
- **Base Class**: `Cake\TestSuite\TestCase` với `Cake\Console\TestSuite\ConsoleIntegrationTestTrait`
- **Các điểm cần test**:
- `assertExitSuccess()` và `assertExitError()`.
- `assertOutputContains()`.
- Arguments và Options flags.
```php
namespace App\Test\TestCase\Command;
use Cake\Console\TestSuite\ConsoleIntegrationTestTrait;
use Cake\TestSuite\TestCase;
class SyncInventoryCommandTest extends TestCase
{
use ConsoleIntegrationTestTrait;
protected function setUp(): void
{
parent::setUp();
$this->useCommandRunner();
}
public function testSyncAll(): void
{
$this->exec('sync_inventory --all');
$this->assertExitSuccess();
$this->assertOutputContains('Inventory sync completed.');
}
}
```
---
## 6. Controller Component Classes (`src/Controller/Component/*.php`)
- Test logic tái sử dụng gắn vào Controller, dùng `ComponentRegistry`.
---
## 7. Middleware Classes (`src/Middleware/*.php`)
- Test PSR-15 Middleware với `Cake\Http\ServerRequest` và Response Handler.
---
## 8. Mailer Classes (`src/Mailer/*Mailer.php`)
- Sử dụng `Cake\TestSuite\EmailTrait`, kiểm tra email recipient, subject, và template body.
---
## 9. Form Classes (`src/Form/*Form.php`)
- Test standalone forms không gắn với ORM table (ContactForm, SearchFilterForm).
- Kiểm tra `validationDefault()` và `_execute()`.
---
## 10. Behavior Classes (`src/Model/Behavior/*.php`)
- Test ORM Behavior hooks (`beforeSave`, `beforeFind`, `afterSave`).
+105 -37
View File
@@ -1,13 +1,12 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ============================================================================== # ==============================================================================
# SMARTGIFT AI - LARAVEL UNIT TESTING TOOLKIT INSTALLER # SMARTGIFT AI - MULTI-FRAMEWORK UNIT TESTING TOOLKIT INSTALLER
# Cài đặt bộ công cụ Unit Test vào bất kỳ source code Laravel nào mà KHÔNG CẦN clone repo về máy. # Hỗ trợ: Laravel & CakePHP
# Cài đặt bộ công cụ Unit Test vào bất kỳ source code nào mà KHÔNG CẦN clone repo về máy.
# #
# Cách dùng 1 (Khuyên dùng - Đứng trong source code Laravel và chạy): # Cách dùng 1 (Khuyên dùng - Đứng trong source code và chạy):
# curl -fsSL https://raw.githubusercontent.com/<OWNER>/agent-unit-testing/main/install.sh | bash # curl -fsSL https://raw.githubusercontent.com/<OWNER>/agent-unit-testing/main/install.sh | bash
# Hoặc:
# bash <(curl -fsSL https://raw.githubusercontent.com/<OWNER>/agent-unit-testing/main/install.sh)
# #
# Cách dùng 2 (Cài Toàn Cục vào máy): # Cách dùng 2 (Cài Toàn Cục vào máy):
# curl -fsSL https://raw.githubusercontent.com/<OWNER>/agent-unit-testing/main/install.sh | bash -s -- --global # curl -fsSL https://raw.githubusercontent.com/<OWNER>/agent-unit-testing/main/install.sh | bash -s -- --global
@@ -23,9 +22,9 @@ CYAN='\033[0;36m'
BOLD='\033[1m' BOLD='\033[1m'
NC='\033[0m' NC='\033[0m'
# Remote Repository URL công khai qua HTTPS (ai cũng truy cập được không cần SSH key) # Remote Repository URL công khai qua HTTPS
DEFAULT_REPO="https://git.smartgift.io.vn/Smartgift-AI/agent-unit-testing.git" DEFAULT_REPO="https://git.smartgift.io.vn/Smartgift-AI/agent-unit-testing.git"
REPO_URL="${LARAVEL_TEST_REPO:-$DEFAULT_REPO}" REPO_URL="${SG_TEST_REPO:-${LARAVEL_TEST_REPO:-$DEFAULT_REPO}}"
TARGET_DIR="" TARGET_DIR=""
GLOBAL_MODE=false GLOBAL_MODE=false
@@ -41,10 +40,10 @@ for arg in "$@"; do
;; ;;
--help|-h) --help|-h)
echo -e "${BLUE}===================================================================${NC}" echo -e "${BLUE}===================================================================${NC}"
echo -e "${CYAN}${BOLD} SMARTGIFT AI: LARAVEL UNIT TESTING TOOLKIT INSTALLER${NC}" echo -e "${CYAN}${BOLD} SMARTGIFT AI: PHP UNIT TESTING TOOLKIT (LARAVEL & CAKEPHP)${NC}"
echo -e "${BLUE}===================================================================${NC}" echo -e "${BLUE}===================================================================${NC}"
echo -e "Cách dùng:" echo -e "Cách dùng:"
echo -e " 1. Cài đặt trực tiếp vào source code Laravel hiện tại:" echo -e " 1. Cài đặt trực tiếp vào source code hiện tại:"
echo -e " ${YELLOW}curl -fsSL <URL>/install.sh | bash${NC}" echo -e " ${YELLOW}curl -fsSL <URL>/install.sh | bash${NC}"
echo -e " 2. Cài đặt vào một source code ở đường dẫn cụ thể:" echo -e " 2. Cài đặt vào một source code ở đường dẫn cụ thể:"
echo -e " ${YELLOW}curl -fsSL <URL>/install.sh | bash -s -- /duong-dan/source-code${NC}" echo -e " ${YELLOW}curl -fsSL <URL>/install.sh | bash -s -- /duong-dan/source-code${NC}"
@@ -68,7 +67,7 @@ done
TARGET_DIR="${TARGET_DIR:-.}" TARGET_DIR="${TARGET_DIR:-.}"
echo -e "${BLUE}===================================================================${NC}" echo -e "${BLUE}===================================================================${NC}"
echo -e "${CYAN}${BOLD} 🚀 SMARTGIFT AI - LARAVEL UNIT TESTING TOOLKIT INSTALLER${NC}" echo -e "${CYAN}${BOLD} 🚀 SMARTGIFT AI - UNIT TESTING TOOLKIT (LARAVEL & CAKEPHP)${NC}"
echo -e "${BLUE}===================================================================${NC}" echo -e "${BLUE}===================================================================${NC}"
# 1. Tìm hoặc Tải nguồn tài nguyên (Local nếu có, hoặc tải tạm qua Git) # 1. Tìm hoặc Tải nguồn tài nguyên (Local nếu có, hoặc tải tạm qua Git)
@@ -88,12 +87,8 @@ else
echo -e "${YELLOW}📥 Đang tải bộ công cụ từ remote repository...${NC}" echo -e "${YELLOW}📥 Đang tải bộ công cụ từ remote repository...${NC}"
TMP_DIR="$(mktemp -d 2>/dev/null || mktemp -d -t 'sg_unit_testing')" TMP_DIR="$(mktemp -d 2>/dev/null || mktemp -d -t 'sg_unit_testing')"
# Thử clone qua git
if ! git clone --depth 1 "$REPO_URL" "$TMP_DIR" --quiet 2>/dev/null; then if ! git clone --depth 1 "$REPO_URL" "$TMP_DIR" --quiet 2>/dev/null; then
echo -e "${RED}❌ Không thể clone từ repository: $REPO_URL${NC}" echo -e "${RED}❌ Không thể clone từ repository: $REPO_URL${NC}"
echo -e "${YELLOW}💡 Lưu ý dành cho người dùng bên ngoài:${NC}"
echo -e " Nếu bạn không phải thành viên có SSH key nội bộ, hãy chạy với URL HTTPS công khai:"
echo -e " ${CYAN}LARAVEL_TEST_REPO=https://github.com/<OWNER>/<REPO>.git curl -fsSL <URL>/install.sh | bash${NC}"
rm -rf "$TMP_DIR" rm -rf "$TMP_DIR"
exit 1 exit 1
fi fi
@@ -119,16 +114,18 @@ if [ "$GLOBAL_MODE" = true ]; then
mkdir -p "$HOME/.claude/skills" mkdir -p "$HOME/.claude/skills"
cp -R "$PACKAGE_ROOT/.claude/skills/"* "$HOME/.claude/skills/" cp -R "$PACKAGE_ROOT/.claude/skills/"* "$HOME/.claude/skills/"
mkdir -p "$HOME/.claude/laravel-testing-docs/templates" mkdir -p "$HOME/.claude/testing-docs/templates"
cp -R "$PACKAGE_ROOT/docs/"* "$HOME/.claude/laravel-testing-docs/" cp -R "$PACKAGE_ROOT/docs/"* "$HOME/.claude/testing-docs/"
cp -R "$PACKAGE_ROOT/rules/"* "$HOME/.claude/laravel-testing-docs/" cp -R "$PACKAGE_ROOT/rules/"* "$HOME/.claude/testing-docs/"
cp -R "$PACKAGE_ROOT/templates/"* "$HOME/.claude/laravel-testing-docs/templates/" cp -R "$PACKAGE_ROOT/templates/"* "$HOME/.claude/testing-docs/templates/"
mkdir -p "$HOME/.local/bin" mkdir -p "$HOME/.local/bin"
# Lệnh khởi tạo test runner
cat << 'EOF' > "$HOME/.local/bin/sg-test-init" cat << 'EOF' > "$HOME/.local/bin/sg-test-init"
#!/usr/bin/env bash #!/usr/bin/env bash
TARGET_DIR="${1:-.}" TARGET_DIR="${1:-.}"
SOURCE_DIR="$HOME/.claude/laravel-testing-docs" SOURCE_DIR="$HOME/.claude/testing-docs"
if [ ! -d "$TARGET_DIR" ]; then if [ ! -d "$TARGET_DIR" ]; then
echo "Lỗi: Thư mục '$TARGET_DIR' không tồn tại!" echo "Lỗi: Thư mục '$TARGET_DIR' không tồn tại!"
@@ -148,16 +145,43 @@ if [ -f "$SOURCE_DIR/templates/setup-dependencies.sh" ]; then
echo " ✓ Đã tạo $TARGET_DIR/setup-test-deps.sh" echo " ✓ Đã tạo $TARGET_DIR/setup-test-deps.sh"
fi fi
echo "🎉 Hoàn tất! Mở Claude Code và gõ: /laravel-unit-test --all --run" echo "🎉 Hoàn tất! Mở Claude Code và gõ lệnh test tương ứng:"
echo " - CakePHP: /cakephp-unit-test --all --run"
echo " - Laravel: /laravel-unit-test --all --run"
EOF EOF
chmod +x "$HOME/.local/bin/sg-test-init" chmod +x "$HOME/.local/bin/sg-test-init"
cp "$HOME/.local/bin/sg-test-init" "$HOME/.local/bin/laravel-test-init" cp "$HOME/.local/bin/sg-test-init" "$HOME/.local/bin/laravel-test-init"
chmod +x "$HOME/.local/bin/laravel-test-init" chmod +x "$HOME/.local/bin/laravel-test-init"
cp "$HOME/.local/bin/sg-test-init" "$HOME/.local/bin/cakephp-test-init"
chmod +x "$HOME/.local/bin/cakephp-test-init"
# Lệnh cập nhật bộ công cụ (Update)
cat << 'EOF' > "$HOME/.local/bin/sg-test-update"
#!/usr/bin/env bash
REPO_URL="${SG_TEST_REPO:-https://git.smartgift.io.vn/Smartgift-AI/agent-unit-testing.git}"
echo "🔄 Đang cập nhật bộ công cụ Unit Test từ Git repository..."
TMP_DIR="$(mktemp -d 2>/dev/null || mktemp -d -t 'sg_update')"
if git clone --depth 1 "$REPO_URL" "$TMP_DIR" --quiet 2>/dev/null; then
bash "$TMP_DIR/install.sh" --global
rm -rf "$TMP_DIR"
echo "✅ Cập nhật toàn bộ công cụ thành công!"
else
echo "❌ Lỗi: Không thể kết nối hoặc clone repository $REPO_URL"
rm -rf "$TMP_DIR"
exit 1
fi
EOF
chmod +x "$HOME/.local/bin/sg-test-update"
cp "$HOME/.local/bin/sg-test-update" "$HOME/.local/bin/laravel-test-update"
chmod +x "$HOME/.local/bin/laravel-test-update"
cp "$HOME/.local/bin/sg-test-update" "$HOME/.local/bin/cakephp-test-update"
chmod +x "$HOME/.local/bin/cakephp-test-update"
echo -e "${GREEN}${BOLD}✓ CÀI ĐẶT TOÀN CỤC HOÀN TẤT THÀNH CÔNG!${NC}" echo -e "${GREEN}${BOLD}✓ CÀI ĐẶT TOÀN CỤC HOÀN TẤT THÀNH CÔNG!${NC}"
echo -e "Kể từ bây giờ, ở bất kỳ source code nào, bạn mở Claude Code là có thể gõ ngay:" echo -e "Kể từ bây giờ, ở bất kỳ source code nào, bạn mở Claude Code là có thể gõ ngay:"
echo -e " • ${CYAN}/laravel-test-setup --install${NC}" echo -e " • CakePHP: ${CYAN}/cakephp-test-setup --install${NC} | ${CYAN}/cakephp-unit-test --all --run${NC}"
echo -e " • ${CYAN}/laravel-unit-test --all --run${NC}" echo -e " • Laravel: ${CYAN}/laravel-test-setup --install${NC} | ${CYAN}/laravel-unit-test --all --run${NC}"
echo -e "Và các lệnh terminal: ${CYAN}cakephp-test-init${NC}, ${CYAN}laravel-test-init${NC}, ${CYAN}cakephp-test-update${NC}, ${CYAN}laravel-test-update${NC}"
exit 0 exit 0
fi fi
@@ -171,13 +195,20 @@ if [ ! -d "$TARGET_DIR" ]; then
exit 1 exit 1
fi fi
if [ ! -f "$TARGET_DIR/artisan" ] && [ ! -f "$TARGET_DIR/composer.json" ]; then # Phát hiện framework
# Kiểm tra xem thư mục có trống hoặc là thư mục mới không FRAMEWORK="generic"
if [ -f "$TARGET_DIR/bin/cake" ] || ( [ -f "$TARGET_DIR/composer.json" ] && grep -q '"cakephp/cakephp"' "$TARGET_DIR/composer.json" ); then
FRAMEWORK="cakephp"
echo -e "${GREEN}✓ Phát hiện dự án: CakePHP${NC}"
elif [ -f "$TARGET_DIR/artisan" ] || ( [ -f "$TARGET_DIR/composer.json" ] && grep -q '"laravel/framework"' "$TARGET_DIR/composer.json" ); then
FRAMEWORK="laravel"
echo -e "${GREEN}✓ Phát hiện dự án: Laravel${NC}"
elif [ ! -f "$TARGET_DIR/composer.json" ]; then
FILE_COUNT=$(ls -A "$TARGET_DIR" 2>/dev/null | wc -l | tr -d ' ') FILE_COUNT=$(ls -A "$TARGET_DIR" 2>/dev/null | wc -l | tr -d ' ')
if [ "$FILE_COUNT" -le 2 ]; then if [ "$FILE_COUNT" -le 2 ]; then
echo -e "${YELLOW}ℹ Thư mục mới/trống: Tự động khởi tạo bộ công cụ Unit Test...${NC}" echo -e "${YELLOW}ℹ Thư mục mới/trống: Tự động khởi tạo bộ công cụ Unit Test...${NC}"
elif [ "$FORCE_MODE" = false ]; then elif [ "$FORCE_MODE" = false ]; then
echo -e "${YELLOW}⚠️ Cảnh báo: Thư mục '$TARGET_DIR' đang chứa dữ liệu khác và chưa có file artisan hoặc composer.json.${NC}" echo -e "${YELLOW}⚠️ Cảnh báo: Thư mục '$TARGET_DIR' đang chứa dữ liệu khác và chưa có file composer.json.${NC}"
echo -e "Nếu bạn vẫn muốn cài đặt vào thư mục này, hãy chạy:" echo -e "Nếu bạn vẫn muốn cài đặt vào thư mục này, hãy chạy:"
echo -e " ${CYAN}bash <(curl -fsSL <URL>/install.sh) --force${NC}" echo -e " ${CYAN}bash <(curl -fsSL <URL>/install.sh) --force${NC}"
exit 1 exit 1
@@ -189,7 +220,7 @@ echo -e "📦 Đang cài đặt công cụ vào source code: ${YELLOW}$TARGET_DI
# A. Copy Claude Skills vào .claude/skills/ của source code # A. Copy Claude Skills vào .claude/skills/ của source code
mkdir -p "$TARGET_DIR/.claude/skills" mkdir -p "$TARGET_DIR/.claude/skills"
cp -R "$PACKAGE_ROOT/.claude/skills/"* "$TARGET_DIR/.claude/skills/" cp -R "$PACKAGE_ROOT/.claude/skills/"* "$TARGET_DIR/.claude/skills/"
echo -e " ${GREEN}✓${NC} Đã cài bộ 4 Claude Code Skills (.claude/skills/)" echo -e " ${GREEN}✓${NC} Đã cài bộ Claude Code Skills (Laravel & CakePHP)"
# B. Copy cẩm nang tài liệu vào .claude/testing-docs/ # B. Copy cẩm nang tài liệu vào .claude/testing-docs/
mkdir -p "$TARGET_DIR/.claude/testing-docs" mkdir -p "$TARGET_DIR/.claude/testing-docs"
@@ -206,17 +237,18 @@ cp "$PACKAGE_ROOT/templates/setup-dependencies.sh" "$TARGET_DIR/setup-test-deps.
chmod +x "$TARGET_DIR/setup-test-deps.sh" chmod +x "$TARGET_DIR/setup-test-deps.sh"
echo -e " ${GREEN}✓${NC} Đã tạo dependency installer: ./setup-test-deps.sh" echo -e " ${GREEN}✓${NC} Đã tạo dependency installer: ./setup-test-deps.sh"
# D. Tự động cấu hình SQLite In-Memory vào phpunit.xml # D. Cấu hình môi trường an toàn SQLite In-Memory
PHPUNIT_XML="$TARGET_DIR/phpunit.xml" if [ "$FRAMEWORK" = "laravel" ]; then
PHPUNIT_DIST="$TARGET_DIR/phpunit.xml.dist" PHPUNIT_XML="$TARGET_DIR/phpunit.xml"
TARGET_XML="" PHPUNIT_DIST="$TARGET_DIR/phpunit.xml.dist"
if [ -f "$PHPUNIT_XML" ]; then TARGET_XML=""
if [ -f "$PHPUNIT_XML" ]; then
TARGET_XML="$PHPUNIT_XML" TARGET_XML="$PHPUNIT_XML"
elif [ -f "$PHPUNIT_DIST" ]; then elif [ -f "$PHPUNIT_DIST" ]; then
TARGET_XML="$PHPUNIT_DIST" TARGET_XML="$PHPUNIT_DIST"
fi fi
if [ -n "$TARGET_XML" ]; then if [ -n "$TARGET_XML" ]; then
if ! grep -q 'name="DB_CONNECTION"' "$TARGET_XML" || ! grep -q 'value="sqlite"' "$TARGET_XML"; then if ! grep -q 'name="DB_CONNECTION"' "$TARGET_XML" || ! grep -q 'value="sqlite"' "$TARGET_XML"; then
if grep -q '<php>' "$TARGET_XML"; then if grep -q '<php>' "$TARGET_XML"; then
sed -i.bak '/<php>/a\ sed -i.bak '/<php>/a\
@@ -233,13 +265,34 @@ if [ -n "$TARGET_XML" ]; then
else else
echo -e " ${GREEN}✓${NC} Cấu hình SQLite in-memory trong phpunit.xml đã chuẩn xác" echo -e " ${GREEN}✓${NC} Cấu hình SQLite in-memory trong phpunit.xml đã chuẩn xác"
fi fi
fi
elif [ "$FRAMEWORK" = "cakephp" ]; then
# Nếu chưa có phpunit.xml, copy file mẫu
if [ ! -f "$TARGET_DIR/phpunit.xml" ] && [ ! -f "$TARGET_DIR/phpunit.xml.dist" ]; then
cp "$PACKAGE_ROOT/templates/cakephp/configs/phpunit.xml.dist.template" "$TARGET_DIR/phpunit.xml.dist"
echo -e " ${GREEN}✓${NC} Đã tạo phpunit.xml.dist chuẩn cho CakePHP"
else
echo -e " ${GREEN}✓${NC} Đã phát hiện cấu hình PHPUnit CakePHP"
fi
if [ -f "$TARGET_DIR/config/app_local.php" ]; then
if grep -q "'test'" "$TARGET_DIR/config/app_local.php"; then
echo -e " ${GREEN}✓${NC} Cấu hình Datasources.test trong config/app_local.php đã sẵn sàng"
else
echo -e " ${YELLOW}ℹ${NC} Lưu ý: Hãy đảm bảo Datasources.test trong config/app_local.php sử dụng SQLite :memory:"
fi
fi
fi fi
# E. Tạo GitHub Actions CI workflow nếu có .git # E. Tạo GitHub Actions CI workflow nếu có .git
if [ -d "$TARGET_DIR/.git" ]; then if [ -d "$TARGET_DIR/.git" ]; then
mkdir -p "$TARGET_DIR/.github/workflows" mkdir -p "$TARGET_DIR/.github/workflows"
if [ ! -f "$TARGET_DIR/.github/workflows/unit-tests.yml" ]; then if [ ! -f "$TARGET_DIR/.github/workflows/unit-tests.yml" ]; then
if [ "$FRAMEWORK" = "cakephp" ]; then
cp "$PACKAGE_ROOT/templates/ci/github-actions-cakephp.yml.template" "$TARGET_DIR/.github/workflows/unit-tests.yml"
else
cp "$PACKAGE_ROOT/templates/ci/github-actions.yml.template" "$TARGET_DIR/.github/workflows/unit-tests.yml" cp "$PACKAGE_ROOT/templates/ci/github-actions.yml.template" "$TARGET_DIR/.github/workflows/unit-tests.yml"
fi
echo -e " ${GREEN}✓${NC} Đã tạo CI/CD Quality Gate (.github/workflows/unit-tests.yml)" echo -e " ${GREEN}✓${NC} Đã tạo CI/CD Quality Gate (.github/workflows/unit-tests.yml)"
fi fi
fi fi
@@ -248,7 +301,22 @@ echo -e "\n${GREEN}${BOLD}🎉 CÀI ĐẶT HOÀN TẤT THÀNH CÔNG VÀO SOURCE
echo -e "\n${YELLOW}${BOLD}BÂY GIỜ BẠN CÓ THỂ VIẾT UNIT TEST NGAY:${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:" echo -e "1. Mở Claude Code tại source code này:"
echo -e " ${CYAN}claude${NC}" echo -e " ${CYAN}claude${NC}"
echo -e "2. Gõ 1 lệnh duy nhất để AI viết test toàn bộ và tự chạy test luôn:"
echo -e " ${CYAN}/laravel-unit-test --all --run${NC}" if [ "$FRAMEWORK" = "cakephp" ]; then
echo -e "3. Hoặc chạy test trực tiếp từ terminal:" echo -e "2. Gõ lệnh dành riêng cho CakePHP:"
echo -e " - Setup môi trường: ${CYAN}/cakephp-test-setup --install${NC}"
echo -e " - Viết test & chạy test: ${CYAN}/cakephp-unit-test --all --run${NC}"
echo -e " - Hoặc theo tầng: ${CYAN}/cakephp-unit-test src/Model/Table/${NC}"
elif [ "$FRAMEWORK" = "laravel" ]; then
echo -e "2. Gõ lệnh dành riêng cho Laravel:"
echo -e " - Setup môi trường: ${CYAN}/laravel-test-setup --install${NC}"
echo -e " - Viết test & chạy test: ${CYAN}/laravel-unit-test --all --run${NC}"
echo -e " - Hoặc theo tầng: ${CYAN}/laravel-unit-test app/Services/${NC}"
else
echo -e "2. Gõ lệnh tương ứng với framework của bạn:"
echo -e " - CakePHP: ${CYAN}/cakephp-unit-test --all --run${NC}"
echo -e " - Laravel: ${CYAN}/laravel-unit-test --all --run${NC}"
fi
echo -e "3. Chạy test trực tiếp từ terminal bất kỳ lúc nào:"
echo -e " ${CYAN}./test-runner.sh${NC}" echo -e " ${CYAN}./test-runner.sh${NC}"
+26 -23
View File
@@ -1,32 +1,34 @@
# System Prompt: Laravel Unit Test Generator # System Prompt: PHP Unit Testing Generator (Laravel & CakePHP)
Bạn là một Chuyên gia Kỹ thuật Cấp cao về Kiểm thử Phần mềm (Principal QA Automation Engineer & Laravel Core Specialist). Nhiệm vụ của bạn là phân tích mã nguồn PHP Laravel bất kỳ và tạo ra các bộ Unit Test đạt tiêu chuẩn ngành cao nhất. Bạn là một Chuyên gia Kỹ thuật Cấp cao về Kiểm thử Phần mềm (Principal QA Automation Engineer, Laravel & CakePHP Core Specialist). Nhiệm vụ của bạn là phân tích mã nguồn PHP (Laravel hoặc CakePHP) và tạo ra các bộ Unit & Integration Test đạt tiêu chuẩn ngành cao nhất.
--- ---
## 1. Mục Tiêu Tối Thượng (Prime Directives) ## 1. Mục Tiêu Tối Thượng (Prime Directives)
1. **Tính Cô Lập Tuyệt Đối (Zero Leakage)**: Mọi Unit Test bạn tạo ra không được phụ thuộc vào Database ngoại vi hoặc Network thật. Mọi external dependencies đều phải được Mock bằng Mockery hoặc Laravel Built-in Fakes. 1. **Tính Cô Lập Tuyệt Đối & An Toàn Database (Zero Leakage)**:
- **Laravel**: Sử dụng kết nối `sqlite` với database `:memory:`. External dependencies (API ngoài, Queue, Mail, Storage) phải được Mock bằng Mockery hoặc Laravel Built-in Fakes (`Http::fake()`, `Queue::fake()`).
- **CakePHP**: Tuyệt đối không chạy test trên kết nối `default`. Bắt buộc dùng `Datasources.test` (SQLite `:memory:`) kết hợp với Fixtures hoặc Transaction Strategy để tự rollback dữ liệu sau mỗi test.
2. **Tuân Thủ Mô Hình AAA**: Bắt buộc chia rõ 3 khối: `// Arrange`, `// Act`, `// Assert`. 2. **Tuân Thủ Mô Hình AAA**: Bắt buộc chia rõ 3 khối: `// Arrange`, `// Act`, `// Assert`.
3. **Độ Phủ Biên & Nhánh Toàn Diện (Exhaustive & SonarQube Branch Coverage)**: 3. **Độ Phủ Biên & Nhánh Toàn Diện (SonarQube Branch Coverage > 85% - 100%)**:
- **Bắt buộc 100% Public Methods**: Mọi phương thức public của class đều phải có bài test. Tuyệt đối không được bỏ qua bất kỳ public method nào. - **Bắt buộc 100% Public Methods**: Mọi phương thức public của class đều phải có bài test.
- **Đặc thù với Jobs & Console Commands**: BẮT BUỘC 100% phải test phương thức **`handle()`** (với đầy đủ luồng thực thi, mock S3, Redis, Database, Mail) và phương thức `failed()`. Tuyệt đối CẤM chỉ test hàm helper phụ (như `buildRedisKey`) mà bỏ qua `handle()`. - **Đặc thù Laravel**:
- **Đặc thù với Repositories**: BẮT BUỘC phải test kết quả trả về của các phương thức truy vấn và cập nhật dữ liệu thật (trên SQLite in-memory). Tuyệt đối CẤM chỉ assert lỏng lẻo kiểu `$this->assertInstanceOf(Builder::class)`. - *Jobs & Artisan Commands*: BẮT BUỘC 100% phải test phương thức **`handle()`** và `failed()`. Tuyệt đối CẤM chỉ test hàm helper phụ.
- **Bao phủ 100% mọi nhánh rẽ SonarQube**: - *Repositories*: BẮT BUỘC phải test kết quả truy vấn / cập nhật dữ liệu thật trên SQLite in-memory, cấm chỉ assert `Builder::class`.
- **Đặc thù CakePHP**:
- *Controllers*: BẮT BUỘC dùng **`Cake\TestSuite\IntegrationTestTrait`**, bật CSRF/Security token (`$this->enableCsrfToken()`, `$this->enableSecurityToken()`), test HTTP status (200, 302, 403, 404, 422), redirect, flash message. Tuyệt đối CẤM tạo `new Controller()` thủ công.
- *Commands*: BẮT BUỘC dùng **`Cake\Console\TestSuite\ConsoleIntegrationTestTrait`**, test exit code (`assertExitSuccess()`, `assertExitError()`), arguments, options và stdout.
- *Tables (ORM)*: BẮT BUỘC test `validationDefault()`, `buildRules()`, custom finders (`find('published')`), và save/delete với Fixtures.
- *Mailers*: BẮT BUỘC dùng **`Cake\TestSuite\EmailTrait`** để kiểm tra email recipient, subject, view vars mà không gửi SMTP thật.
- **Bao phủ 100% mọi nhánh rẽ**:
- Mọi toán tử `??` (Null Coalescing) và `?:` (Ternary) phải có test case cho cả 2 vế. - Mọi toán tử `??` (Null Coalescing) và `?:` (Ternary) phải có test case cho cả 2 vế.
- Mọi khối `try - catch - finally` bắt buộc phải có test case ép mock ném Exception để chạy qua khối `catch`. - Mọi khối `try - catch - finally` bắt buộc phải có test case ép mock ném Exception để chạy qua khối `catch`.
- Mọi `if`, `else if`, `else`, `switch`, `match` (kể cả nhánh `default`) đều phải được kích hoạt. - Mọi `if`, `else if`, `else`, `switch`, `match` (kể cả nhánh `default`) đều phải được kích hoạt.
- Không bao giờ chỉ viết Happy Path. Bắt buộc quét sạch: 4. **Tự Động Thích Ứng Framework & Phiên Bản**:
- Happy Path (Đầu vào chuẩn). - **PHP 8.4+**: Hỗ trợ Property Hooks và Asymmetric Visibility.
- Negative Path (Đầu vào không hợp lệ / Validation lỗi). - **PHPUnit 10/11/12**: Sử dụng PHP 8 Attributes (`#[Test]`, `#[DataProvider]`, `#[TestDox]`).
- Boundary Cases (Min, Max, Zero, Null, Empty Array). - **CakePHP 4/5**: Dùng `Cake\TestSuite\TestCase`, `$this->fetchTable()`.
- Exception Cases (Lỗi mạng, External Service ném Exception, Unauthorized). - **CakePHP 3 / Laravel cũ**: Fallback DocBlock `/** @test */`.
4. **Tự Động Thích Ứng Framework & Phiên Bản (Bao Quát PHP 8.4+ & Laravel 12/13)**:
- Nếu code dùng tính năng **PHP 8.4+** (Property Hooks, Asymmetric Visibility): Test trực tiếp hook logic và xác thực tính toàn vẹn của state.
- Nếu dự án dùng **Pest PHP (v2/v3)**: Sinh test chuẩn Functional syntax (`it()`, `describe()`, `expect()`) và hỗ trợ Architectural Testing (`arch()`).
- Nếu dự án dùng **PHPUnit (10/11/12)**: 100% sử dụng PHP 8 Attributes (`#[Test]`, `#[DataProvider]`, `#[TestDox]`), tuyệt đối không dùng DocBlock cũ.
- Nếu dự án cũ (Laravel 8/9, PHPUnit 9): Fallback về DocBlock `/** @test */`.
5. **Không Vi Phạm 12 Anti-Patterns**: Đọc và tuân thủ tuyệt đối các quy định trong `docs/05-anti-patterns.md`.
--- ---
@@ -34,15 +36,16 @@ Bạn là một Chuyên gia Kỹ thuật Cấp cao về Kiểm thử Phần mề
Khi nhận một class cần viết test: Khi nhận một class cần viết test:
- **Bước 1: Phân tích Class**: - **Bước 1: Phân tích Class**:
- Nhận diện loại component (Action, Service, Controller, FormRequest, Job, Policy,...). - Nhận diện Framework (Laravel vs CakePHP) và loại component.
- Liệt kê các constructor dependencies và public methods. - Liệt kê các constructor dependencies và public methods.
- Phân loại cái gì cần Mock (Services, Repositories, APIs) và cái gì dùng Object thật (DTOs, Value Objects). - Phân loại cái gì cần Mock (Services, 3rd Party APIs) và cái gì dùng Object thật (DTOs, Value Objects, Entities).
- **Bước 2: Lập Ma Trận Kịch Bản Test (Test Matrix)**: - **Bước 2: Lập Ma Trận Kịch Bản Test (Test Matrix)**:
- Liệt kê tối thiểu: 1 Happy Path + N Edge Cases + N Exception Cases. - Liệt kê tối thiểu: 1 Happy Path + N Edge Cases + N Exception Cases.
- **Bước 3: Lựa Chọn Template Tương Ứng**: - **Bước 3: Lựa Chọn Template Tương Ứng**:
- Tham chiếu cấu trúc trong thư mục `docs/components/` và `templates/`. - CakePHP: Tham chiếu `templates/cakephp/phpunit/` và `docs/cakephp/`.
- Laravel: Tham chiếu `templates/phpunit/`, `templates/pest/` và `docs/components/`.
- **Bước 4: Sinh Mã Nguồn Test Hoàn Chỉnh**: - **Bước 4: Sinh Mã Nguồn Test Hoàn Chỉnh**:
- Đầy đủ Namespace, Use statements, Typed properties, strict assertions. - Đầy đủ Namespace, Use statements, Typed properties, strict assertions (`assertSame`).
- Không viết code tắt, không dùng `// TODO`. - Không viết code tắt, không dùng `// TODO`.
- **Bước 5: Tự Thẩm Định (Self-Verification)**: - **Bước 5: Tự Thẩm Định (Self-Verification)**:
- Soát lại qua `rules/verification-checklist.md` trước khi xuất kết quả. - Soát lại qua `rules/verification-checklist.md` trước khi xuất kết quả.
+15 -14
View File
@@ -1,12 +1,13 @@
# Checklist Tự Thẩm Định Chất Lượng Unit Test (Verification Checklist) # Checklist Tự Thẩm Định Chất Lượng Unit Test (Verification Checklist)
Trước khi xuất bất kỳ file test nào cho người dùng, Agent **BẮT BUỘC** phải tự đối chiếu mã nguồn của mình qua 10 câu hỏi kiểm định nghiêm ngặt dưới đây: Trước khi xuất bất kỳ file test nào cho người dùng, Agent **BẮT BUỘC** phải tự đối chiếu mã nguồn của mình qua các câu hỏi kiểm định nghiêm ngặt dưới đây:
--- ---
### [ ] 1. Tính Đơn Vị (Is it really a Unit Test?) ### [ ] 1. Tính Đơn Vị & An Toàn Cơ Sở Dữ Liệu (Isolation & DB Safety)
- Test có đang vô tình gọi Database thật hoặc gọi router HTTP không? - Test có đang vô tình gọi Database dev thật hoặc gọi router HTTP ngoài đời thực không?
- Các dependency nặng (API, Queue, Mail, Storage, Repository) đã được Mock hoặc Fake 100% chưa? - Với **CakePHP**: Đã cấu hình dùng `Datasources.test` (SQLite `:memory:`) và khai báo Fixtures đầy đủ chưa?
- Với **Laravel**: Đã cấu hình SQLite `:memory:` và mock các dịch vụ ngoài bằng Fake/Mockery chưa?
### [ ] 2. Cấu Trúc AAA Rõ Ràng (Is AAA clearly structured?) ### [ ] 2. Cấu Trúc AAA Rõ Ràng (Is AAA clearly structured?)
- Các khối `// Arrange`, `// Act`, `// Assert` có được phân định rõ ràng bằng comment không? - Các khối `// Arrange`, `// Act`, `// Assert` có được phân định rõ ràng bằng comment không?
@@ -16,8 +17,8 @@ Trước khi xuất bất kỳ file test nào cho người dùng, Agent **BẮT
- Mọi mock method có được khai báo số lần gọi cụ thể (`->once()`, `->never()`, `->times(N)`) thay vì bỏ trống không? - Mọi mock method có được khai báo số lần gọi cụ thể (`->once()`, `->never()`, `->times(N)`) thay vì bỏ trống không?
- Các tham số truyền vào hàm mock có được kiểm tra qua `->with(...)` hoặc `->withArgs(...)` không? - Các tham số truyền vào hàm mock có được kiểm tra qua `->with(...)` hoặc `->withArgs(...)` không?
### [ ] 4. Không Over-Mock DTO/Value Object (Are value objects kept real?) ### [ ] 4. Không Over-Mock DTO/Value Object/Entity (Are value objects kept real?)
- Các đối tượng DTO, Value Object, Collection có được tạo bằng instance thật thay vì mock không? - Các đối tượng DTO, Value Object, Collection, CakePHP Entity có được tạo bằng instance thật thay vì mock không?
### [ ] 5. Độ Phủ Ngoại Lệ, Kịch Bản Biên & SonarQube Branches (Are edge cases & branches covered?) ### [ ] 5. Độ Phủ Ngoại Lệ, Kịch Bản Biên & SonarQube Branches (Are edge cases & branches covered?)
- Đã có test case cho trường hợp ném ngoại lệ (Exception) chưa? - Đã có test case cho trường hợp ném ngoại lệ (Exception) chưa?
@@ -25,24 +26,24 @@ Trước khi xuất bất kỳ file test nào cho người dùng, Agent **BẮT
- Mọi toán tử `??` và `?:` đã có test case cho cả 2 vế chưa? - Mọi toán tử `??` và `?:` đã có test case cho cả 2 vế chưa?
- Mọi khối `try/catch` đã có test case ép mock ném Exception để chạy qua khối `catch` chưa? - Mọi khối `try/catch` đã có test case ép mock ném Exception để chạy qua khối `catch` chưa?
### [ ] 6. Không Bỏ Sót Phương Thức Cốt Lõi (Are all core methods tested?) ### [ ] 6. Không Bỏ Sót Phương Thức & Trait Cốt Lõi (Are all core methods & traits tested?)
- Nếu là **Job hoặc Console Command**: Đã test phương thức **`handle()`** chưa? Tuyệt đối không được chỉ test method helper phụ (như `buildRedisKey`) bằng reflection! - **CakePHP Controller**: Đã sử dụng `Cake\TestSuite\IntegrationTestTrait` và gọi `$this->enableCsrfToken()`, `$this->enableSecurityToken()` khi test POST/PUT chưa?
- Nếu là **Repository**: Đã test các phương thức truy vấn và cập nhật dữ liệu với kết quả trả về cụ thể (trên SQLite in-memory) chưa? Tuyệt đối không chỉ assert `Builder::class`! - **CakePHP Command**: Đã sử dụng `Cake\Console\TestSuite\ConsoleIntegrationTestTrait` và assert exit code (`assertExitSuccess()`) chưa?
- **CakePHP Mailer**: Đã sử dụng `Cake\TestSuite\EmailTrait` chưa?
- **Laravel Job hoặc Command**: Đã test phương thức **`handle()`** và `failed()` chưa? Tuyệt đối không chỉ test helper phụ!
- Tất cả các `public methods` của class đã có ít nhất một bài test tương ứng chưa? - Tất cả các `public methods` của class đã có ít nhất một bài test tương ứng chưa?
### [ ] 7. Assertions Chặt Chẽ & Có Nghĩa (Are assertions meaningful?) ### [ ] 7. Assertions Chặt Chẽ & Có Nghĩa (Are assertions meaningful?)
- Có sử dụng Strict Assertion (`assertSame`, `toBe`) thay vì loose assertion không? - Có sử dụng Strict Assertion (`assertSame`, `toBe`) thay vì loose assertion không?
- Với CakePHP: Có dùng assertion chuyên dụng (`assertResponseOk()`, `assertRedirect()`, `assertFlashMessage()`) không?
- Có bị dính assertion rỗng kiểu `$this->assertTrue(true)` không? - Có bị dính assertion rỗng kiểu `$this->assertTrue(true)` không?
### [ ] 8. Đặt Tên Chuẩn & Dễ Đọc (Are method names expressive?) ### [ ] 8. Đặt Tên Chuẩn & Dễ Đọc (Are method names expressive?)
- Tên test method có thể hiện rõ "Hành động + Kết quả + Ngữ cảnh" không? (Ví dụ: `it_throws_exception_when_balance_is_insufficient`) - Tên test method có thể hiện rõ "Hành động + Kết quả + Ngữ cảnh" không? (Ví dụ: `testValidationFailsWhenTitleIsEmpty` hoặc `testAddPostSuccessWithAuth`)
### [ ] 9. Cú Pháp Đúng Phiên Bản Framework (Is syntax version-accurate?) ### [ ] 9. Cú Pháp Đúng Phiên Bản Framework (Is syntax version-accurate?)
- Nếu là PHPUnit 10+: Đã dùng Attribute `#[Test]` và `#[DataProvider]` thay cho DocBlock cũ chưa? - Nếu là PHPUnit 10+: Đã dùng Attribute `#[Test]` và `#[DataProvider]` thay cho DocBlock cũ chưa?
- Nếu là Pest: Đã dùng đúng cú pháp `describe()`, `it()`, `expect()` chưa? - Nếu là Pest: Đã dùng đúng cú pháp `describe()`, `it()`, `expect()` chưa?
### [ ] 10. Import Đầy Đủ & Namespace Chính Xác (Are imports complete?) ### [ ] 10. Import Đầy Đủ & Namespace Chính Xác (Are imports complete?)
- Tất cả các class, exception, interface, attribute sử dụng trong file test đã có lệnh `use ...` ở đầu file chưa? (Tránh lỗi `Class not found`). - Tất cả các class, exception, interface, attribute, trait sử dụng trong file test đã có lệnh `use ...` ở đầu file chưa? (Tránh lỗi `Class not found`).
### [ ] 11. Không Gây Rò Rỉ Trạng Thái (Zero State Leakage)
- Nếu có dùng `Carbon::setTestNow()` hay mock Facade tĩnh, đã đảm bảo dọn dẹp hoặc reset về trạng thái ban đầu chưa?
@@ -0,0 +1,22 @@
<?php
/*
* Local test configuration for CakePHP
* Đảm bảo Datasources.test dùng SQLite In-memory (:memory:) an toàn, tốc độ cao.
*/
return [
'Datasources' => [
'test' => [
'className' => 'Cake\Database\Connection',
'driver' => 'Cake\Database\Driver\Sqlite',
'persistent' => false,
'timezone' => 'UTC',
'flags' => [],
'encoding' => 'utf8',
'database' => ':memory:',
'init' => [],
'cacheMetadata' => true,
'quoteIdentifiers' => false,
'log' => false,
],
],
];
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
colors="true"
processIsolation="false"
stopOnFailure="false"
bootstrap="tests/bootstrap.php"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd"
cacheDirectory=".phpunit.cache">
<php>
<ini name="memory_limit" value="-1"/>
<ini name="apc.enable_cli" value="1"/>
<env name="FIXTURE_SCHEMA_METADATA" value="tests/schema.php"/>
</php>
<!-- Add any additional test suites you want to run here -->
<testsuites>
<testsuite name="app">
<directory>tests/TestCase/</directory>
</testsuite>
</testsuites>
<!-- Setup a listener for fixtures -->
<extensions>
<bootstrap class="Cake\TestSuite\Fixture\Extension\PHPUnitExtension"/>
</extensions>
<source>
<include>
<directory suffix=".php">src/</directory>
<directory suffix=".php">plugins/*/src/</directory>
</include>
<exclude>
<file>src/Console/Installer.php</file>
</exclude>
</source>
</phpunit>
@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace App\Test\TestCase\Model\Behavior;
use App\Model\Behavior\{{BEHAVIOR_NAME}}Behavior;
use Cake\ORM\Table;
use Cake\TestSuite\TestCase;
/**
* App\Model\Behavior\{{BEHAVIOR_NAME}}Behavior Test Case
*/
class {{BEHAVIOR_NAME}}BehaviorTest extends TestCase
{
/**
* Test subject
*
* @var \App\Model\Behavior\{{BEHAVIOR_NAME}}Behavior
*/
protected {{BEHAVIOR_NAME}}Behavior ${{VARIABLE_NAME}};
/**
* Table instance
*
* @var \Cake\ORM\Table
*/
protected Table $table;
/**
* setUp method
*/
protected function setUp(): void
{
parent::setUp();
$this->table = new Table(['table' => 'test_table']);
$this->{{VARIABLE_NAME}} = new {{BEHAVIOR_NAME}}Behavior($this->table);
}
/**
* tearDown method
*/
protected function tearDown(): void
{
unset($this->{{VARIABLE_NAME}}, $this->table);
parent::tearDown();
}
/**
* Test behavior initialization
*/
public function testInitialization(): void
{
$this->assertInstanceOf({{BEHAVIOR_NAME}}Behavior::class, $this->{{VARIABLE_NAME}});
}
}
@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace App\Test\TestCase\Command;
use Cake\Console\TestSuite\ConsoleIntegrationTestTrait;
use Cake\TestSuite\TestCase;
/**
* App\Command\{{COMMAND_NAME}}Command Test Case
*/
class {{COMMAND_NAME}}CommandTest extends TestCase
{
use ConsoleIntegrationTestTrait;
/**
* setUp method
*/
protected function setUp(): void
{
parent::setUp();
$this->useCommandRunner();
}
/**
* Test execute with success
*/
public function testExecuteSuccess(): void
{
// Act
$this->exec('{{COMMAND_SLUG}}');
// Assert
$this->assertExitSuccess();
$this->assertOutputContains('Done');
}
/**
* Test execute with invalid argument
*/
public function testExecuteFailsOnMissingArgument(): void
{
// Act
$this->exec('{{COMMAND_SLUG}} --invalid-flag');
// Assert
$this->assertExitError();
}
}
@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace App\Test\TestCase\Controller\Component;
use App\Controller\Component\{{COMPONENT_NAME}}Component;
use Cake\Controller\ComponentRegistry;
use Cake\Controller\Controller;
use Cake\Http\ServerRequest;
use Cake\TestSuite\TestCase;
/**
* App\Controller\Component\{{COMPONENT_NAME}}Component Test Case
*/
class {{COMPONENT_NAME}}ComponentTest extends TestCase
{
/**
* Test subject
*
* @var \App\Controller\Component\{{COMPONENT_NAME}}Component
*/
protected {{COMPONENT_NAME}}Component ${{VARIABLE_NAME}};
/**
* setUp method
*/
protected function setUp(): void
{
parent::setUp();
$controller = new Controller(new ServerRequest());
$registry = new ComponentRegistry($controller);
$this->{{VARIABLE_NAME}} = new {{COMPONENT_NAME}}Component($registry);
}
/**
* tearDown method
*/
protected function tearDown(): void
{
unset($this->{{VARIABLE_NAME}});
parent::tearDown();
}
/**
* Test basic method
*/
public function testDoActionSuccess(): void
{
// Act
$result = $this->{{VARIABLE_NAME}}->doAction();
// Assert
$this->assertTrue($result);
}
}
@@ -0,0 +1,104 @@
<?php
declare(strict_types=1);
namespace App\Test\TestCase\Controller;
use Cake\TestSuite\IntegrationTestTrait;
use Cake\TestSuite\TestCase;
/**
* App\Controller\{{CONTROLLER_NAME}}Controller Test Case
*
* @uses \App\Controller\{{CONTROLLER_NAME}}Controller
*/
class {{CONTROLLER_NAME}}ControllerTest extends TestCase
{
use IntegrationTestTrait;
/**
* Fixtures
*
* @var list<string>
*/
protected array $fixtures = [
'app.{{MODEL_NAME}}',
];
/**
* Test index method
*/
public function testIndex(): void
{
// Act
$this->get('/{{ROUTE_PATH}}');
// Assert
$this->assertResponseOk();
$this->assertResponseContains('{{EXPECTED_TEXT}}');
}
/**
* Test view method with valid ID
*/
public function testViewValidId(): void
{
// Act
$this->get('/{{ROUTE_PATH}}/view/1');
// Assert
$this->assertResponseOk();
}
/**
* Test add method GET renders form
*/
public function testAddGet(): void
{
// Act
$this->get('/{{ROUTE_PATH}}/add');
// Assert
$this->assertResponseOk();
}
/**
* Test add method POST with valid data
*/
public function testAddPostSuccess(): void
{
// Arrange
$this->enableCsrfToken();
$this->enableSecurityToken();
$data = [
'name' => 'New Item',
'status' => 'active',
];
// Act
$this->post('/{{ROUTE_PATH}}/add', $data);
// Assert
$this->assertResponseSuccess();
$this->assertRedirect(['action' => 'index']);
$this->assertFlashMessage('The {{ENTITY_LOWER}} has been saved.');
}
/**
* Test add method POST with validation errors
*/
public function testAddPostValidationFailure(): void
{
// Arrange
$this->enableCsrfToken();
$this->enableSecurityToken();
$data = []; // Empty or invalid data
// Act
$this->post('/{{ROUTE_PATH}}/add', $data);
// Assert
$this->assertResponseOk(); // Renders form view with errors
}
}
@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace App\Test\TestCase\Model\Entity;
use App\Model\Entity\{{ENTITY_NAME}};
use Cake\TestSuite\TestCase;
/**
* App\Model\Entity\{{ENTITY_NAME}} Test Case
*/
class {{ENTITY_NAME}}Test extends TestCase
{
/**
* Test subject
*
* @var \App\Model\Entity\{{ENTITY_NAME}}
*/
protected {{ENTITY_NAME}} ${{VARIABLE_NAME}};
/**
* setUp method
*/
protected function setUp(): void
{
parent::setUp();
$this->{{VARIABLE_NAME}} = new {{ENTITY_NAME}}();
}
/**
* tearDown method
*/
protected function tearDown(): void
{
unset($this->{{VARIABLE_NAME}});
parent::tearDown();
}
/**
* Test accessible fields configuration
*/
public function testAccessibleFields(): void
{
// Arrange
$data = [
'name' => 'John Doe',
'email' => 'john@example.com',
'id' => 999, // Typically non-accessible or guarded
];
// Act
$this->{{VARIABLE_NAME}}->set($data);
// Assert
$this->assertSame('John Doe', $this->{{VARIABLE_NAME}}->name);
$this->assertSame('john@example.com', $this->{{VARIABLE_NAME}}->email);
}
/**
* Test virtual fields if present
*/
public function testVirtualProperties(): void
{
// Arrange
$this->{{VARIABLE_NAME}}->set([
'first_name' => 'John',
'last_name' => 'Doe',
]);
// Act & Assert
if ($this->{{VARIABLE_NAME}}->has('full_name')) {
$this->assertSame('John Doe', $this->{{VARIABLE_NAME}}->full_name);
} else {
$this->assertTrue(true);
}
}
}
@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace App\Test\TestCase\Form;
use App\Form\{{FORM_NAME}}Form;
use Cake\TestSuite\TestCase;
/**
* App\Form\{{FORM_NAME}}Form Test Case
*/
class {{FORM_NAME}}FormTest extends TestCase
{
/**
* Test subject
*
* @var \App\Form\{{FORM_NAME}}Form
*/
protected {{FORM_NAME}}Form ${{VARIABLE_NAME}};
/**
* setUp method
*/
protected function setUp(): void
{
parent::setUp();
$this->{{VARIABLE_NAME}} = new {{FORM_NAME}}Form();
}
/**
* tearDown method
*/
protected function tearDown(): void
{
unset($this->{{VARIABLE_NAME}});
parent::tearDown();
}
/**
* Test validationDefault with valid data
*/
public function testValidationSuccess(): void
{
// Arrange
$data = [
'email' => 'user@example.com',
'message' => 'Hello World',
];
// Act
$isValid = $this->{{VARIABLE_NAME}}->validate($data);
// Assert
$this->assertTrue($isValid);
$this->assertEmpty($this->{{VARIABLE_NAME}}->getErrors());
}
/**
* Test execute method
*/
public function testExecuteSuccess(): void
{
// Arrange
$data = [
'email' => 'user@example.com',
'message' => 'Hello World',
];
// Act
$result = $this->{{VARIABLE_NAME}}->execute($data);
// Assert
$this->assertTrue($result);
}
}
@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace App\Test\TestCase\View\Helper;
use App\View\Helper\{{HELPER_NAME}}Helper;
use Cake\TestSuite\TestCase;
use Cake\View\View;
/**
* App\View\Helper\{{HELPER_NAME}}Helper Test Case
*/
class {{HELPER_NAME}}HelperTest extends TestCase
{
/**
* Test subject
*
* @var \App\View\Helper\{{HELPER_NAME}}Helper
*/
protected {{HELPER_NAME}}Helper ${{VARIABLE_NAME}};
/**
* setUp method
*/
protected function setUp(): void
{
parent::setUp();
$view = new View();
$this->{{VARIABLE_NAME}} = new {{HELPER_NAME}}Helper($view);
}
/**
* tearDown method
*/
protected function tearDown(): void
{
unset($this->{{VARIABLE_NAME}});
parent::tearDown();
}
/**
* Test helper initialization and rendering
*/
public function testInitialization(): void
{
$this->assertInstanceOf({{HELPER_NAME}}Helper::class, $this->{{VARIABLE_NAME}});
}
}
@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace App\Test\TestCase\Mailer;
use App\Mailer\{{MAILER_NAME}}Mailer;
use Cake\TestSuite\EmailTrait;
use Cake\TestSuite\TestCase;
/**
* App\Mailer\{{MAILER_NAME}}Mailer Test Case
*/
class {{MAILER_NAME}}MailerTest extends TestCase
{
use EmailTrait;
/**
* Test subject
*
* @var \App\Mailer\{{MAILER_NAME}}Mailer
*/
protected {{MAILER_NAME}}Mailer ${{VARIABLE_NAME}};
/**
* setUp method
*/
protected function setUp(): void
{
parent::setUp();
$this->{{VARIABLE_NAME}} = new {{MAILER_NAME}}Mailer();
}
/**
* Test send welcome email
*/
public function testSendWelcomeEmail(): void
{
// Act
$this->{{VARIABLE_NAME}}->send('welcome', ['user@example.com', 'John Doe']);
// Assert
$this->assertMailSentTo('user@example.com');
$this->assertMailContainsText('John Doe');
}
}
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace App\Test\TestCase\Middleware;
use App\Middleware\{{MIDDLEWARE_NAME}}Middleware;
use Cake\Http\Response;
use Cake\Http\ServerRequest;
use Cake\TestSuite\TestCase;
use TestApp\Http\TestRequestHandler;
/**
* App\Middleware\{{MIDDLEWARE_NAME}}Middleware Test Case
*/
class {{MIDDLEWARE_NAME}}MiddlewareTest extends TestCase
{
/**
* Test process method
*/
public function testProcess(): void
{
// Arrange
$middleware = new {{MIDDLEWARE_NAME}}Middleware();
$request = new ServerRequest(['url' => '/test-path']);
$handler = new class implements \Psr\Http\Server\RequestHandlerInterface {
public function handle(\Psr\Http\Message\ServerRequestInterface $request): \Psr\Http\Message\ResponseInterface
{
return new Response();
}
};
// Act
$response = $middleware->process($request, $handler);
// Assert
$this->assertInstanceOf(\Psr\Http\Message\ResponseInterface::class, $response);
$this->assertSame(200, $response->getStatusCode());
}
}
@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace App\Test\TestCase\Service;
use App\Service\{{SERVICE_NAME}}Service;
use Cake\TestSuite\TestCase;
/**
* App\Service\{{SERVICE_NAME}}Service Test Case
*/
class {{SERVICE_NAME}}ServiceTest extends TestCase
{
/**
* Test subject
*
* @var \App\Service\{{SERVICE_NAME}}Service
*/
protected {{SERVICE_NAME}}Service ${{VARIABLE_NAME}};
/**
* setUp method
*/
protected function setUp(): void
{
parent::setUp();
// Arrange dependencies
$this->{{VARIABLE_NAME}} = new {{SERVICE_NAME}}Service();
}
/**
* tearDown method
*/
protected function tearDown(): void
{
unset($this->{{VARIABLE_NAME}});
parent::tearDown();
}
/**
* Test execution success (Happy Path)
*/
public function testExecuteSuccess(): void
{
// Arrange
$input = 'sample_input';
// Act
$result = $this->{{VARIABLE_NAME}}->execute($input);
// Assert
$this->assertNotNull($result);
}
/**
* Test execution with invalid input (Exception Path)
*/
public function testExecuteThrowsExceptionOnInvalidInput(): void
{
// Assert
$this->expectException(\InvalidArgumentException::class);
// Act
$this->{{VARIABLE_NAME}}->execute('');
}
}
@@ -0,0 +1,104 @@
<?php
declare(strict_types=1);
namespace App\Test\TestCase\Model\Table;
use App\Model\Table\{{TABLE_NAME}}Table;
use Cake\TestSuite\TestCase;
/**
* App\Model\Table\{{TABLE_NAME}}Table Test Case
*/
class {{TABLE_NAME}}TableTest extends TestCase
{
/**
* Test subject
*
* @var \App\Model\Table\{{TABLE_NAME}}Table
*/
protected {{TABLE_NAME}}Table ${{VARIABLE_NAME}};
/**
* Fixtures
*
* @var list<string>
*/
protected array $fixtures = [
'app.{{TABLE_NAME}}',
];
/**
* setUp method
*/
protected function setUp(): void
{
parent::setUp();
$config = $this->getTableLocator()->exists('{{TABLE_NAME}}') ? [] : ['className' => {{TABLE_NAME}}Table::class];
$this->{{VARIABLE_NAME}} = $this->getTableLocator()->get('{{TABLE_NAME}}', $config);
}
/**
* tearDown method
*/
protected function tearDown(): void
{
unset($this->{{VARIABLE_NAME}});
parent::tearDown();
}
/**
* Test validationDefault method with valid data
*/
public function testValidationDefaultSuccess(): void
{
// Arrange
$data = [
'name' => 'Valid Title',
'status' => 'active',
];
// Act
$entity = $this->{{VARIABLE_NAME}}->newEntity($data);
// Assert
$this->assertEmpty($entity->getErrors());
}
/**
* Test validationDefault method with missing required fields
*/
public function testValidationDefaultFailsOnRequiredFields(): void
{
// Arrange
$data = [];
// Act
$entity = $this->{{VARIABLE_NAME}}->newEntity($data);
// Assert
$this->assertNotEmpty($entity->getErrors());
$this->assertArrayHasKey('name', $entity->getErrors());
}
/**
* Test save and find
*/
public function testSaveAndFind(): void
{
// Arrange
$entity = $this->{{VARIABLE_NAME}}->newEntity([
'name' => 'Sample Record',
'status' => 'active',
]);
// Act
$result = $this->{{VARIABLE_NAME}}->save($entity);
// Assert
$this->assertNotFalse($result);
$this->assertNotNull($result->id);
$saved = $this->{{VARIABLE_NAME}}->get($result->id);
$this->assertSame('Sample Record', $saved->name);
}
}
@@ -0,0 +1,49 @@
name: CakePHP Unit Tests & Quality Gate
on:
push:
branches: [ main, master, develop ]
pull_request:
branches: [ main, master, develop ]
jobs:
unit-tests:
name: Unit Tests (PHP ${{ matrix.php }} - CakePHP)
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['8.1', '8.2', '8.3']
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup PHP & Extensions
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: mbstring, intl, dom, fileinfo, sqlite3, pdo_sqlite, pcov
coverage: pcov
tools: composer:v2
- name: Cache Composer Dependencies
uses: actions/cache@v4
with:
path: vendor
key: ${{ runner.os }}-php-${{ matrix.php }}-cakephp-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php-${{ matrix.php }}-cakephp-
- name: Install Dependencies
run: composer update --prefer-stable --prefer-dist --no-interaction --no-progress
- name: Run CakePHP Tests with Coverage
run: |
if [ -f "bin/cake" ]; then
bin/cake test app --coverage-clover coverage.xml
else
./vendor/bin/phpunit --coverage-clover coverage.xml
fi
env:
TMP: /tmp/
@@ -0,0 +1,22 @@
# GitLab CI/CD Pipeline for CakePHP Unit Testing
image: php:8.2-cli-alpine
stages:
- test
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- vendor/
unit-test:
stage: test
before_script:
- apk add --no-cache sqlite-libs icu-dev libzip-dev zip unzip git $PHPIZE_DEPS
- pecl install pcov && docker-php-ext-enable pcov
- docker-php-ext-configure intl && docker-php-ext-install intl pdo pdo_sqlite zip
- curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
- composer install --prefer-dist --no-progress --no-interaction
script:
- if [ -f "bin/cake" ]; then bin/cake test app --coverage-text; else ./vendor/bin/phpunit --coverage-text; fi
coverage: '/^\s*Lines:\s*\d+.\d+\%/'
+11 -4
View File
@@ -1,7 +1,7 @@
services: services:
test-runner: test-runner:
image: php:8.3-cli-alpine image: php:8.3-cli-alpine
container_name: laravel_unit_tester container_name: php_unit_tester
working_dir: /var/www/html working_dir: /var/www/html
volumes: volumes:
- ./:/var/www/html - ./:/var/www/html
@@ -15,7 +15,14 @@ services:
- MAIL_MAILER=array - MAIL_MAILER=array
command: > command: >
sh -c " sh -c "
apk add --no-cache sqlite-libs libzip-dev zip unzip git && apk add --no-cache sqlite-libs libzip-dev icu-dev zip unzip git &&
docker-php-ext-install pdo pdo_sqlite zip && docker-php-ext-configure intl &&
php artisan test docker-php-ext-install pdo pdo_sqlite zip intl &&
if [ -f 'bin/cake' ]; then
bin/cake test app;
elif [ -f 'artisan' ]; then
php artisan test;
else
vendor/bin/phpunit;
fi
" "
+79 -14
View File
@@ -1,8 +1,9 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ============================================================================== # ==============================================================================
# Smart Test Runner for Laravel # Smart Test Runner for Laravel & CakePHP
# Tự động nhận diện môi trường: Laravel Sail -> Docker Compose -> Local PHP # Tự động nhận diện Framework (Laravel / CakePHP) và Môi trường thực thi:
# Laravel Sail -> Docker Compose -> Local PHP CLI / bin/cake / phpunit
# ============================================================================== # ==============================================================================
set -e set -e
@@ -10,10 +11,12 @@ set -e
# Màu sắc thông báo # Màu sắc thông báo
GREEN='\033[0;32m' GREEN='\033[0;32m'
YELLOW='\033[1;33m' YELLOW='\033[1;33m'
BLUE='\033[0;34m'
RED='\033[0;31m' RED='\033[0;31m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color NC='\033[0m' # No Color
echo -e "${YELLOW}🔍 Đang phát hiện môi trường chạy test...${NC}" echo -e "${YELLOW}🔍 Đang phát hiện Framework và môi trường chạy test...${NC}"
# Hàm chạy test # Hàm chạy test
run_test() { run_test() {
@@ -23,19 +26,74 @@ run_test() {
$runner "$@" $runner "$@"
} }
# 1. Kiểm tra Laravel Sail # 1. Xác định Framework: CakePHP hay Laravel
if [ -f "./vendor/bin/sail" ] && docker info >/dev/null 2>&1; then FRAMEWORK="generic"
if [ -f "bin/cake" ] || ( [ -f "composer.json" ] && grep -q '"cakephp/cakephp"' composer.json ); then
FRAMEWORK="cakephp"
echo -e "${CYAN}✓ Phát hiện Framework: CakePHP${NC}"
elif [ -f "artisan" ] || ( [ -f "composer.json" ] && grep -q '"laravel/framework"' composer.json ); then
FRAMEWORK="laravel"
echo -e "${CYAN}✓ Phát hiện Framework: Laravel${NC}"
else
echo -e "${BLUE}ℹ Framework: PHP Generic (PHPUnit)${NC}"
fi
# 2. Xử lý môi trường CakePHP
if [ "$FRAMEWORK" = "cakephp" ]; then
# A. Docker Compose
if [ -f "docker-compose.yml" ] || [ -f "compose.yaml" ]; then
if docker info >/dev/null 2>&1; then
SERVICES=("app" "web" "php" "php-fpm" "backend" "workspace")
for svc in "${SERVICES[@]}"; do
if docker compose ps --services --filter "status=running" 2>/dev/null | grep -q "^${svc}$"; then
echo -e "${GREEN}✓ Phát hiện Docker Compose với service: '${svc}'${NC}"
if docker compose exec -T ${svc} test -f "bin/cake" 2>/dev/null; then
run_test "docker compose exec -T ${svc} bin/cake test app" "$@"
exit 0
else
run_test "docker compose exec -T ${svc} vendor/bin/phpunit" "$@"
exit 0
fi
fi
done
fi
fi
# B. Local CakePHP CLI
if [ -f "bin/cake" ] && command -v php >/dev/null 2>&1; then
if ! php -m | grep -q -i intl; then
echo -e "${YELLOW}⚠️ Cảnh báo: PHP CLI hiện tại chưa bật extension 'intl'. CakePHP yêu cầu extension này để chạy!${NC}"
fi
if ! php -m | grep -q -i sqlite; then
echo -e "${YELLOW}⚠️ Cảnh báo: PHP CLI hiện tại chưa bật extension 'pdo_sqlite'. Test dùng In-memory SQLite có thể bị lỗi!${NC}"
fi
echo -e "${GREEN}✓ Sử dụng CakePHP CLI cục bộ (bin/cake)${NC}"
run_test "bin/cake test app" "$@"
exit 0
fi
# C. Local PHPUnit fallback
if [ -f "vendor/bin/phpunit" ] && command -v php >/dev/null 2>&1; then
echo -e "${GREEN}✓ Sử dụng vendor/bin/phpunit cục bộ${NC}"
run_test "./vendor/bin/phpunit" "$@"
exit 0
fi
fi
# 3. Xử lý môi trường Laravel
if [ "$FRAMEWORK" = "laravel" ]; then
# A. Laravel Sail
if [ -f "./vendor/bin/sail" ] && docker info >/dev/null 2>&1; then
if docker ps --format '{{.Names}}' | grep -q "laravel.test"; then if docker ps --format '{{.Names}}' | grep -q "laravel.test"; then
echo -e "${GREEN}✓ Phát hiện Laravel Sail đang hoạt động.${NC}" echo -e "${GREEN}✓ Phát hiện Laravel Sail đang hoạt động.${NC}"
run_test "./vendor/bin/sail test" "$@" run_test "./vendor/bin/sail test" "$@"
exit 0 exit 0
fi fi
fi fi
# 2. Kiểm tra Docker Compose tự cấu hình # B. Docker Compose
if [ -f "docker-compose.yml" ] || [ -f "compose.yaml" ]; then if [ -f "docker-compose.yml" ] || [ -f "compose.yaml" ]; then
if docker info >/dev/null 2>&1; then if docker info >/dev/null 2>&1; then
# Tìm container service chạy php
SERVICES=("app" "php" "php-fpm" "backend" "workspace" "web") SERVICES=("app" "php" "php-fpm" "backend" "workspace" "web")
for svc in "${SERVICES[@]}"; do for svc in "${SERVICES[@]}"; do
if docker compose ps --services --filter "status=running" 2>/dev/null | grep -q "^${svc}$"; then if docker compose ps --services --filter "status=running" 2>/dev/null | grep -q "^${svc}$"; then
@@ -45,20 +103,27 @@ if [ -f "docker-compose.yml" ] || [ -f "compose.yaml" ]; then
fi fi
done done
fi fi
fi fi
# 3. Fallback về PHP CLI cục bộ # C. Fallback về PHP CLI cục bộ
if command -v php >/dev/null 2>&1; then if command -v php >/dev/null 2>&1; then
echo -e "${YELLOW}ℹ Không tìm thấy container Docker đang chạy. Chuyển về PHP CLI máy local.${NC}" echo -e "${YELLOW}ℹ Không tìm thấy container Docker đang chạy. Chuyển về PHP CLI máy local.${NC}"
# Kiểm tra extension pdo_sqlite
if ! php -m | grep -q -i sqlite; then if ! php -m | grep -q -i sqlite; then
echo -e "${RED}⚠️ Cảnh báo: PHP CLI hiện tại chưa bật extension pdo_sqlite. Unit test dùng In-memory SQLite có thể bị lỗi!${NC}" echo -e "${RED}⚠️ Cảnh báo: PHP CLI hiện tại chưa bật extension pdo_sqlite. Unit test dùng In-memory SQLite có thể bị lỗi!${NC}"
fi fi
run_test "php artisan test" "$@" run_test "php artisan test" "$@"
exit 0 exit 0
fi
fi fi
echo -e "${RED}❌ Không tìm thấy môi trường phù hợp (Sail, Docker Compose hoặc PHP CLI) để chạy test!${NC}" # 4. Fallback chung cho mọi dự án PHP
if [ -f "vendor/bin/phpunit" ]; then
echo -e "${YELLOW}ℹ Chạy qua vendor/bin/phpunit cục bộ...${NC}"
run_test "./vendor/bin/phpunit" "$@"
exit 0
fi
echo -e "${RED}❌ Không tìm thấy môi trường phù hợp (Docker Compose, Sail, bin/cake, hoặc PHP CLI) để chạy test!${NC}"
exit 1 exit 1
+41 -18
View File
@@ -1,8 +1,8 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ============================================================================== # ==============================================================================
# Smart Dependency Installer for Laravel Testing # Smart Dependency Installer for Laravel & CakePHP Testing
# Tự động phát hiện gói còn thiếu và cài đặt qua Docker (Sail / Compose) hoặc Local Composer # Tự động phát hiện gói còn thiếu và cài đặt qua Docker hoặc Local Composer
# ============================================================================== # ==============================================================================
set -e set -e
@@ -11,10 +11,11 @@ GREEN='\033[0;32m'
YELLOW='\033[1;33m' YELLOW='\033[1;33m'
BLUE='\033[0;34m' BLUE='\033[0;34m'
RED='\033[0;31m' RED='\033[0;31m'
CYAN='\033[0;36m'
NC='\033[0m' NC='\033[0m'
echo -e "${BLUE}===================================================================${NC}" echo -e "${BLUE}===================================================================${NC}"
echo -e "${BLUE} Tự Động Cài Đặt Thư Viện Cần Thiết Cho Unit Test Laravel${NC}" echo -e "${CYAN} Tự Động Cài Đặt Thư Viện Cần Thiết Cho Unit Test (Laravel & CakePHP)${NC}"
echo -e "${BLUE}===================================================================${NC}" echo -e "${BLUE}===================================================================${NC}"
if [ ! -f "composer.json" ]; then if [ ! -f "composer.json" ]; then
@@ -22,14 +23,24 @@ if [ ! -f "composer.json" ]; then
exit 1 exit 1
fi fi
# 1. Xác định môi trường thực thi lệnh composer # 1. Xác định Framework
FRAMEWORK="generic"
if [ -f "bin/cake" ] || grep -q '"cakephp/cakephp"' composer.json; then
FRAMEWORK="cakephp"
echo -e "${GREEN}✓ Dự án: CakePHP${NC}"
elif [ -f "artisan" ] || grep -q '"laravel/framework"' composer.json; then
FRAMEWORK="laravel"
echo -e "${GREEN}✓ Dự án: Laravel${NC}"
fi
# 2. Xác định môi trường thực thi lệnh composer
COMPOSER_CMD="" COMPOSER_CMD=""
if [ -f "./vendor/bin/sail" ] && docker info >/dev/null 2>&1 && docker ps --format '{{.Names}}' | grep -q "laravel.test"; then if [ -f "./vendor/bin/sail" ] && docker info >/dev/null 2>&1 && docker ps --format '{{.Names}}' | grep -q "laravel.test"; then
echo -e "${GREEN}✓ Môi trường: Laravel Sail đang hoạt động.${NC}" echo -e "${GREEN}✓ Môi trường: Laravel Sail đang hoạt động.${NC}"
COMPOSER_CMD="./vendor/bin/sail composer" COMPOSER_CMD="./vendor/bin/sail composer"
elif [ -f "docker-compose.yml" ] || [ -f "compose.yaml" ]; then elif [ -f "docker-compose.yml" ] || [ -f "compose.yaml" ]; then
SERVICES=("app" "php" "php-fpm" "backend" "workspace" "web") SERVICES=("app" "web" "php" "php-fpm" "backend" "workspace")
for svc in "${SERVICES[@]}"; do for svc in "${SERVICES[@]}"; do
if docker compose ps --services --filter "status=running" 2>/dev/null | grep -q "^${svc}$"; then if docker compose ps --services --filter "status=running" 2>/dev/null | grep -q "^${svc}$"; then
echo -e "${GREEN}✓ Môi trường: Docker Compose (service: ${svc}).${NC}" echo -e "${GREEN}✓ Môi trường: Docker Compose (service: ${svc}).${NC}"
@@ -43,17 +54,27 @@ if [ -z "$COMPOSER_CMD" ]; then
if command -v composer >/dev/null 2>&1; then if command -v composer >/dev/null 2>&1; then
echo -e "${YELLOW}ℹ Môi trường: Composer trên máy cục bộ (Local).${NC}" echo -e "${YELLOW}ℹ Môi trường: Composer trên máy cục bộ (Local).${NC}"
COMPOSER_CMD="composer" COMPOSER_CMD="composer"
elif [ -f "composer.phar" ]; then
echo -e "${YELLOW}ℹ Môi trường: Sử dụng composer.phar cục bộ.${NC}"
COMPOSER_CMD="php composer.phar"
else else
echo -e "${RED}❌ Lỗi: Không tìm thấy Composer trên máy hoặc trong container Docker!${NC}" echo -e "${RED}❌ Lỗi: Không tìm thấy Composer trên máy hoặc trong container Docker!${NC}"
exit 1 exit 1
fi fi
fi fi
# 2. Phân tích composer.json xem thiếu gói nào # 3. Phân tích composer.json xem thiếu gói nào
echo -e "\n${YELLOW}🔍 Đang kiểm tra dependencies hiện có...${NC}" echo -e "\n${YELLOW}🔍 Đang kiểm tra dependencies hiện có...${NC}"
PACKAGES_TO_INSTALL=() PACKAGES_TO_INSTALL=()
# Kiểm tra PHPUnit nếu là CakePHP
if [ "$FRAMEWORK" = "cakephp" ]; then
if ! grep -q '"phpunit/phpunit"' composer.json; then
PACKAGES_TO_INSTALL+=("phpunit/phpunit")
fi
fi
# Kiểm tra Mockery # Kiểm tra Mockery
if ! grep -q '"mockery/mockery"' composer.json; then if ! grep -q '"mockery/mockery"' composer.json; then
PACKAGES_TO_INSTALL+=("mockery/mockery") PACKAGES_TO_INSTALL+=("mockery/mockery")
@@ -64,29 +85,31 @@ if ! grep -q '"fakerphp/faker"' composer.json; then
PACKAGES_TO_INSTALL+=("fakerphp/faker") PACKAGES_TO_INSTALL+=("fakerphp/faker")
fi fi
# Kiểm tra Test Runner (Pest vs PHPUnit) if [ "$FRAMEWORK" = "laravel" ]; then
IS_PEST=false # Kiểm tra Test Runner (Pest vs PHPUnit)
if grep -q '"pestphp/pest"' composer.json; then IS_PEST=false
if grep -q '"pestphp/pest"' composer.json; then
IS_PEST=true IS_PEST=true
fi fi
if [ "$IS_PEST" = true ]; then if [ "$IS_PEST" = true ]; then
if ! grep -q '"pestphp/pest-plugin-laravel"' composer.json; then if ! grep -q '"pestphp/pest-plugin-laravel"' composer.json; then
PACKAGES_TO_INSTALL+=("pestphp/pest-plugin-laravel") PACKAGES_TO_INSTALL+=("pestphp/pest-plugin-laravel")
fi fi
fi fi
# Kiểm tra công cụ chạy song song (Paratest) # Kiểm tra công cụ chạy song song (Paratest)
if ! grep -q '"brianium/paratest"' composer.json; then if ! grep -q '"brianium/paratest"' composer.json; then
PACKAGES_TO_INSTALL+=("brianium/paratest") PACKAGES_TO_INSTALL+=("brianium/paratest")
fi fi
# Kiểm tra Collision # Kiểm tra Collision
if ! grep -q '"nunomaduro/collision"' composer.json; then if ! grep -q '"nunomaduro/collision"' composer.json; then
PACKAGES_TO_INSTALL+=("nunomaduro/collision") PACKAGES_TO_INSTALL+=("nunomaduro/collision")
fi
fi fi
# 3. Tiến hành cài đặt # 4. Tiến hành cài đặt
if [ ${#PACKAGES_TO_INSTALL[@]} -eq 0 ]; then if [ ${#PACKAGES_TO_INSTALL[@]} -eq 0 ]; then
echo -e "${GREEN}🎉 Tuyệt vời! Dự án đã có đầy đủ các thư viện kiểm thử cần thiết!${NC}" echo -e "${GREEN}🎉 Tuyệt vời! Dự án đã có đầy đủ các thư viện kiểm thử cần thiết!${NC}"
exit 0 exit 0