# 02. Testing Frameworks: PHPUnit 10/11 vs Pest PHP v2/v3 So sánh chi tiết, cú pháp đối chiếu và quy tắc viết code giữa hai framework kiểm thử phổ biến nhất trong hệ sinh thái Laravel. --- ## 1. Bảng Đối Chiếu Cú Pháp (Cheat Sheet) | Tính năng | PHPUnit 10/11 (Class-based) | Pest PHP v2/v3 (Functional) | | :--- | :--- | :--- | | **Khai báo Test** | `#[Test]` trên public method | `it('does something', function () { ... });` | | **Khởi tạo (Setup)** | `protected function setUp(): void` | `beforeEach(function () { ... });` | | **Dọn dẹp (Teardown)** | `protected function tearDown(): void` | `afterEach(function () { ... });` | | **Assert giá trị bằng** | `$this->assertSame($expected, $actual);` | `expect($actual)->toBe($expected);` | | **Assert kiểu dữ liệu** | `$this->assertInstanceOf(User::class, $res);` | `expect($res)->toBeInstanceOf(User::class);` | | **Assert True/False** | `$this->assertTrue($flag);` | `expect($flag)->toBeTrue();` | | **Assert Array có key**| `$this->assertArrayHasKey('id', $data);` | `expect($data)->toHaveKey('id');` | | **Assert Count** | `$this->assertCount(3, $items);` | `expect($items)->toHaveCount(3);` | | **Kỳ vọng Exception** | `$this->expectException(CustomException::class);` | `expect(fn() => ...)->toThrow(CustomException::class);` | | **Data Provider** | `#[DataProvider('provideData')]` | `->with([...])` hoặc `->with('dataset_name')` | --- ## 2. Chuẩn viết cho PHPUnit 10 & 11 Trong Laravel 10 và 11 với PHPUnit 10+, **KHÔNG dùng DocBlock `@test`** lỗi thời mà chuyển hẳn sang dùng **PHP 8 Attributes**: ```php service = new OrderCalculationService(); } #[Test] public function it_applies_tax_correctly(): void { // Arrange $amount = 100.0; $taxRate = 0.1; // 10% // Act $total = $this->service->calculateWithTax($amount, $taxRate); // Assert $this->assertSame(110.0, $total); } #[Test] #[DataProvider('invalidAmountProvider')] public function it_throws_exception_when_amount_is_negative(float $invalidAmount): void { $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Amount must be positive'); $this->service->calculateWithTax($invalidAmount, 0.1); } public static function invalidAmountProvider(): array { return [ 'negative amount' => [-10.0], 'zero amount' => [0.0], ]; } } ``` --- ## 3. Chuẩn viết cho Pest PHP v2 & v3 Pest đem lại cú pháp ngắn gọn, dễ đọc như văn xuôi (BDD style): ```php service = new OrderCalculationService(); }); describe('OrderCalculationService', function () { it('applies tax correctly', function () { // Arrange $amount = 100.0; $taxRate = 0.1; // Act $total = $this->service->calculateWithTax($amount, $taxRate); // Assert expect($total)->toBe(110.0); }); it('throws exception when amount is negative or zero', function (float $invalidAmount) { expect(fn () => $this->service->calculateWithTax($invalidAmount, 0.1)) ->toThrow(\InvalidArgumentException::class, 'Amount must be positive'); })->with([ 'negative amount' => -10.0, 'zero amount' => 0.0, ]); }); ``` --- ## 4. Nguyên tắc Nhận diện Tự động của Agent Khi Agent sinh code test cho một dự án: 1. Đọc `composer.json`: - Nếu có package `pestphp/pest` -> **BẮT BUỘC sinh file test theo chuẩn Pest**. - Nếu chỉ có `phpunit/phpunit`: - Kiểm tra phiên bản PHP: Nếu PHP >= 8.1 và PHPUnit >= 10 -> Dùng **PHP 8 Attributes (`#[Test]`)**. - Nếu PHP <= 8.0 hoặc PHPUnit 9 (Laravel 8/9) -> Dùng **DocBlock `/** @test */`**.