> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 测试入门

> 使用 Pest 与 PHPUnit 编写 Laravel 应用测试的方法。

## 什么是测试

测试是一种自动验证代码是否按预期运行的机制。
编写测试可以在添加或修改功能时快速确认既有行为没有被破坏。
在团队开发中，测试让你可以放心地修改和评审代码。

Laravel 从一开始就内置了对测试的支持，[Pest](https://pestphp.com) 和 [PHPUnit](https://phpunit.de) 都可以使用。
新安装时会自动准备好 `phpunit.xml` 配置和 `tests/` 目录。

<Tip>
  Pest 构建在 PHPUnit 之上，语法更简洁易读。初学者建议直接选择 Pest。
</Tip>

## `tests/` 目录结构

```
tests/
├── Feature/      # 功能测试
│   └── ExampleTest.php
├── Unit/         # 单元测试
│   └── ExampleTest.php
└── TestCase.php
```

* **`Feature/`**：功能测试。用于包含 HTTP 请求的较大粒度测试。适合验证多对象协作、API 端点等接近整体的行为。**多数测试都会写在这里。**
* **`Unit/`**：单元测试。用于单个类或方法等小粒度测试。此处不会启动 Laravel 应用，因此不能使用数据库或其他框架功能。

## 创建测试

使用 `make:test` Artisan 命令生成新的测试类。

```shell theme={null}
# 生成 Feature 测试（默认）
php artisan make:test TodoTest

# 生成 Unit 测试
php artisan make:test TodoTest --unit
```

会生成 `tests/Feature/TodoTest.php`。

## 运行测试

使用 `php artisan test` 命令。

```shell theme={null}
php artisan test
```

也可以直接运行 `vendor/bin/pest` 或 `vendor/bin/phpunit`，但 `php artisan test` 的输出更易读，推荐使用。

只运行特定套件：

```shell theme={null}
# 只运行 Feature 测试
php artisan test --testsuite=Feature

# 遇到失败立即停止
php artisan test --stop-on-failure
```

## 基本测试写法

简单断言的示例：

<CodeGroup>
  ```php Pest theme={null}
  <?php

  test('true 应为 true', function () {
      expect(true)->toBeTrue();
  });

  test('字符串是否包含', function () {
      $message = 'Hello, Laravel!';

      expect($message)->toContain('Laravel');
  });
  ```

  ```php PHPUnit theme={null}
  <?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);
      }
  }
  ```
</CodeGroup>

### 常用断言

| 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 请求并校验响应。

<CodeGroup>
  ```php Pest theme={null}
  <?php

  test('显示首页', function () {
      $response = $this->get('/');

      $response->assertStatus(200);
  });
  ```

  ```php PHPUnit theme={null}
  <?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);
      }
  }
  ```
</CodeGroup>

### ToDo 应用的 HTTP 测试示例

以带有 `Todo` 模型的应用为例，测试各 CRUD 操作。

<CodeGroup>
  ```php Pest theme={null}
  <?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 PHPUnit theme={null}
  <?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]);
      }
  }
  ```
</CodeGroup>

### 常用响应断言

| 方法                                      | 说明          |
| --------------------------------------- | ----------- |
| `assertStatus(200)`                     | 校验 HTTP 状态码 |
| `assertOk()`                            | 状态码为 200    |
| `assertRedirect('/path')`               | 校验重定向目标     |
| `assertSee('文本')`                       | 响应体包含指定文本   |
| `assertDontSee('文本')`                   | 响应体不包含      |
| `assertJson([...])`                     | 校验 JSON 数据  |
| `assertDatabaseHas('table', [...])`     | 校验数据库存在记录   |
| `assertDatabaseMissing('table', [...])` | 校验数据库无记录    |

<Info>
  使用 `RefreshDatabase` trait 时，每次测试执行后都会重置数据库，避免测试间数据互相干扰。
</Info>

## 测试环境

### `phpunit.xml` 配置

在项目根目录的 `phpunit.xml` 中配置测试环境。默认 Session 和 Cache 使用 `array` 驱动，运行过程中不会留下数据。

```xml theme={null}
<env name="APP_ENV" value="testing"/>
<env name="CACHE_STORE" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
```

数据库通常使用 SQLite 的内存数据库，避免产生文件：

```xml theme={null}
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
```

### `.env.testing` 文件

在项目根目录创建 `.env.testing` 文件，测试执行时会替代 `.env` 被读取。
适合分离测试专用的数据库连接或外部服务配置。

```ini theme={null}
APP_ENV=testing
DB_CONNECTION=sqlite
DB_DATABASE=:memory:
```

<Warning>
  如果启用了配置缓存，请先运行 `php artisan config:clear` 再跑测试。旧缓存可能导致配置未生效。
</Warning>

## 并行测试

测试数量增多时，运行时间也会变长。
可用 `--parallel` 选项启动多个进程并行执行，从而缩短耗时。

首先安装 `brianium/paratest`：

```shell theme={null}
composer require brianium/paratest --dev
```

然后添加 `--parallel`：

```shell theme={null}
php artisan test --parallel
```

默认使用 CPU 核心数个进程。可用 `--processes` 指定：

```shell theme={null}
php artisan test --parallel --processes=4
```

<Tip>
  并行测试时，每个进程都需要独立的数据库。搭配 `RefreshDatabase` trait 与 `phpunit.xml` 中的内存 SQLite 就能顺利运行。
</Tip>

## 下一步

<Card title="HTTP 测试" icon="arrow-right" href="/zh/http-tests">
  查看请求模拟、认证测试、JSON 断言等更详细的 HTTP 测试方法。
</Card>


## Related topics

- [HTTP 测试](/zh/http-tests.md)
- [使用 Pest 进行进阶测试](/zh/advanced/testing-pest.md)
- [Eloquent 工厂](/zh/eloquent-factories.md)
- [Laravel Nightwatch 入门](/zh/blog/nightwatch-introduction.md)
- [测试](/zh/packages/laravel-bluesky/testing.md)
