什么是测试
测试是一种自动验证代码是否按预期运行的机制。
编写测试可以在添加或修改功能时快速确认既有行为没有被破坏。
在团队开发中,测试让你可以放心地修改和评审代码。
Laravel 从一开始就内置了对测试的支持,Pest 和 PHPUnit 都可以使用。
新安装时会自动准备好 phpunit.xml 配置和 tests/ 目录。
Pest 构建在 PHPUnit 之上,语法更简洁易读。初学者建议直接选择 Pest。
tests/ 目录结构
tests/
├── Feature/ # 功能测试
│ └── ExampleTest.php
├── Unit/ # 单元测试
│ └── ExampleTest.php
└── TestCase.php
Feature/:功能测试。用于包含 HTTP 请求的较大粒度测试。适合验证多对象协作、API 端点等接近整体的行为。多数测试都会写在这里。
Unit/:单元测试。用于单个类或方法等小粒度测试。此处不会启动 Laravel 应用,因此不能使用数据库或其他框架功能。
创建测试
使用 make:test Artisan 命令生成新的测试类。
# 生成 Feature 测试(默认)
php artisan make:test TodoTest
# 生成 Unit 测试
php artisan make:test TodoTest --unit
会生成 tests/Feature/TodoTest.php。
运行测试
使用 php artisan test 命令。
也可以直接运行 vendor/bin/pest 或 vendor/bin/phpunit,但 php artisan test 的输出更易读,推荐使用。
只运行特定套件:
# 只运行 Feature 测试
php artisan test --testsuite=Feature
# 遇到失败立即停止
php artisan test --stop-on-failure
基本测试写法
简单断言的示例:
<?php
test('true 应为 true', function () {
expect(true)->toBeTrue();
});
test('字符串是否包含', function () {
$message = 'Hello, Laravel!';
expect($message)->toContain('Laravel');
});
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
class ExampleTest extends TestCase
{
public function test_true_is_true(): void
{
$this->assertTrue(true);
}
public function test_string_contains_laravel(): void
{
$message = 'Hello, Laravel!';
$this->assertStringContainsString('Laravel', $message);
}
}
常用断言
| Pest | PHPUnit | 说明 |
|---|
expect($x)->toBe($y) | $this->assertSame($y, $x) | 严格相等 |
expect($x)->toEqual($y) | $this->assertEquals($y, $x) | 相等(不比较类型) |
expect($x)->toBeTrue() | $this->assertTrue($x) | 为 true |
expect($x)->toBeFalse() | $this->assertFalse($x) | 为 false |
expect($x)->toBeNull() | $this->assertNull($x) | 为 null |
expect($x)->toContain($y) | $this->assertStringContainsString($y, $x) | 包含字符串 |
expect($x)->toHaveCount($n) | $this->assertCount($n, $x) | 元素数量一致 |
HTTP 测试
Laravel 的 HTTP 测试无需启动真实 HTTP 服务器,就能模拟路由请求。
这是 Feature/ 中最强大的能力。
检查页面显示
用 get() 发送 GET 请求并校验响应。
<?php
test('显示首页', function () {
$response = $this->get('/');
$response->assertStatus(200);
});
<?php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_top_page_is_displayed(): void
{
$response = $this->get('/');
$response->assertStatus(200);
}
}
ToDo 应用的 HTTP 测试示例
以带有 Todo 模型的应用为例,测试各 CRUD 操作。
<?php
use App\Models\Todo;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
test('ToDo 列表页可展示', function () {
Todo::factory()->count(3)->create();
$response = $this->get('/todos');
$response->assertStatus(200);
$response->assertSee('列表');
});
test('可以新增 ToDo', function () {
$response = $this->post('/todos', [
'title' => '学习 Laravel',
]);
$response->assertRedirect('/todos');
$this->assertDatabaseHas('todos', ['title' => '学习 Laravel']);
});
test('可以删除 ToDo', function () {
$todo = Todo::factory()->create();
$response = $this->delete("/todos/{$todo->id}");
$response->assertRedirect('/todos');
$this->assertDatabaseMissing('todos', ['id' => $todo->id]);
});
<?php
namespace Tests\Feature;
use App\Models\Todo;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class TodoTest extends TestCase
{
use RefreshDatabase;
public function test_todo_list_is_displayed(): void
{
Todo::factory()->count(3)->create();
$response = $this->get('/todos');
$response->assertStatus(200);
$response->assertSee('列表');
}
public function test_todo_can_be_created(): void
{
$response = $this->post('/todos', [
'title' => '学习 Laravel',
]);
$response->assertRedirect('/todos');
$this->assertDatabaseHas('todos', ['title' => '学习 Laravel']);
}
public function test_todo_can_be_deleted(): void
{
$todo = Todo::factory()->create();
$response = $this->delete("/todos/{$todo->id}");
$response->assertRedirect('/todos');
$this->assertDatabaseMissing('todos', ['id' => $todo->id]);
}
}
常用响应断言
| 方法 | 说明 |
|---|
assertStatus(200) | 校验 HTTP 状态码 |
assertOk() | 状态码为 200 |
assertRedirect('/path') | 校验重定向目标 |
assertSee('文本') | 响应体包含指定文本 |
assertDontSee('文本') | 响应体不包含 |
assertJson([...]) | 校验 JSON 数据 |
assertDatabaseHas('table', [...]) | 校验数据库存在记录 |
assertDatabaseMissing('table', [...]) | 校验数据库无记录 |
使用 RefreshDatabase trait 时,每次测试执行后都会重置数据库,避免测试间数据互相干扰。
测试环境
phpunit.xml 配置
在项目根目录的 phpunit.xml 中配置测试环境。默认 Session 和 Cache 使用 array 驱动,运行过程中不会留下数据。
<env name="APP_ENV" value="testing"/>
<env name="CACHE_STORE" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
数据库通常使用 SQLite 的内存数据库,避免产生文件:
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
.env.testing 文件
在项目根目录创建 .env.testing 文件,测试执行时会替代 .env 被读取。
适合分离测试专用的数据库连接或外部服务配置。
APP_ENV=testing
DB_CONNECTION=sqlite
DB_DATABASE=:memory:
如果启用了配置缓存,请先运行 php artisan config:clear 再跑测试。旧缓存可能导致配置未生效。
并行测试
测试数量增多时,运行时间也会变长。
可用 --parallel 选项启动多个进程并行执行,从而缩短耗时。
首先安装 brianium/paratest:
composer require brianium/paratest --dev
然后添加 --parallel:
php artisan test --parallel
默认使用 CPU 核心数个进程。可用 --processes 指定:
php artisan test --parallel --processes=4
并行测试时,每个进程都需要独立的数据库。搭配 RefreshDatabase trait 与 phpunit.xml 中的内存 SQLite 就能顺利运行。
下一步
HTTP 测试
查看请求模拟、认证测试、JSON 断言等更详细的 HTTP 测试方法。