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:
@@ -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...
|
||||
```
|
||||
```
|
||||
@@ -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!**
|
||||
```
|
||||
@@ -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
|
||||
```
|
||||
@@ -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.
|
||||
|
||||
@@ -9,38 +9,35 @@
|
||||
## 📑 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)
|
||||
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)
|
||||
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ỳ)
|
||||
5. [Quy Trình 5 Bước Sinh & Chạy Unit Test](#5-quy-trình-5-bước-sinh--chạy-unit-test)
|
||||
- [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)
|
||||
- [Bước 2: Sinh Unit Test Tự Động (`/laravel-unit-test`)](#bước-2-sinh-unit-test-tự-động-laravel-unit-test)
|
||||
- [Bước 3: Chạy Test Thông Minh (`test-runner.sh`)](#bước-3-chạy-test-thông-minh-test-runnersh)
|
||||
- [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)
|
||||
- [Bước 5: Sinh Mã Giả Lập Nhanh (`/laravel-mock-helper`)](#bước-5-sinh-mã-giả-lập-nhanh-laravel-mock-helper)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
9. [Ma Trận Tương Thích & Bản Quyền](#9-ma-trận-tương-thích--bản-quyền)
|
||||
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. [🔴 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)
|
||||
- [4.1. Kiến Trúc & 13 Tầng Hỗ Trợ](#41-kiến-trúc--13-tầng-hỗ-trợ-laravel)
|
||||
- [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)
|
||||
- [4.3. Bảng Tra Cứu Lệnh Laravel (Cheat Sheet)](#43-bảng-tra-cứu-lệnh-laravel-cheat-sheet)
|
||||
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)
|
||||
- [5.1. Kiến Trúc & 10 Tầng Hỗ Trợ](#51-kiến-trúc--10-tầng-hỗ-trợ-cakephp)
|
||||
- [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)
|
||||
- [5.3. Bảng Tra Cứu Lệnh CakePHP (Cheat Sheet)](#53-bảng-tra-cứu-lệnh-cakephp-cheat-sheet)
|
||||
6. [Cập Nhật Bộ Công Cụ](#6-cập-nhật-bộ-công-cụ)
|
||||
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)
|
||||
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. **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ộ**.
|
||||
- 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.
|
||||
- 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 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.
|
||||
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.
|
||||
- Không over-mocking: giữ nguyên thực thể DTO, Value Object, Collection thật.
|
||||
- Chặn đứng lỗi anti-pattern mock chuỗi Eloquent Query Builder dễ vỡ.
|
||||
3. **Bao Phủ Trọn Vẹn 13 Tầng Kiến Trúc Laravel**:
|
||||
- Services, Actions, Controllers, FormRequests, Custom Rules, Repositories, Query Builders, Models, Jobs, Events, Listeners, Middlewares, Policies, Notifications, Mailables, Artisan Commands, View Components, Observers, Exceptions.
|
||||
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**:
|
||||
- Đả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, Entity thật.
|
||||
3. **Chế Độ Autopilot Khép Kín (`--all --run`)**:
|
||||
- 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%.
|
||||
4. **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).
|
||||
- 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%.
|
||||
|
||||
---
|
||||
@@ -51,303 +48,407 @@
|
||||
agent-unit-testing/
|
||||
├── install.sh # Script cài đặt chính (hỗ trợ Toàn cục & Theo dự án)
|
||||
├── .claude/
|
||||
│ └── skills/ # Bộ 4 Skills tích hợp cho Claude Code
|
||||
│ ├── laravel-test-setup/ # Kiểm tra môi trường & tự cài đặt dependencies
|
||||
│ ├── laravel-unit-test/ # Sinh Unit Test tự động (hỗ trợ Autopilot --all --run)
|
||||
│ ├── laravel-test-review/ # Chấm điểm chất lượng test theo thang 100 & bắt lỗi anti-patterns
|
||||
│ └── skills/ # Bộ Skills tích hợp cho Claude Code
|
||||
│ │── # Skills Dành Cho CakePHP:
|
||||
│ ├── cakephp-unit-test/ # Sinh Test tự động cho CakePHP (Table, Controller, Service...)
|
||||
│ ├── 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
|
||||
│
|
||||
├── docs/ # 11 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
|
||||
│ ├── 01-overview-and-principles.md # Triết lý Unit Test: AAA, cách ly, ranh giới Feature test
|
||||
│ ├── 02-test-frameworks.md # So sánh & cú pháp PHPUnit 10/11 vs Pest PHP v2/v3
|
||||
│ ├── 03-mocking-strategies.md # Toàn tập Mockery vs Laravel Fakes vs Static Facades
|
||||
│ ├── 04-naming-and-structure.md # Quy chuẩn đặt tên file, namespace, test method
|
||||
│ ├── 05-anti-patterns.md # 12 lỗi cấm kỵ khi viết Unit Test Laravel
|
||||
│ ├── 06-future-php84-laravel-latest.md # Hướng dẫn chuyên sâu PHP 8.4+ & Laravel 12/13
|
||||
│ ├── 07-troubleshooting-and-debugging.md # Cẩm nang sửa 10 lỗi thường gặp nhất khi chạy test
|
||||
│ ├── 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
|
||||
├── docs/ # Cẩm Nang Kiến Thức & Quy Chuẩn Cốt Lõi
|
||||
│ ├── cakephp/ # Cẩm nang chuyên sâu CakePHP (Testing Guide & 10 Components)
|
||||
│ ├── components/ # Cẩm nang 13 tầng component Laravel
|
||||
│ ├── 00-environment-and-setup.md # Setup dependencies, SQLite in-memory & Docker
|
||||
│ ├── 01-overview-and-principles.md # Triết lý Unit Test: AAA, cách ly
|
||||
│ ├── 02-test-frameworks.md # So sánh PHPUnit vs Pest PHP
|
||||
│ ├── 03-mocking-strategies.md # Toàn tập Mockery vs Fakes
|
||||
│ ├── 05-anti-patterns.md # 12 lỗi cấm kỵ khi viết Unit Test
|
||||
│ └── 10-sonarqube-100-percent-coverage.md # Chiến lược đạt 100% Coverage trên SonarQube
|
||||
│
|
||||
├── templates/ # Templates Mẫu Chuẩn
|
||||
│ ├── configs/ # phpunit.xml.dist, .env.testing, Pest.php
|
||||
│ ├── docker/ # Smart test-runner.sh & docker-compose.test.yml
|
||||
│ ├── ci/ # GitHub Actions & GitLab CI quality gates
|
||||
│ ├── phpunit/ # 8 Component templates + ArchitectureTest
|
||||
│ └── pest/ # 8 Component templates + ArchitectureTest
|
||||
│ ├── cakephp/ # Templates PHPUnit & Config cho CakePHP
|
||||
│ │ ├── phpunit/ # 10 Component templates (Table, Entity, Controller, Command...)
|
||||
│ │ └── configs/ # phpunit.xml.dist & app_local.test.php (:memory:)
|
||||
│ ├── phpunit/ # Templates PHPUnit cho Laravel (8 component templates)
|
||||
│ ├── 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á
|
||||
│ ├── 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
|
||||
└── 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
|
||||
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
|
||||
```
|
||||
|
||||
*(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:
|
||||
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/`.
|
||||
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):
|
||||
### ⚡ Cách 2: Cài Đặt Toàn Cục Cho Máy Tính (Global Mode)
|
||||
Nếu muốn cài 1 lần duy nhất để dùng cho mọi dự án trên máy:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://git.smartgift.io.vn/Smartgift-AI/agent-unit-testing/raw/branch/main/install.sh | bash -s -- --global
|
||||
```
|
||||
|
||||
🎯 **Lợi ích:**
|
||||
- Tự động cài đặt 4 Skills vào `~/.claude/skills/`.
|
||||
- Đă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`.
|
||||
|
||||
🎯 **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`.
|
||||
- Đăng ký sẵn các lệnh tiện ích trong Terminal: `laravel-test-init`, `cakephp-test-init`, `sg-test-init`.
|
||||
- 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.
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
┌────────────────┐ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐
|
||||
│ 1. CÀI ĐẶT │ ──> │ 2. SETUP ENV │ ──> │ 3. AUTOPILOT │ ──> │ 4. NGHIỆM THU │
|
||||
│ curl | bash │ │ test-setup │ │ unit-test --all│ │ Coverage > 80% │
|
||||
│ 1. CÀI ĐẶT │ ──> │ 2. SETUP ENV │ ──> │ 3. SINH TEST │ ──> │ 4. CHẠY & REVIEW│
|
||||
│ curl | bash │ │ laravel-setup │ │ laravel-test │ │ ./test-runner │
|
||||
└────────────────┘ └────────────────┘ └────────────────┘ └────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🔹 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:
|
||||
#### 🔹 Bước 1: Khởi động Claude Code & Setup môi trường
|
||||
Đứng tại thư mục Laravel và gõ:
|
||||
```bash
|
||||
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
|
||||
/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)
|
||||
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:
|
||||
##### 🏆 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:
|
||||
```text
|
||||
# 1. Viết toàn bộ tầng 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/
|
||||
|
||||
# 3. Viết tiếp FormRequests & Custom Rules:
|
||||
/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 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:
|
||||
##### 🌟 Cách Dành Cho Dự Án Vừa & Nhỏ: Autopilot Khép Kín
|
||||
```text
|
||||
/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
|
||||
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:
|
||||
```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:*
|
||||
#### 🔹 Bước 3: Chạy Test Ngoài Terminal & Đo Độ Phủ SonarQube
|
||||
Quay ra terminal và chạy:
|
||||
```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
|
||||
```
|
||||
|
||||
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
|
||||
/laravel-test-review tests/Unit/Services/PaymentServiceTest.php
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🔹 Giai đoạn 5: Các chế độ chạy linh hoạt khác
|
||||
- **Chỉ viết cho 1 class & test luôn**:
|
||||
#### 🔹 Bước 5: Sinh Nhanh Mã Mock Cho Interface / Service
|
||||
```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
|
||||
/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
|
||||
/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
|
||||
/laravel-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
|
||||
/cakephp-unit-test --untested
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
Đứ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
|
||||
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)
|
||||
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.
|
||||
## 7. Giải Đáp Thắc Mắc & Xử Lý Lỗi (FAQ)
|
||||
|
||||
---
|
||||
### 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)
|
||||
|
||||
### 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`:
|
||||
### Q2: Cài vào thư mục mới/trống có được không?
|
||||
👉 **Trả lời**: Được! Thêm cờ `--force`:
|
||||
```bash
|
||||
install.sh --force
|
||||
# Hoặc:
|
||||
cakephp-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`)?
|
||||
👉 **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.
|
||||
### Q3: Chạy test có làm mất hoặc thay đổi dữ liệu database dev khô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%?
|
||||
👉 **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)
|
||||
|
||||
| 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
|
||||
## 8. Ma Trận Tương Thích & Bản Quyền
|
||||
|
||||
| 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) |
|
||||
| **Laravel** | 8.x, 9.x, 10.x, 11.x, **12.x & 13.x (Kiến trúc hiện đại)** |
|
||||
| **PHPUnit** | 9.x (DocBlock fallback), 10.x, **11.x & 12.x** (PHP 8 Attributes `#[Test]`) |
|
||||
| **PHP** | 7.4, 8.0, 8.1, 8.2, 8.3, **8.4+** (Property Hooks, Asymmetric Visibility) |
|
||||
| **CakePHP** | **3.x, 4.x, 5.x+ (Toàn bộ kiến trúc Table, Entity, Controller, Command, Mailer...)** |
|
||||
| **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) |
|
||||
| **Mocking** | Mockery, PHPUnit Test Doubles, Laravel Built-in Fakes, `Concurrency::fake()`, `Context::fake()` |
|
||||
| **Database** | SQLite In-Memory (`:memory:`), MySQL Test DB |
|
||||
| **Mocking** | CakePHP Fixtures, PHPUnit Test Doubles, Mockery, Laravel Built-in Fakes |
|
||||
| **Database** | SQLite In-Memory (`:memory:`), MySQL Test DB, PostgreSQL Test DB |
|
||||
| **Hệ Điều Hành** | macOS, Linux, WSL2 (Windows), Docker, CI/CD Environments |
|
||||
|
||||
### Giấy Phép (License)
|
||||
|
||||
@@ -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
|
||||
```
|
||||
@@ -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`).
|
||||
+114
-46
@@ -1,13 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ==============================================================================
|
||||
# SMARTGIFT AI - LARAVEL 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.
|
||||
# SMARTGIFT AI - MULTI-FRAMEWORK UNIT TESTING TOOLKIT INSTALLER
|
||||
# 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
|
||||
# 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):
|
||||
# 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'
|
||||
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"
|
||||
REPO_URL="${LARAVEL_TEST_REPO:-$DEFAULT_REPO}"
|
||||
REPO_URL="${SG_TEST_REPO:-${LARAVEL_TEST_REPO:-$DEFAULT_REPO}}"
|
||||
|
||||
TARGET_DIR=""
|
||||
GLOBAL_MODE=false
|
||||
@@ -41,10 +40,10 @@ for arg in "$@"; do
|
||||
;;
|
||||
--help|-h)
|
||||
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 "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 " 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}"
|
||||
@@ -68,7 +67,7 @@ done
|
||||
TARGET_DIR="${TARGET_DIR:-.}"
|
||||
|
||||
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}"
|
||||
|
||||
# 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}"
|
||||
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
|
||||
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"
|
||||
exit 1
|
||||
fi
|
||||
@@ -119,16 +114,18 @@ if [ "$GLOBAL_MODE" = true ]; then
|
||||
mkdir -p "$HOME/.claude/skills"
|
||||
cp -R "$PACKAGE_ROOT/.claude/skills/"* "$HOME/.claude/skills/"
|
||||
|
||||
mkdir -p "$HOME/.claude/laravel-testing-docs/templates"
|
||||
cp -R "$PACKAGE_ROOT/docs/"* "$HOME/.claude/laravel-testing-docs/"
|
||||
cp -R "$PACKAGE_ROOT/rules/"* "$HOME/.claude/laravel-testing-docs/"
|
||||
cp -R "$PACKAGE_ROOT/templates/"* "$HOME/.claude/laravel-testing-docs/templates/"
|
||||
mkdir -p "$HOME/.claude/testing-docs/templates"
|
||||
cp -R "$PACKAGE_ROOT/docs/"* "$HOME/.claude/testing-docs/"
|
||||
cp -R "$PACKAGE_ROOT/rules/"* "$HOME/.claude/testing-docs/"
|
||||
cp -R "$PACKAGE_ROOT/templates/"* "$HOME/.claude/testing-docs/templates/"
|
||||
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
|
||||
# Lệnh khởi tạo test runner
|
||||
cat << 'EOF' > "$HOME/.local/bin/sg-test-init"
|
||||
#!/usr/bin/env bash
|
||||
TARGET_DIR="${1:-.}"
|
||||
SOURCE_DIR="$HOME/.claude/laravel-testing-docs"
|
||||
SOURCE_DIR="$HOME/.claude/testing-docs"
|
||||
|
||||
if [ ! -d "$TARGET_DIR" ]; then
|
||||
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"
|
||||
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
|
||||
chmod +x "$HOME/.local/bin/sg-test-init"
|
||||
cp "$HOME/.local/bin/sg-test-init" "$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 "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 " • ${CYAN}/laravel-unit-test --all --run${NC}"
|
||||
echo -e " • CakePHP: ${CYAN}/cakephp-test-setup --install${NC} | ${CYAN}/cakephp-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
|
||||
fi
|
||||
|
||||
@@ -171,13 +195,20 @@ if [ ! -d "$TARGET_DIR" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$TARGET_DIR/artisan" ] && [ ! -f "$TARGET_DIR/composer.json" ]; then
|
||||
# Kiểm tra xem thư mục có trống hoặc là thư mục mới không
|
||||
# Phát hiện framework
|
||||
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 ' ')
|
||||
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}"
|
||||
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 " ${CYAN}bash <(curl -fsSL <URL>/install.sh) --force${NC}"
|
||||
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
|
||||
mkdir -p "$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/
|
||||
mkdir -p "$TARGET_DIR/.claude/testing-docs"
|
||||
@@ -206,20 +237,21 @@ cp "$PACKAGE_ROOT/templates/setup-dependencies.sh" "$TARGET_DIR/setup-test-deps.
|
||||
chmod +x "$TARGET_DIR/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
|
||||
PHPUNIT_XML="$TARGET_DIR/phpunit.xml"
|
||||
PHPUNIT_DIST="$TARGET_DIR/phpunit.xml.dist"
|
||||
TARGET_XML=""
|
||||
if [ -f "$PHPUNIT_XML" ]; then
|
||||
TARGET_XML="$PHPUNIT_XML"
|
||||
elif [ -f "$PHPUNIT_DIST" ]; then
|
||||
TARGET_XML="$PHPUNIT_DIST"
|
||||
fi
|
||||
# D. Cấu hình môi trường an toàn SQLite In-Memory
|
||||
if [ "$FRAMEWORK" = "laravel" ]; then
|
||||
PHPUNIT_XML="$TARGET_DIR/phpunit.xml"
|
||||
PHPUNIT_DIST="$TARGET_DIR/phpunit.xml.dist"
|
||||
TARGET_XML=""
|
||||
if [ -f "$PHPUNIT_XML" ]; then
|
||||
TARGET_XML="$PHPUNIT_XML"
|
||||
elif [ -f "$PHPUNIT_DIST" ]; then
|
||||
TARGET_XML="$PHPUNIT_DIST"
|
||||
fi
|
||||
|
||||
if [ -n "$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
|
||||
sed -i.bak '/<php>/a\
|
||||
if [ -n "$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
|
||||
sed -i.bak '/<php>/a\
|
||||
<env name="DB_CONNECTION" value="sqlite"/>\
|
||||
<env name="DB_DATABASE" value=":memory:"/>\
|
||||
<env name="BCRYPT_ROUNDS" value="4"/>\
|
||||
@@ -227,11 +259,28 @@ if [ -n "$TARGET_XML" ]; then
|
||||
<env name="QUEUE_CONNECTION" value="sync"/>\
|
||||
<env name="MAIL_MAILER" value="array"/>
|
||||
' "$TARGET_XML" 2>/dev/null || true
|
||||
rm -f "$TARGET_XML.bak" 2>/dev/null || true
|
||||
echo -e " ${GREEN}✓${NC} Đã cấu hình SQLite in-memory (:memory:) an toàn vào phpunit.xml"
|
||||
rm -f "$TARGET_XML.bak" 2>/dev/null || true
|
||||
echo -e " ${GREEN}✓${NC} Đã cấu hình SQLite in-memory (:memory:) an toàn vào phpunit.xml"
|
||||
fi
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} Cấu hình SQLite in-memory trong phpunit.xml đã chuẩn xác"
|
||||
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} Cấu hình SQLite in-memory trong phpunit.xml đã chuẩn xác"
|
||||
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
|
||||
|
||||
@@ -239,7 +288,11 @@ fi
|
||||
if [ -d "$TARGET_DIR/.git" ]; then
|
||||
mkdir -p "$TARGET_DIR/.github/workflows"
|
||||
if [ ! -f "$TARGET_DIR/.github/workflows/unit-tests.yml" ]; then
|
||||
cp "$PACKAGE_ROOT/templates/ci/github-actions.yml.template" "$TARGET_DIR/.github/workflows/unit-tests.yml"
|
||||
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"
|
||||
fi
|
||||
echo -e " ${GREEN}✓${NC} Đã tạo CI/CD Quality Gate (.github/workflows/unit-tests.yml)"
|
||||
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 "1. Mở Claude Code tại source code này:"
|
||||
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}"
|
||||
echo -e "3. Hoặc chạy test trực tiếp từ terminal:"
|
||||
|
||||
if [ "$FRAMEWORK" = "cakephp" ]; then
|
||||
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}"
|
||||
|
||||
@@ -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. **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`.
|
||||
3. **Độ Phủ Biên & Nhánh Toàn Diện (Exhaustive & SonarQube Branch Coverage)**:
|
||||
- **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.
|
||||
- **Đặ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ù 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)`.
|
||||
- **Bao phủ 100% mọi nhánh rẽ SonarQube**:
|
||||
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.
|
||||
- **Đặc thù Laravel**:
|
||||
- *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ụ.
|
||||
- *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 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.
|
||||
- Không bao giờ chỉ viết Happy Path. Bắt buộc quét sạch:
|
||||
- Happy Path (Đầu vào chuẩn).
|
||||
- Negative Path (Đầu vào không hợp lệ / Validation lỗi).
|
||||
- Boundary Cases (Min, Max, Zero, Null, Empty Array).
|
||||
- Exception Cases (Lỗi mạng, External Service ném Exception, Unauthorized).
|
||||
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`.
|
||||
4. **Tự Động Thích Ứng Framework & Phiên Bản**:
|
||||
- **PHP 8.4+**: Hỗ trợ Property Hooks và Asymmetric Visibility.
|
||||
- **PHPUnit 10/11/12**: Sử dụng PHP 8 Attributes (`#[Test]`, `#[DataProvider]`, `#[TestDox]`).
|
||||
- **CakePHP 4/5**: Dùng `Cake\TestSuite\TestCase`, `$this->fetchTable()`.
|
||||
- **CakePHP 3 / Laravel cũ**: Fallback DocBlock `/** @test */`.
|
||||
|
||||
---
|
||||
|
||||
@@ -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:
|
||||
- **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.
|
||||
- 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)**:
|
||||
- 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**:
|
||||
- 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**:
|
||||
- Đầ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`.
|
||||
- **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ả.
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
# 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?)
|
||||
- Test có đang vô tình gọi Database thật hoặc gọi router HTTP không?
|
||||
- Các dependency nặng (API, Queue, Mail, Storage, Repository) đã được Mock hoặc Fake 100% chưa?
|
||||
### [ ] 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 dev thật hoặc gọi router HTTP ngoài đời thực không?
|
||||
- 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?)
|
||||
- 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?
|
||||
- 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?)
|
||||
- Các đối tượng DTO, Value Object, Collection có được tạo bằng instance thật thay vì mock không?
|
||||
### [ ] 4. Không Over-Mock DTO/Value Object/Entity (Are value objects kept real?)
|
||||
- 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?)
|
||||
- Đã 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 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?)
|
||||
- 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!
|
||||
- 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`!
|
||||
### [ ] 6. Không Bỏ Sót Phương Thức & Trait Cốt Lõi (Are all core methods & traits tested?)
|
||||
- **CakePHP Controller**: Đã sử dụng `Cake\TestSuite\IntegrationTestTrait` và gọi `$this->enableCsrfToken()`, `$this->enableSecurityToken()` khi test POST/PUT chưa?
|
||||
- **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?
|
||||
|
||||
### [ ] 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?
|
||||
- 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?
|
||||
|
||||
### [ ] 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?)
|
||||
- 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?
|
||||
|
||||
### [ ] 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`).
|
||||
|
||||
### [ ] 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?
|
||||
- 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`).
|
||||
|
||||
@@ -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+\%/'
|
||||
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
test-runner:
|
||||
image: php:8.3-cli-alpine
|
||||
container_name: laravel_unit_tester
|
||||
container_name: php_unit_tester
|
||||
working_dir: /var/www/html
|
||||
volumes:
|
||||
- ./:/var/www/html
|
||||
@@ -15,7 +15,14 @@ services:
|
||||
- MAIL_MAILER=array
|
||||
command: >
|
||||
sh -c "
|
||||
apk add --no-cache sqlite-libs libzip-dev zip unzip git &&
|
||||
docker-php-ext-install pdo pdo_sqlite zip &&
|
||||
php artisan test
|
||||
apk add --no-cache sqlite-libs libzip-dev icu-dev zip unzip git &&
|
||||
docker-php-ext-configure intl &&
|
||||
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
|
||||
"
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ==============================================================================
|
||||
# Smart Test Runner for Laravel
|
||||
# Tự động nhận diện môi trường: Laravel Sail -> Docker Compose -> Local PHP
|
||||
# Smart Test Runner for Laravel & CakePHP
|
||||
# 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
|
||||
@@ -10,10 +11,12 @@ set -e
|
||||
# Màu sắc thông báo
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
RED='\033[0;31m'
|
||||
CYAN='\033[0;36m'
|
||||
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
|
||||
run_test() {
|
||||
@@ -23,42 +26,104 @@ run_test() {
|
||||
$runner "$@"
|
||||
}
|
||||
|
||||
# 1. Kiểm tra Laravel Sail
|
||||
if [ -f "./vendor/bin/sail" ] && docker info >/dev/null 2>&1; 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}"
|
||||
run_test "./vendor/bin/sail test" "$@"
|
||||
# 1. Xác định Framework: CakePHP hay Laravel
|
||||
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
|
||||
|
||||
# 2. Kiểm tra Docker Compose tự cấu hình
|
||||
if [ -f "docker-compose.yml" ] || [ -f "compose.yaml" ]; then
|
||||
if docker info >/dev/null 2>&1; then
|
||||
# Tìm container service chạy php
|
||||
SERVICES=("app" "php" "php-fpm" "backend" "workspace" "web")
|
||||
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}"
|
||||
run_test "docker compose exec -T ${svc} php artisan test" "$@"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
# 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
|
||||
echo -e "${GREEN}✓ Phát hiện Laravel Sail đang hoạt động.${NC}"
|
||||
run_test "./vendor/bin/sail test" "$@"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# B. Docker Compose
|
||||
if [ -f "docker-compose.yml" ] || [ -f "compose.yaml" ]; then
|
||||
if docker info >/dev/null 2>&1; then
|
||||
SERVICES=("app" "php" "php-fpm" "backend" "workspace" "web")
|
||||
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}"
|
||||
run_test "docker compose exec -T ${svc} php artisan test" "$@"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
# C. Fallback về PHP CLI cục bộ
|
||||
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}"
|
||||
|
||||
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}"
|
||||
fi
|
||||
|
||||
run_test "php artisan test" "$@"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# 3. Fallback về PHP CLI cục bộ
|
||||
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}"
|
||||
|
||||
# Kiểm tra extension pdo_sqlite
|
||||
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}"
|
||||
fi
|
||||
|
||||
run_test "php artisan test" "$@"
|
||||
# 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 (Sail, Docker Compose hoặc PHP CLI) để chạy test!${NC}"
|
||||
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
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ==============================================================================
|
||||
# Smart Dependency Installer for Laravel 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
|
||||
# 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 hoặc Local Composer
|
||||
# ==============================================================================
|
||||
|
||||
set -e
|
||||
@@ -11,10 +11,11 @@ GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
RED='\033[0;31m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
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}"
|
||||
|
||||
if [ ! -f "composer.json" ]; then
|
||||
@@ -22,14 +23,24 @@ if [ ! -f "composer.json" ]; then
|
||||
exit 1
|
||||
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=""
|
||||
|
||||
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}"
|
||||
COMPOSER_CMD="./vendor/bin/sail composer"
|
||||
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
|
||||
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}"
|
||||
@@ -43,17 +54,27 @@ if [ -z "$COMPOSER_CMD" ]; 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}"
|
||||
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
|
||||
echo -e "${RED}❌ Lỗi: Không tìm thấy Composer trên máy hoặc trong container Docker!${NC}"
|
||||
exit 1
|
||||
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}"
|
||||
|
||||
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
|
||||
if ! grep -q '"mockery/mockery"' composer.json; then
|
||||
PACKAGES_TO_INSTALL+=("mockery/mockery")
|
||||
@@ -64,29 +85,31 @@ if ! grep -q '"fakerphp/faker"' composer.json; then
|
||||
PACKAGES_TO_INSTALL+=("fakerphp/faker")
|
||||
fi
|
||||
|
||||
# Kiểm tra Test Runner (Pest vs PHPUnit)
|
||||
IS_PEST=false
|
||||
if grep -q '"pestphp/pest"' composer.json; then
|
||||
IS_PEST=true
|
||||
fi
|
||||
if [ "$FRAMEWORK" = "laravel" ]; then
|
||||
# Kiểm tra Test Runner (Pest vs PHPUnit)
|
||||
IS_PEST=false
|
||||
if grep -q '"pestphp/pest"' composer.json; then
|
||||
IS_PEST=true
|
||||
fi
|
||||
|
||||
if [ "$IS_PEST" = true ]; then
|
||||
if ! grep -q '"pestphp/pest-plugin-laravel"' composer.json; then
|
||||
PACKAGES_TO_INSTALL+=("pestphp/pest-plugin-laravel")
|
||||
if [ "$IS_PEST" = true ]; then
|
||||
if ! grep -q '"pestphp/pest-plugin-laravel"' composer.json; then
|
||||
PACKAGES_TO_INSTALL+=("pestphp/pest-plugin-laravel")
|
||||
fi
|
||||
fi
|
||||
|
||||
# Kiểm tra công cụ chạy song song (Paratest)
|
||||
if ! grep -q '"brianium/paratest"' composer.json; then
|
||||
PACKAGES_TO_INSTALL+=("brianium/paratest")
|
||||
fi
|
||||
|
||||
# Kiểm tra Collision
|
||||
if ! grep -q '"nunomaduro/collision"' composer.json; then
|
||||
PACKAGES_TO_INSTALL+=("nunomaduro/collision")
|
||||
fi
|
||||
fi
|
||||
|
||||
# Kiểm tra công cụ chạy song song (Paratest)
|
||||
if ! grep -q '"brianium/paratest"' composer.json; then
|
||||
PACKAGES_TO_INSTALL+=("brianium/paratest")
|
||||
fi
|
||||
|
||||
# Kiểm tra Collision
|
||||
if ! grep -q '"nunomaduro/collision"' composer.json; then
|
||||
PACKAGES_TO_INSTALL+=("nunomaduro/collision")
|
||||
fi
|
||||
|
||||
# 3. Tiến hành cài đặt
|
||||
# 4. Tiến hành cài đặt
|
||||
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}"
|
||||
exit 0
|
||||
|
||||
Reference in New Issue
Block a user