Trong 50 bài chính, chúng ta đã hoàn thành một Blog CMS với:
Authentication
CRUD
Database
Eloquent
Relationship
Validation
Upload
Search
Pagination
Soft Delete
Authorization
Queue
Mail
Notification
Cache
Storage
Logging
REST API
Sanctum
Deployment
Nhưng có một vấn đề:
Làm sao biết code của chúng ta vẫn hoạt động đúng sau khi sửa hoặc thêm tính năng mới?
Đó là lúc Automated Testing xuất hiện.
Trong bài Bonus này, chúng ta chỉ học những kiến thức PHPUnit cần thiết để bắt đầu kiểm thử một ứng dụng Laravel 13.
1. Testing là gì?
Testing là quá trình kiểm tra xem chương trình có hoạt động đúng theo yêu cầu hay không.
Ví dụ Blog CMS có chức năng:
GET /blog
Chúng ta mong muốn:
HTTP 200
Thay vì mỗi lần sửa code lại mở trình duyệt kiểm tra bằng tay, chúng ta có thể viết test:
$response = $this->get('/blog');
$response->assertOk();
Sau đó Laravel tự kiểm tra.
Nếu đúng:
PASS
Nếu sai:
FAIL
2. PHPUnit là gì?
PHPUnit là framework testing phổ biến trong PHP.
Laravel tích hợp sẵn khả năng testing với PHPUnit và Pest. Một project Laravel mới đã có phpunit.xml và thư mục tests.
Trong bài này chúng ta tập trung vào:
PHPUnit
vì mục tiêu của Bonus là giúp người mới hiểu nền tảng testing.
3. Tests nằm ở đâu?
Trong Laravel:
tests/
│
├── Feature/
│
├── Unit/
│
└── TestCase.php
Hai loại quan trọng:
Unit Test
Feature Test
4. Unit Test
Unit Test kiểm tra một phần nhỏ và tương đối độc lập của code.
Ví dụ:
Một method
Một class
Một hàm xử lý
Một business rule
Ví dụ:
public function add(int $a, int $b): int
{
return $a + $b;
}
Test:
$this->assertEquals(
5,
$calculator->add(2, 3)
);
Unit Test thường không cần boot toàn bộ ứng dụng Laravel hoặc truy cập database.
5. Feature Test
Feature Test kiểm tra một chức năng lớn hơn.
Ví dụ:
Browser
↓
Route
↓
Middleware
↓
Controller
↓
Model
↓
Database
↓
Response
Ví dụ:
$response = $this->get('/blog');
$response->assertOk();
Đây là một Feature Test.
Laravel lưu ý rằng Feature Test có thể kiểm tra cả HTTP request, database và nhiều thành phần phối hợp với nhau; trong ứng dụng thực tế, phần lớn test thường có thể tập trung vào Feature Test vì nó cho mức độ tin cậy cao hơn về hành vi tổng thể của hệ thống.
6. Tạo Test
Laravel cung cấp Artisan command:
php artisan make:test BlogTest
File sẽ nằm trong:
tests/Feature/BlogTest.php
Nếu muốn tạo Unit Test:
php artisan make:test CalculatorTest --unit
File:
tests/Unit/CalculatorTest.php
7. Test đầu tiên
Tạo:
php artisan make:test BlogTest
Sau đó mở:
tests/Feature/BlogTest.php
Ví dụ:
<?php
namespace Tests\Feature;
use Tests\TestCase;
class BlogTest extends TestCase
{
public function test_blog_page_is_accessible(): void
{
$response = $this->get('/blog');
$response->assertOk();
}
}
Test này nói:
Khi request
/blog, ứng dụng phải trả HTTP 200.
8. Chạy Test
Có thể chạy:
php artisan test
Laravel cũng cho phép chạy trực tiếp PHPUnit:
vendor/bin/phpunit
Laravel cung cấp php artisan test như một cách thuận tiện để chạy test suite.
Kết quả có thể giống:
PASS Tests\Feature\BlogTest
✓ blog page is accessible
Tests: 1 passed
Nếu test thất bại:
FAIL
Laravel sẽ hiển thị thông tin để chúng ta tìm nguyên nhân.
9. assertOk()
Một assertion rất thường dùng:
$response->assertOk();
Nó kiểm tra HTTP response có status:
200 OK
Ví dụ:
public function test_blog_page_is_accessible(): void
{
$response = $this->get('/blog');
$response->assertOk();
}
10. assertStatus()
Có thể kiểm tra status cụ thể:
$response->assertStatus(200);
Ví dụ:
$response = $this->get('/blog');
$response->assertStatus(200);
Hoặc:
$response->assertStatus(404);
Ví dụ test một trang không tồn tại:
public function test_missing_page_returns_404(): void
{
$response = $this->get('/this-page-does-not-exist');
$response->assertStatus(404);
}
11. assertRedirect()
Nếu request phải redirect:
$response->assertRedirect('/login');
Ví dụ:
public function test_admin_page_requires_login(): void
{
$response = $this->get('/admin/users');
$response->assertRedirect('/login');
}
Đây là một test rất thực tế đối với Blog CMS.
12. Kiểm tra nội dung HTML
Có thể kiểm tra response có chứa text:
$response->assertSee('Blog');
Ví dụ:
public function test_blog_contains_title(): void
{
$response = $this->get('/blog');
$response->assertOk();
$response->assertSee('Blog');
}
Hoặc:
$response->assertDontSee('Something');
13. Test JSON API
Đây là phần đặc biệt quan trọng đối với Blog CMS.
API:
GET /api/posts
Test:
public function test_posts_api_returns_success(): void
{
$response = $this->getJson('/api/posts');
$response->assertStatus(200);
}
Hoặc:
$response->assertOk();
14. assertJson()
Giả sử API trả về:
{
"data": [
{
"id": 1,
"title": "Laravel 13"
}
]
}
Có thể kiểm tra:
$response->assertJson([
'data' => [
[
'id' => 1,
'title' => 'Laravel 13',
],
],
]);
Mục tiêu là kiểm tra API trả về đúng cấu trúc dữ liệu mong muốn.
15. Test Database
Feature Test có thể kiểm tra database.
Laravel cung cấp các trait hỗ trợ việc reset database giữa các test.
Ví dụ:
use Illuminate\Foundation\Testing\RefreshDatabase;
Sau đó:
class PostTest extends TestCase
{
use RefreshDatabase;
// ...
}
Mỗi test có thể làm việc với database testing thay vì phá dữ liệu development hiện tại.
16. Tạo dữ liệu bằng Factory
Giả sử chúng ta có:
PostFactory
Có thể tạo Post:
$post = Post::factory()->create();
Sau đó test:
$response = $this->get('/blog/' . $post->slug);
$response->assertOk();
Đây là cách rất phù hợp với Blog CMS.
17. Test Post
Ví dụ:
<?php
namespace Tests\Feature;
use App\Models\Post;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class PostTest extends TestCase
{
use RefreshDatabase;
public function test_post_page_can_be_opened(): void
{
$post = Post::factory()->create();
$response = $this->get(
'/blog/' . $post->slug
);
$response->assertOk();
}
}
Flow:
Factory
↓
Create Post
↓
Request URL
↓
Laravel
↓
Response
↓
assertOk()
18. Test Authentication
Ví dụ một trang admin yêu cầu đăng nhập.
public function test_admin_requires_authentication(): void
{
$response = $this->get('/admin/users');
$response->assertRedirect('/login');
}
Sau đó test user đã đăng nhập:
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->get('/admin/users');
Đây là cách kiểm tra các route có authentication.
19. Test Authorization
Authentication chưa đủ.
Chúng ta còn phải kiểm tra quyền.
Ví dụ:
user
admin
Một user thông thường không được phép truy cập:
/admin/users
Test có thể kiểm tra:
$user = User::factory()->create([
'role' => 'user',
]);
$this->actingAs($user);
$response = $this->get('/admin/users');
$response->assertForbidden();
Trong trường hợp ứng dụng của bạn trả về redirect hoặc response khác tùy middleware/policy, assertion phải phù hợp với implementation thực tế.
Điểm quan trọng:
Phải test cả Authentication và Authorization.
20. Test Validation
Ví dụ tạo Post yêu cầu:
title
content
category_id
Nếu gửi request thiếu title:
$response = $this->post('/admin/posts', [
'content' => 'Test content',
]);
Có thể kiểm tra validation:
$response->assertSessionHasErrors([
'title',
]);
Đây là một test rất hữu ích.
21. Test REST API
Blog CMS của chúng ta có:
GET
POST
PUT/PATCH
DELETE
Do đó có thể xây dựng một bộ test:
Post API
│
├── GET /api/posts
├── GET /api/posts/{id}
├── POST /api/posts
├── PUT /api/posts/{id}
└── DELETE /api/posts/{id}
Ví dụ:
public function test_posts_api_returns_json(): void
{
$response = $this->getJson('/api/posts');
$response->assertOk();
$response->assertJsonStructure([
'data',
]);
}
22. Test Sanctum API
API private yêu cầu:
Bearer Token
Có thể tạo user:
$user = User::factory()->create();
Tạo token:
$token = $user->createToken('testing')->plainTextToken;
Sau đó request:
$response = $this
->withToken($token)
->getJson('/api/user');
Kiểm tra:
$response->assertOk();
23. Test API Authentication
Test request không có token:
public function test_private_api_requires_authentication(): void
{
$response = $this->getJson('/api/user');
$response->assertUnauthorized();
}
Kết quả mong muốn:
HTTP 401
Sau đó test với token hợp lệ:
$user = User::factory()->create();
$token = $user
->createToken('testing')
->plainTextToken;
$response = $this
->withToken($token)
->getJson('/api/user');
$response->assertOk();
24. Test Event
Trong Blog CMS, chúng ta đã học Event và Listener.
Testing cũng có thể kiểm tra Event có được dispatch hay không.
Laravel cung cấp:
Event::fake();
Sau đó chạy code cần kiểm tra.
Cuối cùng:
Event::assertDispatched(
Registered::class
);
Laravel hỗ trợ Event::fake() và các assertion như assertDispatched() để kiểm tra event mà không cần thực thi listener trong test đó.
Flow:
Test
│
▼
Event::fake()
│
▼
Execute Code
│
▼
Event dispatched?
│
├── YES → PASS
└── NO → FAIL
25. Test Queue
Tương tự Event, Queue cũng có thể được fake.
Ví dụ:
Queue::fake();
Sau đó:
SomeJob::dispatch();
Kiểm tra:
Queue::assertPushed(SomeJob::class);
Mục tiêu:
Kiểm tra Job đã được dispatch hay chưa.
Không nhất thiết phải thực sự chạy Job trong test này.
26. Test Mail
Mail cũng có thể fake.
Ví dụ:
Mail::fake();
Sau đó:
Mail::to($user)->send(
new WelcomeMail($user)
);
Kiểm tra:
Mail::assertSent(WelcomeMail::class);
Điều này đặc biệt hữu ích với:
Register
Password Reset
Contact Form
Notification
Order Confirmation
27. Test Upload
Blog CMS có upload image.
Có thể fake Storage:
Storage::fake('public');
Sau đó:
$file = UploadedFile::fake()
->image('post.jpg');
Gửi request:
$response = $this->post('/admin/posts', [
'title' => 'Laravel 13',
'image' => $file,
]);
Kiểm tra file:
Storage::disk('public')->assertExists(
'posts/' . $file->hashName()
);
Đây là cách test upload mà không cần ghi file thật vào storage production.
28. Test Soft Delete
Blog CMS có Soft Delete.
Có thể kiểm tra:
$post = Post::factory()->create();
$post->delete();
Sau đó:
$this->assertSoftDeleted($post);
Hoặc kiểm tra record đã bị đánh dấu:
deleted_at != NULL
Sau đó test restore:
$post->restore();
29. Test một CRUD hoàn chỉnh
Ví dụ Post:
Create
Read
Update
Delete
Chúng ta có thể xây dựng:
PostTest
│
├── can list posts
├── can show post
├── can create post
├── can update post
├── can delete post
└── can restore post
Đây chính là cách biến test thành một bộ kiểm tra tự động cho CMS.
30. Ví dụ Feature Test cho Blog CMS
Một test đơn giản:
<?php
namespace Tests\Feature;
use App\Models\Post;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class BlogTest extends TestCase
{
use RefreshDatabase;
public function test_blog_page_is_accessible(): void
{
$response = $this->get('/blog');
$response->assertOk();
}
public function test_post_can_be_viewed(): void
{
$post = Post::factory()->create();
$response = $this->get(
'/blog/' . $post->slug
);
$response->assertOk();
}
}
Chúng ta có thể tiếp tục mở rộng thành:
BlogTest
PostTest
CategoryTest
UserTest
ApiTest
AuthenticationTest
31. Test API Response Structure
Với API Resource, đây là một test rất đáng làm:
$response->assertJsonStructure([
'data' => [
'*' => [
'id',
'title',
'slug',
],
],
]);
Nếu sau này developer vô tình xóa slug khỏi Resource:
Test FAIL
Nhờ vậy chúng ta phát hiện thay đổi API trước khi frontend hoặc mobile app gặp lỗi.
32. Test không chỉ để tìm Bug
Một cách hiểu tốt hơn:
Test là một lớp bảo vệ cho source code.
Ví dụ:
Hôm nay
↓
Code chạy đúng
↓
Viết Test
↓
3 tháng sau
↓
Sửa Controller
↓
Chạy Test
↓
PASS
Nếu:
FAIL
chúng ta biết thay đổi mới có thể đã ảnh hưởng chức năng cũ.
33. Regression Test
Đây gọi là Regression Testing.
Ví dụ:
Version 1
↓
Login hoạt động
↓
Version 2
↓
Thêm API
↓
Version 3
↓
Sửa User
↓
Login bị lỗi
Nếu có test:
php artisan test
chúng ta có thể phát hiện lỗi nhanh hơn.
34. Test Environment
Khi chạy test, Laravel sử dụng environment:
testing
Các thiết lập testing có thể được cấu hình trong phpunit.xml hoặc .env.testing. Laravel cũng cấu hình một số driver phù hợp cho testing, chẳng hạn session và cache mặc định không ghi dữ liệu vào hệ thống thật.
Có thể tạo:
.env.testing
Ví dụ:
APP_ENV=testing
DB_CONNECTION=sqlite
DB_DATABASE=:memory:
Tùy cấu hình project, bạn cũng có thể sử dụng một database testing riêng.
Điểm quan trọng:
Không chạy test trên database production.
35. phpunit.xml
Laravel project có:
phpunit.xml
File này chứa cấu hình dành cho PHPUnit.
Ví dụ có thể cấu hình:
<php>
<env name="APP_ENV" value="testing"/>
<env name="CACHE_STORE" value="array"/>
</php>
Không nên tùy tiện sửa cấu hình nếu chưa hiểu tác động của nó.
36. Chạy một Test cụ thể
Không cần chạy toàn bộ test suite.
Có thể chỉ chạy:
php artisan test --filter=BlogTest
Hoặc:
vendor/bin/phpunit --filter BlogTest
Điều này rất hữu ích khi đang phát triển một module.
37. Quy trình Testing cơ bản
Khi xây dựng một feature:
Requirement
↓
Code
↓
Test
↓
Run Test
↓
PASS?
┌──┴──┐
YES NO
│ │
▼ ▼
Done Debug
│
▼
Fix
│
└──────► Test lại
Đây là vòng lặp rất quan trọng trong phát triển phần mềm.
38. Testing Blog CMS
Với project cuối khóa, không cần test mọi dòng code.
Có thể ưu tiên các chức năng quan trọng:
Authentication
↓
Authorization
↓
Categories
↓
Posts
↓
Upload
↓
Soft Delete
↓
REST API
↓
Sanctum
Ví dụ:
tests/
│
├── Feature/
│ ├── AuthenticationTest.php
│ ├── CategoryTest.php
│ ├── PostTest.php
│ ├── UserTest.php
│ └── ApiTest.php
│
└── Unit/
└── ExampleTest.php
Không cần biến project Blog CMS thành một hệ thống test khổng lồ.
39. Test quan trọng hơn Coverage 100%
Một người mới rất dễ nghĩ:
"Phải test 100% code."
Không nhất thiết.
100% coverage không đồng nghĩa:
100% không có bug
Điều quan trọng hơn là test những behavior quan trọng.
Ví dụ Blog CMS:
User không đăng nhập
→ không vào Admin
User thường
→ không quản lý User
Admin
→ quản lý User
Post draft
→ không xuất hiện public
Post published
→ xuất hiện Blog
API private
→ yêu cầu token
Upload sai file
→ validation fail
Đây là những behavior có giá trị cao để kiểm thử.
40. PHPUnit và CI/CD
Khi đã có test, sau này có thể đưa vào CI/CD:
Git Push
│
▼
CI Server
│
├── composer install
├── Run Tests
├── Build Assets
└── Deploy
Nếu:
Tests PASS
→ có thể tiếp tục deployment.
Nếu:
Tests FAIL
→ dừng deployment.
Đây là bước phát triển tiếp theo sau khi đã nắm testing cơ bản.
41. PHPUnit Cheat Sheet
Một số lệnh và assertion cần nhớ:
Chạy toàn bộ test
php artisan test
Chạy một test
php artisan test --filter=BlogTest
HTTP 200
$response->assertOk();
HTTP status
$response->assertStatus(200);
Redirect
$response->assertRedirect('/login');
Có nội dung
$response->assertSee('Blog');
Có validation error
$response->assertSessionHasErrors([
'title',
]);
JSON
$response->assertJson([
'data' => [],
]);
JSON structure
$response->assertJsonStructure([
'data',
]);
Unauthorized
$response->assertUnauthorized();
Forbidden
$response->assertForbidden();
42. Bài tập Bonus
Bài tập 1
Tạo:
tests/Feature/BlogTest.php
Test:
GET /blog
Yêu cầu:
HTTP 200
Bài tập 2
Test Post:
GET /blog/{slug}
Yêu cầu:
HTTP 200
với một Post hợp lệ.
Bài tập 3
Test validation khi tạo Post.
Thiếu:
title
Kết quả:
validation error
Bài tập 4
Test API:
GET /api/posts
Yêu cầu:
HTTP 200
JSON
data
Bài tập 5
Test API private:
GET /api/user
Không có token:
401
Có token:
200
Bài tập 6
Test Upload Image.
Kiểm tra:
image hợp lệ
→ upload thành công
file không hợp lệ
→ validation fail
43. Khi nào nên viết Test?
Không nhất thiết phải chờ project hoàn thành.
Có thể viết test ngay khi hoàn thành một feature:
Category
↓
Code
↓
Test
Post
↓
Code
↓
Test
API
↓
Code
↓
Test
Cách này giúp lỗi được phát hiện sớm.
44. Testing trong quy trình phát triển thực tế
Một workflow đơn giản:
1. Nhận requirement
↓
2. Thiết kế
↓
3. Code
↓
4. Viết Test
↓
5. Run Test
↓
6. Fix Bug
↓
7. Code Review
↓
8. Deploy
Ở project lớn hơn:
Developer
↓
Git
↓
CI
↓
Tests
↓
Build
↓
Deploy
45. Laravel 13 + PHPUnit — Tổng kết
Sau Bonus này, người học cần hiểu được:
PHPUnit
│
├── Unit Test
│
├── Feature Test
│
├── HTTP Test
│
├── Database Test
│
├── API Test
│
├── Authentication Test
│
├── Validation Test
│
├── Upload Test
│
└── Event / Queue / Mail Test
Và những assertion cơ bản:
assertOk()
assertStatus()
assertRedirect()
assertSee()
assertSessionHasErrors()
assertJson()
assertJsonStructure()
assertUnauthorized()
assertForbidden()
46. Hoàn thành Bonus PHPUnit
Toàn bộ phần Laravel của khóa học bây giờ có thể tóm tắt:
PHP
│
▼
Laravel 13
│
├── MVC
├── Routing
├── Controller
├── Blade
│
├── Database
├── Eloquent
├── Relationship
│
├── Authentication
├── Authorization
│
├── CRUD
├── Upload
├── Search
├── Pagination
├── Soft Delete
│
├── Event
├── Queue
├── Mail
├── Notification
├── Cache
├── Storage
├── Logging
│
├── REST API
├── Resource
├── Sanctum
├── API Authentication
│
├── VPS
├── Nginx
├── Queue Worker
├── Scheduler
└── Production
+
PHPUnit
│
▼
Testing
47. Điều quan trọng nhất
Testing không phải là việc viết thật nhiều code test.
Điều quan trọng là xác định:
Ứng dụng phải làm gì?
Sau đó biến yêu cầu đó thành test.
Ví dụ:
Requirement:
"User chưa đăng nhập không được vào Admin."
↓
Test:
GET /admin/users
↓
Expected:
401 / Redirect Login
Hoặc:
Requirement:
"API Posts phải trả JSON."
↓
Test:
GET /api/posts
↓
Expected:
200
JSON
data
Hoặc:
Requirement:
"User thường không được quản lý User."
↓
Test:
User → GET /admin/users
↓
Expected:
403
Đó chính là tư duy testing.
48. Kết luận
Sau 50 bài Laravel 13 và Bonus PHPUnit, chúng ta đã đi qua toàn bộ quy trình cơ bản của một ứng dụng Web:
PHP
↓
Laravel
↓
Database
↓
Eloquent
↓
CRUD
↓
Authentication
↓
Authorization
↓
API
↓
Sanctum
↓
Queue
↓
Cache
↓
Storage
↓
Logging
↓
Testing
↓
Deployment
Người học không cần nhớ mọi method của Laravel.
Điều quan trọng hơn là biết:
Tìm tài liệu
↓
Hiểu vấn đề
↓
Thiết kế
↓
Viết code
↓
Test
↓
Debug
↓
Deploy
Laravel là framework.
PHP là ngôn ngữ.
Database là nền tảng dữ liệu.
Testing là lớp bảo vệ.
Deployment đưa sản phẩm đến người dùng.
Và Blog CMS chính là project kết nối tất cả những kiến thức đó lại.
🎓 HOÀN THÀNH PHẦN LARAVEL 13
┌─────────────────────────────────────────┐
│ │
│ PHP → LARAVEL 13 │
│ │
│ 50 BÀI + BONUS PHPUnit │
│ │
│ BLOG CMS PROJECT │
│ │
│ CRUD • AUTH • API • SANCTUM │
│ QUEUE • CACHE • STORAGE • LOGGING │
│ TESTING • NGINX • VPS • PRODUCTION │
│ │
└─────────────────────────────────────────┘
Hoàn thành phần Laravel 13.
Bước tiếp theo không còn là học thêm syntax một cách máy móc.
Hãy lấy Blog CMS làm nền tảng, sau đó tự xây dựng một project mới:
Ý tưởng
↓
Database
↓
Laravel
↓
CRUD
↓
Authentication
↓
API
↓
Testing
↓
Deploy
Đó mới là lúc kiến thức Laravel bắt đầu trở thành kỹ năng lập trình thực tế.




Không có nhận xét nào:
Đăng nhận xét