---
title: "PestPHPとは？PHPUnitとの違い・導入手順と使い方｜Pest 5.3を実測で解説"
url: "https://www.issoh.co.jp/tech/details/7255/"
published: 2025-06-16
updated: 2026-10-20
categories: ["テスト"]
publisher: "株式会社一創"
---

# PestPHPとは？PHPUnitとの違い・導入手順と使い方｜Pest 5.3を実測で解説

PestPHP（パッケージ名`pestphp/pest`）は、PHPUnitの上に関数ベースの記法を重ねたPHPのテストフレームワークです。テストクラスを定義せず`it()`と`expect()`だけで書けるので記述量が減り、実行基盤とアサーションはPHPUnitのものをそのまま使います。この記事では2026年10月20日時点の最新版v5.3.3を素のPHPプロジェクトへ導入し、各コマンドの出力と終了コードを確かめながら、導入のつまずき、PHPUnitとの違い、Pest 5の新機能の効きどころを説明します。

## まとめ：PestPHPの要点

- **最新版はv5.3.3**（Packagist公開は2026年10月18日）。PHP `^8.4`とPHPUnit `^13.4.3`を要求します。PHP 8.3ではv4系、8.2ではv3系が上限です。
- **PestはPHPUnitを置き換えず、包みます。**判定とテストランナーはPHPUnitで、Pestが足すのは記法・CLI・プラグインです。既存のPHPUnitテストもそのまま実行できます。
- **素のPHPプロジェクトでは、`./vendor/bin/pest --init`が`tests`ディレクトリ不在で失敗し、初回のテスト実行もオートロード未設定で止まります。**Laravel 13のスケルトンではこの2つは起きませんが、PHPUnit 12の制約を残したまま版を指定せずに追加すると、Pest 5ではなくv4.7.8が入りました。
- **CIで品質を担保するなら終了コードを確認してください。**`--mutate`は未検出の変異が残っても終了コード0で、`--min`を付けて初めて失敗します。スナップショットも初回はローカルで素通りします。
- **Pest 5の目玉Tia Engineは大きなスイート向けです。**14件の検証用スイートでは、再生（1.74秒）が通常実行（0.47秒）より遅くなりました。公式もCIの通常コマンドへの`--tia`付与を避けるよう明記しています。

## PestPHPの基本｜PHPUnitを包む関数ベースのテストフレームワーク

Pestのテストファイルにはクラス宣言がありません。ファイル内でグローバル関数を呼ぶと、Pestがそれを実行時にPHPUnitのテストケースへ変換します。作者はNuno Maduro氏で、ライセンスはMITです（`composer.json`の記載）。

### test()・it()・describe()の書き分け

`test()`が基本形で、`it()`は出力の先頭に「it」が付く別名です。`describe()`は関連するテストを入れ子にまとめます。`beforeEach()`で用意した値は`$this`のプロパティ経由で各テストから参照します。

```
<?php

use App\Cart;

beforeEach(function () {
    $this->cart = new Cart();
});

it('adds an item', function () {
    $this->cart->add('SKU-1');
    expect($this->cart->count())->toBe(1);
});

test('same sku is merged', function () {
    $this->cart->add('SKU-1', 2);
    $this->cart->add('SKU-1', 3);
    expect($this->cart->items())->toBe(['SKU-1' => 5]);
});

describe('count', function () {
    it('starts at zero', function () {
        expect($this->cart->count())->toBe(0);
    });
});
```

v5.3.3で実行すると、`describe()`の中のテストは「count → it starts at zero」と親子を矢印でつないで表示されます。

```
   PASS  Tests\Unit\CartTest
  ✓ it adds an item                                                      0.01s
  ✓ same sku is merged
  ✓ count → it starts at zero

  Tests:    3 passed (3 assertions)
```

### メジャー版とPHP・PHPUnit要件・サポート期限の対応

