- Add 10 core testing guides (setup, AAA principles, PHPUnit/Pest, mocking, anti-patterns, PHP 8.4+, debugging, data factories, advanced edge cases) - Add 13 component testing guides covering all Laravel layers - Add standard templates for PHPUnit 10/11 and Pest v2/v3 including ArchitectureTest - Add Docker smart test runner, dependency installer, and CI/CD templates (GitHub Actions, GitLab CI) - Add 4 Claude Code skills (laravel-test-setup, laravel-unit-test with --all mode, laravel-test-review, laravel-mock-helper) - Add single unified installer script (sg-install-unit-testing.sh) with global support, CLI runners, and 1-click updater (sg-test-update) - Add comprehensive documentation (README.md, USAGE-GUIDE.md) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4.8 KiB
08. Quản Lý Dữ Liệu Kiểm Thử: Factories, States & Test Builders (Pro Level)
Trong các dự án quy mô lớn, việc chuẩn bị dữ liệu kiểm thử (Test Fixtures / Test Data) chiếm tới 50% thời gian viết test. Tài liệu này hướng dẫn các kỹ thuật chuyên sâu cấp Senior/Principal để chuẩn bị dữ liệu siêu tốc, sạch và tái sử dụng cao.
1. Bí Mật Tốc Độ: make() vs create() Trong Model Factory
Trong Laravel Model Factory có 2 phương thức sinh dữ liệu chính:
| Phương thức | Hành vi | Tốc độ | Khi nào nên dùng? |
|---|---|---|---|
Model::factory()->create() |
Sinh model + LƯU VÀO DATABASE (INSERT INTO) |
Chậm (~20-50ms) | Feature Test (Khi cần database thật để join/query) |
Model::factory()->make() |
Sinh model TRONG BỘ NHỚ (RAM), KHÔNG chạm DB | Siêu nhanh (< 1ms) | Unit Test (Truyền model vào Service/Action/Policy) |
💡 Quy Tắc Vàng Cho Unit Test:
LUÔN DÙNG
make()HOẶCmakeOne()CHO UNIT TEST. Trừ khi class bạn đang test bắt buộc phải query DB (như Repository), tuyệt đối không dùngcreate().
// ❌ CHẬM VÀ SAI NGUYÊN TẮC UNIT TEST:
$user = User::factory()->create(); // Chạy INSERT vào SQLite/MySQL
// ✅ CHUẨN PRO: Khởi tạo hoàn toàn trong RAM:
$user = User::factory()->make([
'id' => 1, // Tự gán ID giả lập nếu logic cần so sánh $user->id
]);
2. Tận Dụng Factory States Cho Các Kịch Bản Biên
Thay vì gán thủ công từng thuộc tính trong test method, hãy định nghĩa các Factory States mang ý nghĩa nghiệp vụ:
// Trong database/factories/UserFactory.php
public function admin(): static
{
return $this->state(fn (array $attributes) => [
'is_admin' => true,
'role' => 'super_admin',
]);
}
public function banned(): static
{
return $this->state(fn (array $attributes) => [
'status' => 'banned',
'banned_at' => now()->subDay(),
]);
}
Sử dụng trong Unit Test:
#[Test]
public function it_denies_access_for_banned_user(): void
{
// Cực kỳ trực quan, dễ đọc như văn xuôi
$bannedUser = User::factory()->banned()->make();
$this->assertFalse($this->policy->login($bannedUser));
}
3. Object Mother Pattern (Bộ Dựng Dữ Liệu Tập Trung)
Khi dự án có nhiều DTO hoặc Value Object phức tạp không dùng Eloquent Factory, hãy áp dụng pattern Object Mother:
// tests/Fixtures/OrderDataMother.php
namespace Tests\Fixtures;
use App\DTOs\CreateOrderDTO;
use App\ValueObjects\Money;
class OrderDataMother
{
public static function standardOrder(int $total = 100_000): CreateOrderDTO
{
return new CreateOrderDTO(
customerId: 1,
items: [
['sku' => 'ITEM-01', 'qty' => 1, 'price' => $total]
],
totalAmount: new Money($total),
shippingAddress: '123 Nguyen Hue, Quan 1, TP.HCM'
);
}
public static function zeroAmountOrder(): CreateOrderDTO
{
return self::standardOrder(total: 0);
}
}
Sử dụng trong Test:
#[Test]
public function it_rejects_order_with_zero_amount(): void
{
$invalidOrder = OrderDataMother::zeroAmountOrder();
$this->expectException(InvalidOrderException::class);
$this->orderService->placeOrder($invalidOrder);
}
4. DTO Test Builder Pattern (Fluent API)
Khi một DTO có 10-15 trường và mỗi test case chỉ muốn thay đổi 1-2 trường:
// tests/Builders/UserDTOBuilder.php
namespace Tests\Builders;
use App\DTOs\UserRegistrationDTO;
class UserDTOBuilder
{
private string $email = 'default@smartgift.vn';
private string $phone = '0912345678';
private string $name = 'Nguyen Van A';
private int $age = 25;
public static function aUser(): self
{
return new self();
}
public function withEmail(string $email): self
{
$this->email = $email;
return $this;
}
public function underAge(int $age = 16): self
{
$this->age = $age;
return $this;
}
public function build(): UserRegistrationDTO
{
return new UserRegistrationDTO(
name: $this->name,
email: $this->email,
phone: $this->phone,
age: $this->age
);
}
}
Sử dụng trong Test:
#[Test]
public function it_throws_exception_when_user_is_under_18(): void
{
// Chỉ thay đổi đúng trường cần test, các trường khác giữ giá trị mặc định hợp lệ
$dto = UserDTOBuilder::aUser()->underAge(15)->build();
$this->expectException(UnderAgeRegistrationException::class);
$this->registrationAction->execute($dto);
}