From 7425b263d5adbaa2786baf052f8587a99115002a Mon Sep 17 00:00:00 2001 From: thienpt Date: Mon, 14 Sep 2026 10:19:40 +0700 Subject: [PATCH] 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 --- .claude/skills/cakephp-mock-helper/skill.md | 54 ++ .claude/skills/cakephp-test-review/skill.md | 65 +++ .claude/skills/cakephp-test-setup/skill.md | 109 ++++ .claude/skills/cakephp-unit-test/skill.md | 138 +++++ README.md | 527 +++++++++++------- docs/cakephp/01-cakephp-testing-guide.md | 161 ++++++ docs/cakephp/02-cakephp-components-guide.md | 211 +++++++ install.sh | 160 ++++-- rules/system-prompt-unit-test.md | 49 +- rules/verification-checklist.md | 29 +- templates/cakephp/configs/app_local.test.php | 22 + .../cakephp/configs/phpunit.xml.dist.template | 37 ++ .../cakephp/phpunit/BehaviorTest.template.php | 55 ++ .../cakephp/phpunit/CommandTest.template.php | 49 ++ .../phpunit/ComponentTest.template.php | 55 ++ .../phpunit/ControllerTest.template.php | 104 ++++ .../cakephp/phpunit/EntityTest.template.php | 77 +++ .../cakephp/phpunit/FormTest.template.php | 75 +++ .../cakephp/phpunit/HelperTest.template.php | 48 ++ .../cakephp/phpunit/MailerTest.template.php | 45 ++ .../phpunit/MiddlewareTest.template.php | 40 ++ .../cakephp/phpunit/ServiceTest.template.php | 66 +++ .../cakephp/phpunit/TableTest.template.php | 104 ++++ .../ci/github-actions-cakephp.yml.template | 49 ++ templates/ci/gitlab-ci-cakephp.yml.template | 22 + templates/docker/docker-compose.test.yml | 15 +- templates/docker/test-runner.sh | 127 +++-- templates/setup-dependencies.sh | 73 ++- 28 files changed, 2210 insertions(+), 356 deletions(-) create mode 100644 .claude/skills/cakephp-mock-helper/skill.md create mode 100644 .claude/skills/cakephp-test-review/skill.md create mode 100644 .claude/skills/cakephp-test-setup/skill.md create mode 100644 .claude/skills/cakephp-unit-test/skill.md create mode 100644 docs/cakephp/01-cakephp-testing-guide.md create mode 100644 docs/cakephp/02-cakephp-components-guide.md create mode 100644 templates/cakephp/configs/app_local.test.php create mode 100644 templates/cakephp/configs/phpunit.xml.dist.template create mode 100644 templates/cakephp/phpunit/BehaviorTest.template.php create mode 100644 templates/cakephp/phpunit/CommandTest.template.php create mode 100644 templates/cakephp/phpunit/ComponentTest.template.php create mode 100644 templates/cakephp/phpunit/ControllerTest.template.php create mode 100644 templates/cakephp/phpunit/EntityTest.template.php create mode 100644 templates/cakephp/phpunit/FormTest.template.php create mode 100644 templates/cakephp/phpunit/HelperTest.template.php create mode 100644 templates/cakephp/phpunit/MailerTest.template.php create mode 100644 templates/cakephp/phpunit/MiddlewareTest.template.php create mode 100644 templates/cakephp/phpunit/ServiceTest.template.php create mode 100644 templates/cakephp/phpunit/TableTest.template.php create mode 100644 templates/ci/github-actions-cakephp.yml.template create mode 100644 templates/ci/gitlab-ci-cakephp.yml.template diff --git a/.claude/skills/cakephp-mock-helper/skill.md b/.claude/skills/cakephp-mock-helper/skill.md new file mode 100644 index 0000000..90e2912 --- /dev/null +++ b/.claude/skills/cakephp-mock-helper/skill.md @@ -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 +``` +*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']); +``` diff --git a/.claude/skills/cakephp-test-review/skill.md b/.claude/skills/cakephp-test-review/skill.md new file mode 100644 index 0000000..09984ee --- /dev/null +++ b/.claude/skills/cakephp-test-review/skill.md @@ -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... +``` +``` diff --git a/.claude/skills/cakephp-test-setup/skill.md b/.claude/skills/cakephp-test-setup/skill.md new file mode 100644 index 0000000..32069df --- /dev/null +++ b/.claude/skills/cakephp-test-setup/skill.md @@ -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 `` 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!** +``` diff --git a/.claude/skills/cakephp-unit-test/skill.md b/.claude/skills/cakephp-unit-test/skill.md new file mode 100644 index 0000000..8881187 --- /dev/null +++ b/.claude/skills/cakephp-unit-test/skill.md @@ -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 + ``` diff --git a/README.md b/README.md index 81973a9..8cffa33 100644 --- a/README.md +++ b/README.md @@ -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//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) diff --git a/docs/cakephp/01-cakephp-testing-guide.md b/docs/cakephp/01-cakephp-testing-guide.md new file mode 100644 index 0000000..547a72d --- /dev/null +++ b/docs/cakephp/01-cakephp-testing-guide.md @@ -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 + ``` diff --git a/docs/cakephp/02-cakephp-components-guide.md b/docs/cakephp/02-cakephp-components-guide.md new file mode 100644 index 0000000..4388350 --- /dev/null +++ b/docs/cakephp/02-cakephp-components-guide.md @@ -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`). diff --git a/install.sh b/install.sh index 492dbff..3391bea 100755 --- a/install.sh +++ b/install.sh @@ -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//agent-unit-testing/main/install.sh | bash -# Hoặc: -# bash <(curl -fsSL https://raw.githubusercontent.com//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//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 /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 /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//.git curl -fsSL /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 /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 '' "$TARGET_XML"; then - sed -i.bak '//a\ + if [ -n "$TARGET_XML" ]; then + if ! grep -q 'name="DB_CONNECTION"' "$TARGET_XML" || ! grep -q 'value="sqlite"' "$TARGET_XML"; then + if grep -q '' "$TARGET_XML"; then + sed -i.bak '//a\ \ \ \ @@ -227,11 +259,28 @@ if [ -n "$TARGET_XML" ]; then \ ' "$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}" diff --git a/rules/system-prompt-unit-test.md b/rules/system-prompt-unit-test.md index 6aa9112..eb8c3c2 100644 --- a/rules/system-prompt-unit-test.md +++ b/rules/system-prompt-unit-test.md @@ -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ả. diff --git a/rules/verification-checklist.md b/rules/verification-checklist.md index 0960d0a..afed2b3 100644 --- a/rules/verification-checklist.md +++ b/rules/verification-checklist.md @@ -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`). diff --git a/templates/cakephp/configs/app_local.test.php b/templates/cakephp/configs/app_local.test.php new file mode 100644 index 0000000..10245c9 --- /dev/null +++ b/templates/cakephp/configs/app_local.test.php @@ -0,0 +1,22 @@ + [ + '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, + ], + ], +]; diff --git a/templates/cakephp/configs/phpunit.xml.dist.template b/templates/cakephp/configs/phpunit.xml.dist.template new file mode 100644 index 0000000..cdc9084 --- /dev/null +++ b/templates/cakephp/configs/phpunit.xml.dist.template @@ -0,0 +1,37 @@ + + + + + + + + + + + + tests/TestCase/ + + + + + + + + + + + src/ + plugins/*/src/ + + + src/Console/Installer.php + + + diff --git a/templates/cakephp/phpunit/BehaviorTest.template.php b/templates/cakephp/phpunit/BehaviorTest.template.php new file mode 100644 index 0000000..7e7fd12 --- /dev/null +++ b/templates/cakephp/phpunit/BehaviorTest.template.php @@ -0,0 +1,55 @@ +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}}); + } +} diff --git a/templates/cakephp/phpunit/CommandTest.template.php b/templates/cakephp/phpunit/CommandTest.template.php new file mode 100644 index 0000000..e712309 --- /dev/null +++ b/templates/cakephp/phpunit/CommandTest.template.php @@ -0,0 +1,49 @@ +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(); + } +} diff --git a/templates/cakephp/phpunit/ComponentTest.template.php b/templates/cakephp/phpunit/ComponentTest.template.php new file mode 100644 index 0000000..ed72128 --- /dev/null +++ b/templates/cakephp/phpunit/ComponentTest.template.php @@ -0,0 +1,55 @@ +{{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); + } +} diff --git a/templates/cakephp/phpunit/ControllerTest.template.php b/templates/cakephp/phpunit/ControllerTest.template.php new file mode 100644 index 0000000..9ee9dee --- /dev/null +++ b/templates/cakephp/phpunit/ControllerTest.template.php @@ -0,0 +1,104 @@ + + */ + 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 + } +} diff --git a/templates/cakephp/phpunit/EntityTest.template.php b/templates/cakephp/phpunit/EntityTest.template.php new file mode 100644 index 0000000..5a3d968 --- /dev/null +++ b/templates/cakephp/phpunit/EntityTest.template.php @@ -0,0 +1,77 @@ +{{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); + } + } +} diff --git a/templates/cakephp/phpunit/FormTest.template.php b/templates/cakephp/phpunit/FormTest.template.php new file mode 100644 index 0000000..56760e5 --- /dev/null +++ b/templates/cakephp/phpunit/FormTest.template.php @@ -0,0 +1,75 @@ +{{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); + } +} diff --git a/templates/cakephp/phpunit/HelperTest.template.php b/templates/cakephp/phpunit/HelperTest.template.php new file mode 100644 index 0000000..57125c6 --- /dev/null +++ b/templates/cakephp/phpunit/HelperTest.template.php @@ -0,0 +1,48 @@ +{{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}}); + } +} diff --git a/templates/cakephp/phpunit/MailerTest.template.php b/templates/cakephp/phpunit/MailerTest.template.php new file mode 100644 index 0000000..5e2e7af --- /dev/null +++ b/templates/cakephp/phpunit/MailerTest.template.php @@ -0,0 +1,45 @@ +{{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'); + } +} diff --git a/templates/cakephp/phpunit/MiddlewareTest.template.php b/templates/cakephp/phpunit/MiddlewareTest.template.php new file mode 100644 index 0000000..b2c5b42 --- /dev/null +++ b/templates/cakephp/phpunit/MiddlewareTest.template.php @@ -0,0 +1,40 @@ + '/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()); + } +} diff --git a/templates/cakephp/phpunit/ServiceTest.template.php b/templates/cakephp/phpunit/ServiceTest.template.php new file mode 100644 index 0000000..4465884 --- /dev/null +++ b/templates/cakephp/phpunit/ServiceTest.template.php @@ -0,0 +1,66 @@ +{{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(''); + } +} diff --git a/templates/cakephp/phpunit/TableTest.template.php b/templates/cakephp/phpunit/TableTest.template.php new file mode 100644 index 0000000..f547143 --- /dev/null +++ b/templates/cakephp/phpunit/TableTest.template.php @@ -0,0 +1,104 @@ + + */ + 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); + } +} diff --git a/templates/ci/github-actions-cakephp.yml.template b/templates/ci/github-actions-cakephp.yml.template new file mode 100644 index 0000000..dcb4dbb --- /dev/null +++ b/templates/ci/github-actions-cakephp.yml.template @@ -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/ diff --git a/templates/ci/gitlab-ci-cakephp.yml.template b/templates/ci/gitlab-ci-cakephp.yml.template new file mode 100644 index 0000000..7150e90 --- /dev/null +++ b/templates/ci/gitlab-ci-cakephp.yml.template @@ -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+\%/' diff --git a/templates/docker/docker-compose.test.yml b/templates/docker/docker-compose.test.yml index a9d0ad9..93a84b1 100644 --- a/templates/docker/docker-compose.test.yml +++ b/templates/docker/docker-compose.test.yml @@ -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 " diff --git a/templates/docker/test-runner.sh b/templates/docker/test-runner.sh index 0172f76..4e1d00f 100755 --- a/templates/docker/test-runner.sh +++ b/templates/docker/test-runner.sh @@ -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 diff --git a/templates/setup-dependencies.sh b/templates/setup-dependencies.sh index 1e81c09..60ea829 100755 --- a/templates/setup-dependencies.sh +++ b/templates/setup-dependencies.sh @@ -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