PestはPHPUnitのメジャー版に追随するため、Pestを上げるとPHPの下限も上がります。次の表は[Packagistのpestphp/pest](https://packagist.org/packages/pestphp/pest)に登録された`require`と、公式の[サポートポリシー](https://pestphp.com/docs/support-policy)のバグ修正期限を並べたものです（2026年10月20日取得）。

| Pest              | 公開日(Packagist) | PHP要件  | PHPUnit要件 | バグ修正期限         |
| ----------------- | -------------- | ------ | --------- | -------------- |
| v3.0.0            | 2024-09-09     | ^8.2.0 | ^11.3.4   | 2026-08-21（終了） |
| v4.0.0            | 2025-08-20     | ^8.3.0 | ^12.3.5   | 2027-07-28     |
| v4.7.8（確認時点の4系最新） | 2026-08-03     | ^8.3.0 | ^12.5.33  | 2027-07-28     |
| v5.0.0            | 2026-07-24     | ^8.4   | ^13.2.4   | 未定             |
| v5.3.3            | 2026-10-18     | ^8.4   | ^13.4.3   | 未定             |

PHP 8.2の環境で使えるv3系は、2026年8月21日でバグ修正の対象から外れました。8.2のまま運用しているプロジェクトは、PHPを8.3以上へ上げる計画とセットでPestの版を決めてください。また各版は`conflict`で対応するPHPUnitのパッチ版より新しいものを除外しており、v5.3.3も13.4.3より新しいPHPUnitを受け付けません。PHPUnitだけを先に上げることはできない設計です。

## 導入手順｜composer requireから最初のテスト実行まで

[公式のインストール手順](https://pestphp.com/docs/installation)は次の3行です。`--with-all-dependencies`は、PHPUnitを含む依存パッケージの版もまとめて動かすためのオプションです。

```
composer remove phpunit/phpunit
composer require pestphp/pest --dev --with-all-dependencies
./vendor/bin/pest --init
```

v5.3.3を入れると、本体と同時に`pest-plugin-arch`、`pest-plugin-mutate`、`pest-plugin-profanity`の3プラグインと、並列実行に使う`brianium/paratest`（7.27.2）が入りました。アーキテクチャテストとミューテーションテストは追加のインストールなしで使えます。

### 初期化と初回テスト実行で起きる2つのエラーと回避手順

`tests`ディレクトリと`Tests\`のオートロード設定が無い素のPHPプロジェクトでは、v5.3.3でも初期化時と初回のテスト実行時に、それぞれ別のエラーが起きます。1つ目は`tests`ディレクトリの不在です。`--init`はテストディレクトリを作るコマンドですが、その前段のブートストラップがディレクトリの存在を確かめるため先に落ちます。終了コードは1でした（v5.1.4では255）。

```
$ ./vendor/bin/pest --init

   Pest\Exceptions\FatalException

  The test directory [/private/tmp/pestv/lab/tests/] does not exist.

  at vendor/pestphp/pest/src/Bootstrappers/BootFiles.php:40
$ echo $?
1
```

回避は先に`mkdir tests`を打つことです。これで`phpunit.xml`、`tests/Pest.php`、`tests/TestCase.php`、`tests/Unit/ExampleTest.php`、`tests/Feature/ExampleTest.php`の5ファイルができ、終了コードは0になります。

2つ目は、生成された`tests/Pest.php`が`Tests\TestCase`を参照しているのに、`--init`が`composer.json`へ`Tests\`名前空間を登録しない点です。初期化直後に`./vendor/bin/pest`を打つと、テストが1件も走らないまま終了コード1で止まります。

```
   Pest\Exceptions\TestCaseClassOrTraitNotFound

  The class or trait [Tests\TestCase] could not be found. Please check the name, and make sure it is autoloadable.
```

`composer.json`に次を足し、`composer dump-autoload`を実行すれば解消します。`autoload`は後述のテスト対象`App\Cart`を`src`から読み込むための設定です。

```
"autoload": {
    "psr-4": {
        "App\\": "src/"
    }
},
"autoload-dev": {
    "psr-4": {
        "Tests\\": "tests/"
    }
}
```

### Laravel 13のスケルトンでPest 4が入る理由とPest 5への上げ方

Laravelのスケルトンは`tests`ディレクトリと`Tests\`の登録を最初から持つため、上の2つのエラーは起きません。ただし、前掲の`composer remove phpunit/phpunit`を省いてPestを追加すると、版で引っかかります。[laravel/laravelのcomposer.json](https://github.com/laravel/laravel/blob/13.x/composer.json)は`phpunit/phpunit`を`^12.5.12`で固定しており、Pest 5が要求するPHPUnit 13と両立しません。同じ制約の`composer.json`で版を指定せずに入れると、Composerは`^4.7`を選び、v4.7.8が入りました。

```
$ composer require pestphp/pest --dev -W
  - Downgrading phpunit/phpunit (12.5.40 => 12.5.33)
  - Installing pestphp/pest (v4.7.8)
Using version ^4.7 for pestphp/pest
```

この状態から`pestphp/pest:^5.3`だけを指定しても「conflicts with your root composer.json require (^12.5.12)」で失敗します。PHPUnitを先に外すか、PHPUnitの制約を同時に書き換えます。

```
composer require "pestphp/pest:^5.3" "phpunit/phpunit:^13.4" --dev -W
```

このコマンドでv4.7.8からv5.3.3へ上がり、arch・mutate等のプラグインも5系へ揃いました。`Pest\Laravel\get()`のような関数形式のヘルパは`pestphp/pest-plugin-laravel`が提供し、最新はv5.0.1で、PHP ^8.4・Pest ^5.0.1に加えてLaravel Framework ^13.23.0を要求します。Pest 4のまま使う場合はプラグインも4系に揃えてください。

## 基本構文｜expect・フック・データセット・実行対象の絞り込み

Pestの記法は`test`、`it`、`describe`、`expect`、`beforeEach`、`afterEach`、`beforeAll`、`afterAll`、`dataset`、`pest`、`covers`、`mutates`などのグローバル関数で構成されます。

### expect()のチェーンと例外・高階テスト

`expect()`は値を受け取り、`toBe()`、`toBeArray()`、`toHaveCount()`、`toHaveKey()`などの期待値メソッドを連結できます。例外は、実行対象のクロージャを`expect()`へ渡し、`toThrow()`に例外クラスとメッセージを指定して検証します。

```
expect($this->cart->items())
    ->toBeArray()
    ->toHaveCount(2)
    ->toHaveKey('SKU-2', 2);

expect(fn () => $this->cart->add('SKU-1', 0))
    ->toThrow(InvalidArgumentException::class, '数量は1以上を指定してください');
```

1行で済む検証は、クロージャを書かない高階テストにできます。`it()`の後ろへ`expect()`を直接つなぐ書き方で、v5.3.3でもそのまま通りました。

```
it('has an empty cart by default')
    ->expect(fn () => (new Cart())->items())
    ->toBeEmpty();
```

プロジェクト固有の判定は`expect()->extend()`で足せます。`--init`が作る`tests/Pest.php`にも`toBeOne`という雛形が入っています。

### フックとtests/Pest.phpの役割

`beforeEach()`と`afterEach()`は同一ファイル内の各テストの前後で動き、クロージャの`$this`はテストケースに束縛されます。`beforeAll()`と`afterAll()`はファイル単位で1回ずつ動きますが、`$this`は使えません。共有したい状態は`beforeEach()`でプロパティへ入れます。

`tests/Pest.php`はスイート全体の設定ファイルで、v5の雛形は基底テストクラスの割り当てに`pest()`を使います。旧来の`uses()`も残っていますが、新しく書くなら雛形と同じ形に揃えたほうが読みやすくなります。

```
pest()->extend(Tests\TestCase::class)->in('Feature');
```

### データセットによる複数入力の検証

インラインのデータセットはテスト定義に`->with([...])`を連結します。複数ファイルで使い回す場合は、`tests/Datasets`配下に`dataset()`で定義し、テスト側から`->with('名前')`で参照します。実行結果には入力値がそのまま出るので、どの値で落ちたかがすぐ分かります。

```
// tests/Datasets/Skus.php
dataset('skus', ['SKU-1', 'SKU-2']);

// tests/Unit/DsTest.php
it('uses a shared dataset', function (string $sku) {
    $cart = new App\Cart();
    $cart->add($sku);
    expect($cart->items())->toHaveKey($sku);
})->with('skus');
```

```
   PASS  Tests\Unit\DsTest
  ✓ it uses a shared dataset with ('SKU-1')                              0.01s
  ✓ it uses a shared dataset with ('SKU-2')

  Tests:    2 passed (2 assertions)
```

### groupによる絞り込みとtodoによるタスク管理

テストに`->group('smoke')`を連結すると`./vendor/bin/pest --group=smoke`でそのグループだけを実行でき、登録済みのグループは`--list-groups`で確認できます。アーキテクチャテストは自動的に`arch`へ入ります。後述の`covers()`を書いたファイルのテストは`__pest_mutate_only`というグループにも入るので、一覧に見慣れない名前があっても設定ミスではありません。

```
$ ./vendor/bin/pest --list-groups

   INFO  Available test groups:

 - __pest_mutate_only (8 tests)
 - arch (4 tests)
 - default (6 tests)
 - smoke (1 test).
```

`->todo()`は担当者とissue番号を名前付き引数で受け取り、`--todos`で未着手のテストだけを一覧にできます。実装前のテストを先に置いておく用途に使えます。

```
it('is planned')->todo(assignee: 'taro', issue: 42);
```

## PHPUnitとの違いと、Pestを選ばない方がよい場面

判定はPHPUnitが行うので、同じテストなら合否は変わりません。違いが出るのは書き方と、Pestが同梱する追加機能です。PHPUnit 13とPest 5.3.3の主な対応は次のとおりです。

| 項目          | PHPUnit              | Pest                       |
| ----------- | -------------------- | -------------------------- |
| テストの単位      | クラスのメソッド             | test() / it() の関数          |
| アサーション      | $this->assertSame()  | expect()->toBe()           |
| 入力の組み合わせ    | #\[DataProvider\]    | \->with() / dataset()      |
| 前後処理        | setUp() / tearDown() | beforeEach() / afterEach() |
| アーキテクチャテスト  | なし                   | 標準で同梱                      |
| ミューテーションテスト | なし（Infection等を別途）    | 標準で同梱                      |
| スナップショット    | なし                   | toMatchSnapshot()          |

### driftプラグインによるPHPUnitテストの変換

既存のPHPUnitテストは`pestphp/pest-plugin-drift`で関数ベースへ自動変換できます。[公式の移行ガイド](https://pestphp.com/docs/migrating-from-phpunit-guide)は、一部のテストは手作業での変換が必要になると断っています。`./vendor/bin/pest --drift tests/Helpers`のように対象フォルダを指定して少しずつ移すこともできます。変換しなくても、同じスイートにPHPUnitのテストクラスとPestのテストファイルが混在したまま両方実行されます。

```
composer require pestphp/pest-plugin-drift --dev
./vendor/bin/pest --drift
```

### Pestを避けたほうがよい条件

次のどれかに当てはまるなら、PHPUnitのまま運用するほうが安全です。第一に、PHPを8.2以下から当面上げられず、バグ修正期間内のPestを使いたい場合です。8.2向けのv3系はすでにバグ修正が終わっています。PHP 8.3なら、2027年7月28日までバグ修正対象のPest 4が選択肢になります。第二に、テストのソースに書かれた`#[DataProvider]`や`#[CoversClass]`などPHPUnitの属性を静的に読む独自の解析・レポートを組んでいる場合です。Pestは実行時に`->with()`から`DataProvider`を、`covers()`から`CoversClass`を生成します（v5.3.3の`TestCaseMethodFactory.php`と`TestCall.php`）。それでもソースからは属性が消えるので、ソースを読む型のツールは作り直しになります。第三に、チーム全員がPHPUnitに習熟し、レビューの観点まで統一されている場合です。記法の乗り換えがそのまま学習コストになります。

## アーキテクチャテストとスナップショットテストの使い方

### presetによる依存ルールの一括検査

アーキテクチャテストは、コードの書き方や依存の向きをテストとして固定する機能です。`arch()->preset()`には[公式](https://pestphp.com/docs/arch-testing)が用意する`php`、`security`、`laravel`、`strict`などのルール集があり、1行で有効にできます。

```
arch('app')->expect('App')->toUseStrictTypes();
arch('no debug')->expect(['dd', 'dump', 'var_dump'])->not->toBeUsed();

arch()->preset()->php();
arch()->preset()->security();
```

試しに`src`へ`md5()`を返すクラスを置くと、`security`プリセットが該当行を示して失敗しました。正当な理由で使う関数は`->ignoring('md5')`で除外できます。

```
  ⨯ preset → security
   FAILED  Tests\ArchTest > preset → security   ArchExpectationFailedException
  Expecting 'md5' not to be used on 'App\Token'.
  at src/Token.php:11
```

### スナップショットの初回作成とCI環境での失敗条件

`toMatchSnapshot()`は初回実行で値を`tests/.pest/snapshots/`配下へ保存し、2回目以降はその内容と比較します。配列は整形済みJSONとして保存されます。考え方は[JestやVitestのスナップショットテスト](/tech/details/16263/)と同じです。

```
it('matches the cart snapshot', function () {
    $cart = new Cart();
    $cart->add('SKU-1', 2);
    expect($cart->items())->toMatchSnapshot();
});
```

注意したいのは初回の扱いです。ローカルではスナップショットを作ったテストが「incomplete」になり、終了コードは0でした。一方、環境変数`CI`や`GITHUB_ACTIONS`などがある環境ではスナップショットを作らず、次のメッセージで失敗します。判定はPest本体の`src/Support/Ci.php`が19種の環境変数を見て行っています。

```
  Snapshot is missing at [tests/.pest/snapshots/Unit/ExtraTest/it_matches_the_cart_snapshot.snap]. Run Pest with --update-snapshots to create it.
```

つまり`tests/.pest/snapshots`をコミットし忘れるとCIだけが落ちます。スナップショットはテストコードと同じコミットに含め、値を意図して変えたときは`--update-snapshots`で作り直します。

## ミューテーションテストによるアサーションの検出力評価

ミューテーションテストは、対象コードに小さな変異を加えてテストを走らせ、その変異をテストが検出できるかを測ります。行カバレッジが100%でも、アサーションが甘ければ変異は素通りします。仕組みと指標の読み方は[ミューテーションテストの解説記事](/tech/details/16261/)で扱っており、ここではPestでの実測に絞ります。

### ミューテーション対象の指定とカバレッジドライバの要件

[公式ドキュメント](https://pestphp.com/docs/mutation-testing)のとおり、実行にはXdebug 3.0以上かPCOVが必要です。ドライバが無いと「Mutation testing requires code coverage to be enabled.」で止まります。対象はテストファイルの`covers()`か`mutates()`で指定し、全クラスを対象にしたいときは`--everything`を付けます。

```
covers(App\Cart::class);
```

### アサーション変更前後のミューテーションスコア比較

測定に使った実装は次の`src/Cart.php`で、テストは前掲のカート用テストに例外・チェーン・グループの検証を足した7件です。環境はmacOS上のPHP 8.5.8、Pest v5.3.3、PHPUnit 13.4.3、pcovです。

```
<?php

declare(strict_types=1);

namespace App;

final class Cart
{
    /** @var array<string, int> */
    private array $items = [];

    public function add(string $sku, int $qty = 1): void
    {
        if ($qty < 1) {
            throw new \InvalidArgumentException('数量は1以上を指定してください');
        }
        $this->items[$sku] = ($this->items[$sku] ?? 0) + $qty;
    }

    public function count(): int
    {
        return array_sum($this->items);
    }

    /** @return array<string, int> */
    public function items(): array
    {
        return $this->items;
    }
}
```

この構成では12個の変異がすべて検出され、スコアは100%でした。所要時間は通常実行の0.49秒に対し8.76秒で、変更のあったクラスに絞って回すのが現実的です。

```
  Mutating application files...
  12 Mutations for 1 Files created

   RUN  src/Cart.php
  ✓ Line 12: DecrementInteger
  ✓ Line 14: IfNegated
  ✓ Line 17: PlusToMinus
  ✓ Line 17: CoalesceRemoveLeft
  ✓ Line 28: AlwaysReturnEmptyArray
  （中略・計12件）
  Mutations: 12 tested
  Score:     100.00%
  Duration:  8.76s
```

次に、数量の合算を見ていたアサーションを`toBe(['SKU-1' => 5])`から`toHaveKey('SKU-1')`へ緩めます。テストは全件通り、`--coverage`の結果もCartクラス100.0%のままです。ところがスコアは91.67%へ落ち、検出できなかった変異が差分付きで表示されました。

```
   UNTESTED  src/Cart.php  > Line 17: CoalesceRemoveLeft - ID: 3443682a55a375de

  -        $this->items[$sku] = ($this->items[$sku] ?? 0) + $qty;
  +        $this->items[$sku] = 0 + $qty;

  Mutations: 1 untested, 11 tested
  Score:     91.67%
```

既存の値に足す処理を「常に0から足す」へ書き換えても、キーの有無しか見ないテストは落ちません。カバレッジの数字が動かないまま検出力が下がったことを、ミューテーションスコアだけが示しています。

### CIで落とすための–minと終了コード

この91.67%の状態でも、`./vendor/bin/pest --mutate`の終了コードは0でした。結果を表示するだけで、未検出の変異があってもCIは緑のままです。下限を`--min`で与えると、下回ったときに終了コード1で失敗します。

```
$ ./vendor/bin/pest --mutate --min=95

   FAIL  Mutation score below expected: 91.7 %. Minimum: 95.0 %.
$ echo $?
1
```

CIへ組み込むなら`--min`は必須と考えてください。スイートが大きい場合は`--mutate --parallel`で並列化でき、`--mutate --class='App\Models'`のように対象を絞ることもできます。

## Pest 5のTia Engine｜変更影響に基づく実行と結果の再生

Tia Engine（Test Impact Analysis）は、テストとファイルの依存グラフを記録し、以降は変更の影響を受けるテストだけを実行して残りをキャッシュから再生する機能です。[Pest 5の告知](https://pestphp.com/docs/pest5-now-available)は、10分かかっていたLaravelのスイートが約4秒で再生される例（774件中765件を再生）を挙げています。

### 依存グラフの記録と再生の実測

測定は、後述のスナップショットとpresetのテストを足す前の14件で行いました。`--tia --fresh`でグラフを記録すると、`~/.pest/tia/`配下にプロジェクトごとのディレクトリができます。変更が無ければ全件が再生扱いになり、`src/Cart.php`を1行変えると影響するテストファイルだけが実行されました。

```
$ ./vendor/bin/pest --tia
  ─ Experimental TIA mode enabled.
  Tests:    1 todo, 13 passed (18 assertions, 14 replayed)

$ ./vendor/bin/pest --tia
  ─ Experimental TIA mode enabled / 3 affected test files (from 1 changed file).
  Tests:    1 todo, 13 passed (18 assertions, 12 affected, 2 replayed)
```

ただし、この14件のスイートでは再生のほうが遅くなりました。同じ条件で5回ずつ測ると、通常実行が0.45〜0.51秒、全件を再生した`--tia`が1.65〜1.78秒です。グラフの読み込みと照合の固定費が、テスト本体の実行時間を上回っています。この測定だけでは速くなる境目までは決められないので、自分のスイートで通常実行と再生の時間を比べ、1回の変更で影響を受けるテストの割合も見てから採用を判断してください。

### カバレッジドライバの要件とCIでの扱い

pcovもXdebugも有効でない環境では依存グラフを記録できず、Pestは「Running in TIA mode, however TIA is skipped as it needs ext-pcov or Xdebug.」と1行出して全件を実行します。[公式のTiaページ](https://pestphp.com/docs/tia)は、Tia Engineはローカル開発のためのもので、CIでスイートを走らせるコマンドに`--tia`を加えるべきではないと明記しています。CIで`--tia`を使うのはベースラインを記録する専用ジョブだけで、開発者は`--tia --baselined`でそのベースラインをGitHub Actionsから取得できます。

Pest 5.0以降も機能追加が続いています。[v5.2.0](https://github.com/pestphp/pest/releases/tag/v5.2.0)でGitLab CIのベースライン対応が入り、v5.3.0では置き換えられたマージコミット間でベースラインを再利用する修正が入りました。公式ページの手順はGitHub Actions前提のままなので、GitLabで使う場合はリリースノートとソースを確認してください。ローカルだけで自動的に有効にしたいなら`--tia --locally`を使います。

## 並列実行・カバレッジ・CIの実務設定

並列実行は`--parallel`を付けるだけで有効になります。Pest 2以降は`brianium/paratest`が本体の直接の依存なので、別途プラグインは要りません。[v5.3.3](https://github.com/pestphp/pest/releases/tag/v5.3.3)では並列ワーカーのポーリング間隔が1ミリ秒へ短縮されています。

### 逐次実行と並列実行の所要時間比較

スナップショットとpresetのテストを足す前の14件（アーキテクチャテスト2件・todo 1件を含む、Tiaの実測と同じ構成）を、論理CPU 16個のMacで各5回実行した結果が次です。1件あたり数ミリ秒の軽いテストでは、プロセス起動の固定費が勝ち、16プロセスの並列は逐次の約3倍遅くなりました。プロセス数を2に絞ると差は縮まりますが、それでも逐次に届きません。

| 実行方法                   | 所要時間（5回の範囲） | 中央値   |
| ---------------------- | ----------- | ----- |
| 逐次実行                   | 0.45〜0.51秒  | 0.47秒 |
| –parallel（16プロセス）      | 1.32〜1.59秒  | 1.39秒 |
| –parallel –processes=2 | 0.77〜0.92秒  | 0.82秒 |
| –tia（全件再生）             | 1.65〜1.78秒  | 1.74秒 |

並列化が効くのは、データベースやHTTPを伴う重いテストが多いスイートです。採用前に逐次実行と比べ、遅いテストは`--profile`で上位10件を洗い出してから判断してください。

### CIでの実行と静的解析の併用

カバレッジは`--coverage`で表示し、`--coverage --min=90`で下限を割ったときにCIを失敗させられます。実行環境にはpcovかXdebugが必要です。GitHub Actionsでの組み方は[GitHub Actionsでビルド・自動テストを設定する方法](/tech/details/2497/)で扱っています。テスト・静的解析・デプロイを一続きのパイプラインにまとめる設計は、[DevOps・CI/CD導入支援](/service/system/devops/)でも支援しています。

テストで拾えるのは実行時の振る舞いだけなので、型の不整合は静的解析と併せて潰します。`pestphp/pest-plugin-phpstan`を入れると、`it()`や`expect()`などPestの関数をPHPStanに認識させられます。PHPStan自体の導入とレベルの選び方は[PHPStanとは？レベル0〜10の違いと導入・ベースライン運用](/tech/details/12338/)を参照してください。なお`--help`には`--ai`が並びますが、エージェントプラグインを入れていない状態で実行すると「Unknown option」と表示されます。

## ブラウザテストとLaravel E2Eの守備範囲

ブラウザを起動して画面を操作するテストは本記事の範囲外です。Pest 4で追加されたブラウザテストとPlaywrightの版の扱いは[Pest 4のブラウザテストの解説記事](/tech/details/10349/)で、Laravel公式のDuskを使う構成は[LaravelのテストをDuskで自動化する手順](/tech/details/5648/)で扱っています。v5.3.0ではブラウザテストに`--trace`が追加されました。

## よくある質問

### PestPHPとPHPUnitはどちらを選ぶべきですか？

PHP 8.4以上を使える新規プロジェクトで、チームがPestの記法を受け入れられるならPestを推奨します。判定はPHPUnitのままなので失うものが少なく、アーキテクチャテストとミューテーションテストが追加費用なしで手に入ります。PHPを上げられない場合や、PHPUnitの属性に依存した独自ツールを持つ場合はPHPUnitのままにしてください。

### 既存のPHPUnitのテストはPestへ移行できますか？

できます。`pestphp/pest-plugin-drift`でフォルダ単位に変換するか、変換せずに共存させます。PHPUnitのテストクラスとPestのテストファイルが混在していても、Pestは両方を実行します。

### Laravelでpestphp/pest-plugin-laravelは必要ですか？

Laravelの`Tests\TestCase`を基底クラスにしていれば、プラグインなしでも`$this->get()`などのHTTPテストやArtisanのテストは書けます。`Pest\Laravel`名前空間の関数形式のヘルパや、プラグインが追加するArtisanコマンドを使う場合に必要です。2026年10月20日時点の最新はv5.0.1で、Pest 5用です。Pest 4を使っている場合は4系のプラグインを選んでください。

### beforeEachで用意した値がテストから見えないのはなぜですか？

ローカル変数に代入していないかを確認してください。値は`$this->プロパティ名`へ代入する必要があります。次に多いのが`beforeAll()`との取り違えで、こちらは`$this`を使えません。

### Pest 5へ上げるときに確認することは何ですか？

PHPが8.4以上であること、Pestが管理するプラグインを`^5.0`へ揃えること、PHPUnitの制約を13系へ上げることの3点です。[公式のアップグレードガイド](https://pestphp.com/docs/upgrade-guide)は前の2点を挙げていますが、Laravelのスケルトンのように`phpunit/phpunit`を12系で固定している場合は3点目を省くと依存解決に失敗します。

## 関連記事

- [Pest 4のブラウザテストとは？pest-plugin-browserの導入とPlaywright版の落とし穴【4.7.8実測】](/tech/details/10349/)
- [Laravelのテスト自動化をDuskで実装する方法｜インストールからCI実行まで](/tech/details/5648/)
- [ミューテーションテストとは？ミュータント生成とスコアの読み方を実装者向けに解説](/tech/details/16261/)
- [PHPStanとは？レベル0〜10の違いと導入・ベースライン運用【2.3対応】](/tech/details/12338/)
- [GitHub Actionsでビルド・自動テストを設定する方法｜CI/CDワークフローの作り方](/tech/details/2497/)

---

出典: [PestPHPとは？PHPUnitとの違い・導入手順と使い方｜Pest 5.3を実測で解説](<https://www.issoh.co.jp/tech/details/7255/>)（株式会社一創）
