PHPUnit入門①
ユニットテストと呼ばれるテストの書き方を解説します。ユニットテストとは「機能」に焦点を当てて行うテストです。具体的には関数やメソッドの返り値をテストすることになります。
テストを書くメリットは一度テストを書いてしまえば何度でもテストを自動で実行できて、関数やメソッドの中身を修正する時に修正の前後で結果が変わっていない(意図した通りである)ことが保証できることです。動いているプログラムを修正することは大変勇気のいることで、テストプログラムは開発者にとって大変心強い味方になります。
筆者の開発環境
PC:Apple M1 チップ搭載MacBook Air
OS:macOS Ventura 13.6
MAMP:6.8
PHP:8.2.0
PHPUnit:10.4.1
事前準備
PHPUnitのインストールにComposerを使用しますので、下記の記事を参考に、phpコマンドにパスを通し、Composerをインストールしておいてください。
PHPUnitのインストール
まずプロジェクトフォルダを作成します。MAMPのデフォルトドキュメントルートの直下に作成します。下記のコマンドを実行しドキュメントルートに移動してください。
cd /Applications/MAMP/htdocs
下記のコマンドを実行し、プロジェクトフォルダを作成してください。
mkdir phpunit
下記のコマンドを実行し、プロジェクトフォルダに移動してください。
cd phpunit
下記のコマンドを実行し、PHPUnitをインストールしてください。
composer require --dev phpunit/phpunit
インストールが完了したらcomposer.lockとvendorフォルダが作成されていることを確認してください。
ls -l
念のため、バージョンを確認しましょう。PHPUnitをインストールするとphpunitコマンドも一緒にインストールされます。下記のコマンドを実行してください。
./vendor/bin/phpunit --version
2023年10月16日時点では「10.4.1」がインストールされます。
PHPUnit 10.4.1 by Sebastian Bergmann and contributors.
テスト結果を見やすくするため下記の設定ファイルを作成してください。
touch phpunit.xml
作成できたらお使いのエディターで下記のように編集してください。
<phpunit colors="true"></phpunit>
編集できたら保存してファイルを閉じてください。プロジェクトフォルダはそのままエディターで開いておいてください。
テスト対象のプログラムを作成する
今回は四則演算を行うCalculatorクラスを作成します。下記のコマンドを実行し、プログラムを格納するフォルダを作成してください。
mkdir src
下記のコマンドを実行し、新しいファイルを作成してください。
touch src/Calculator.php
src/Calculator.phpファイルをエディターで開いてください。下記のように編集してください。
<?php
namespace Src;
class Calculator
{
/**
* 足し算
*
* @param integer $number1
* @param integer $number2
* @return integer
*/
public function add(int $number1, int $number2): int
{
return $number1 + $number2;
}
}
テストファイルの作成
下記のコマンドを実行し、テストファイルを格納するフォルダを作成してください。
mkdir tests
下記のコマンドを実行し、テストファイルを作成してください。必ず●●●Test.phpという命名規則になるようにします。
touch tests/CalculatorTest.php
tests/CalculatorTest.phpファイルをエディターで開いてください。下記のように編集してください。
<?php
use PHPUnit\Framework\TestCase;
use Src\Calculator;
require_once __DIR__ . '/../src/Calculator.php';
class CalculatorTest extends TestCase
{
public function test引数1と引数2の和が返ること(): void
{
$calculator = new Calculator();
$ret = $calculator->add(3, 4);
$this->assertSame(7, $ret);
}
}
ポイントがいくつかあります。
TestCaseクラスを必ず継承すること
メソッド名はtest●●●で始めること
メソッド名に日本語が使用可能
TestCaseクラスを継承することにより便利なテスト用メソッドを使うことができるようになります。
メソッド名に日本語が使えますのでテストの内容を表すようにすると後から見返した時にどんなテストか分かって便利です。
assertSame()メソッドは第1引数と第2引数が(型も含めて)同じであることを検証します。
それではテストを実行してみましょう。下記のコマンドを実行してください。
./vendor/bin/phpunit tests/CalculatorTest.php
下記のような結果が表示されればテストは成功です。
. 1 / 1 (100%)
Time: 00:00.008, Memory: 8.00 MB
OK (1 test, 1 assertion)
下記のようにエラーが報告されれば「src/Calculator.php」と「tests/CalculatorTest.php」をよく見直してください。
E 1 / 1 (100%)
Time: 00:00.014, Memory: 8.00 MB
There was 1 error:
1) CalculatorTest::test引数1と引数2の和が返ること
Error: Call to undefined method CalculatorTest::assertSama()
/Applications/MAMP/htdocs/phpunit/tests/CalculatorTest.php:14
ERRORS!
Tests: 1, Assertions: 0, Errors: 1.
下記のようにメソッドの前にアノテーションと呼ばれるコメントを入れることでテストに細工ができます。下記のように「@test」と記述するとメソッドのプレフィクス「test」を省略することができます。本稿では以降「@test」を用いてテストメソッドであることを表すようにします。
/**
* @test
*/
public function 引数1と引数2の和が返ること(): void
{
$calculator = new Calculator();
$ret = $calculator->add(3, 4);
$this->assertSame(7, $ret);
}
Calculatorクラスを完成させる
src/Calculator.phpを下記の様に編集してください。
<?php
namespace Src;
class Calculator
{
/**
* 足し算
*
* @param integer $number1
* @param integer $number2
* @return integer
*/
public function add(int $number1, int $number2): int
{
return $number1 + $number2;
}
/**
* 引き算
*
* @param integer $number1
* @param integer $number2
* @return integer
*/
public function sub(int $number1, int $number2): int
{
return $number1 - $number2;
}
/**
* 掛け算
*
* @param integer $number1
* @param integer $number2
* @return integer
*/
public function multi(int $number1, int $number2): int
{
return $number1 * $number2;
}
/**
* 割り算
*
* @param integer $number1
* @param integer $number2
* @return integer
*/
public function div(int $number1, int $number2): int
{
return $number1 / $number2;
}
}
テストファイルを修正
tests/CalculatorTest.phpを下記のように編集してください。各メソッドに対応したテストメソッドを追加しました。
<?php
use PHPUnit\Framework\TestCase;
use Src\Calculator;
require_once __DIR__ . '/../src/Calculator.php';
class CalculatorTest extends TestCase
{
/**
* @test
*/
public function 引数1と引数2の和が返ること(): void
{
$calculator = new Calculator();
$ret = $calculator->add(3, 4);
$this->assertSame(7, $ret);
}
/**
* @test
*/
public function 引数1と引数2の差が返ること(): void
{
$calculator = new Calculator();
$ret = $calculator->sub(4, 3);
$this->assertSame(1, $ret);
}
/**
* @test
*/
public function 引数1と引数2の積が返ること(): void
{
$calculator = new Calculator();
$ret = $calculator->multi(4, 3);
$this->assertSame(12, $ret);
}
/**
* @test
*/
public function 引数1を引数2で割った商が返ること(): void
{
$calculator = new Calculator();
$ret = $calculator->div(6, 3);
$this->assertSame(2, $ret);
}
}
修正できたら下記のコマンドを実行してください。
./vendor/bin/phpunit tests/CalculatorTest.php
全てのテストがパス(成功)すればOKです。
setUpメソッド
各テストメソッドが実行される前に必ず実行されるメソッドを定義できます。テストの前提条件を整えたりするのに便利です。今回は$calculatorインスタンスを作成する処理が各メソッドで重複していますので切り出してみましょう。下記のようにtests/CalculatorTest.phpを編集してください。注意点として、必ず親クラスのsetUp()メソッドを呼び出すようにしてください。
<?php
use PHPUnit\Framework\TestCase;
use Src\Calculator;
require_once __DIR__ . '/../src/Calculator.php';
class CalculatorTest extends TestCase
{
/**
* @var Calculator
*/
private $calculator;
/**
* 各テストメソッドの前に呼ばれる
*
* @return void
*/
public function setUp(): void
{
// 親クラスのsetUp()メソッドを呼び出すこと
parent::setUp();
$this->calculator = new Calculator();
}
/**
* @test
*/
public function 引数1と引数2の和が返ること(): void
{
$ret = $this->calculator->add(3, 4);
$this->assertSame(7, $ret);
}
/**
* @test
*/
public function 引数1と引数2の差が返ること(): void
{
$ret = $this->calculator->sub(4, 3);
$this->assertSame(1, $ret);
}
/**
* @test
*/
public function 引数1と引数2の積が返ること(): void
{
$ret = $this->calculator->multi(4, 3);
$this->assertSame(12, $ret);
}
/**
* @test
*/
public function 引数1を引数2で割った商が返ること(): void
{
$ret = $this->calculator->div(6, 3);
$this->assertSame(2, $ret);
}
}
編集できたらテストを実行してください。
./vendor/bin/phpunit tests/CalculatorTest.php
例外発生のテストを追加
0で割るとエラーが発生します。想定通りのエラーが発生したかテストします。下記のようにtests/CalculatorTest.phpを編集してください。
<?php
use PHPUnit\Framework\TestCase;
use Src\Calculator;
require_once __DIR__ . '/../src/Calculator.php';
class CalculatorTest extends TestCase
{
(省略)
/**
* @test
*/
public function 引数1を引数2で割った商が返ること(): void
{
$ret = $this->calculator->div(6, 3);
$this->assertSame(2, $ret);
}
/**
* @test
*/
public function ゼロで割った場合例外が発生すること(): void
{
$this->expectException(DivisionByZeroError::class);
$this->calculator->div(6, 0);
}
}
ポイントは例外発生のテストはテストしたいメソッドを呼ぶ前に書いておくことです。expectException()メソッドを使います。
編集できたらテストを実行してみましょう。
./vendor/bin/phpunit tests/CalculatorTest.php
データプロバイダー
メソッドに渡す引数をいろいろ変えてテストしたいケースもあるかと思います。下記のように必要な数だけアサーションを並べてもよいのですが、あまりスマートではありません。
/**
* @test
*/
public function 引数1と引数2の和が返ること(): void
{
$this->assertSame(7, $this->calculator->add(3, 4));
$this->assertSame(12, $this->calculator->add(5, 7));
$this->assertSame(3, $this->calculator->add(4, -1));
}
そこでデータプロバイダーという仕組みを使います。下記のようにtests/CalculatorTest.phpを編集してください。
<?php
use PHPUnit\Framework\TestCase;
use Src\Calculator;
require_once __DIR__ . '/../src/Calculator.php';
class CalculatorTest extends TestCase
{
/**
* @var Calculator
*/
private $calculator;
/**
* 各テストメソッドの前に呼ばれる
*
* @return void
*/
public function setUp(): void
{
$this->calculator = new Calculator();
}
public static function 足し算DataProvider(): array
{
return [
[3, 4, 7],
[1, 1, 2],
[3, -1, 2],
[4, 0, 4],
[5, 9, 14],
];
}
/**
* @test
* @dataProvider 足し算DataProvider
*/
public function 引数1と引数2の和が返ること(int $number1, int $number2, int $expected): void
{
$ret = $this->calculator->add($number1, $number2);
$this->assertSame($expected, $ret);
}
(省略)
}
アノテーションに「@dataProvider <メソッド名>」と記述すると、テストメソッドの引数にデータプロバイダーメソッドの返り値の配列の要素が1つずつ渡されます。下記のようにデータが「引数1と引数2の和が返ることメソッド」に渡ってきます。データプロバイダーのメソッド名にも日本語が使えます。
public function 引数1と引数2の和が返ること(int $number1 = 3, int $number2 = 4, int $expected = 7): void
public function 引数1と引数2の和が返ること(int $number1 = 1, int $number2 = 1, int $expected = 2): void
public function 引数1と引数2の和が返ること(int $number1 = 3, int $number2 = -1, int $expected = 2): void
public function 引数1と引数2の和が返ること(int $number1 = 4, int $number2 = 0, int $expected = 4): void
public function 引数1と引数2の和が返ること(int $number1 = 5, int $number2 = 9, int $expected = 14): void
編集できたらテストを実行してみましょう。
./vendor/bin/phpunit tests/CalculatorTest.php
引き算 掛け算、割り算のテストにもデータプロバイダーを適用してみましょう。下記のようにtests/CalculatorTest.phpを編集してください。
<?php
use PHPUnit\Framework\TestCase;
use Src\Calculator;
require_once __DIR__ . '/../src/Calculator.php';
class CalculatorTest extends TestCase
{
/**
* @var Calculator
*/
private $calculator;
/**
* 各テストメソッドの前に呼ばれる
*
* @return void
*/
public function setUp(): void
{
$this->calculator = new Calculator();
}
public static function 足し算DataProvider(): array
{
return [
[3, 4, 7],
[1, 1, 2],
[3, -1, 2],
[4, 0, 4],
[5, 9, 14],
];
}
/**
* @test
* @dataProvider 足し算DataProvider
*/
public function 引数1と引数2の和が返ること(int $number1, int $number2, int $expected): void
{
$ret = $this->calculator->add($number1, $number2);
$this->assertSame($expected, $ret);
}
public static function 引き算DataProvider(): array
{
return [
[4, 3, 1],
[1, 1, 0],
[3, -1, 4],
[4, 0, 4],
[5, 9, -4],
];
}
/**
* @test
* @dataProvider 引き算DataProvider
*/
public function 引数1と引数2の差が返ること(int $number1, int $number2, int $expected): void
{
$ret = $this->calculator->sub($number1, $number2);
$this->assertSame($expected, $ret);
}
public static function 掛け算DataProvider(): array
{
return [
[4, 3, 12],
[1, 1, 1],
[3, -1, -3],
[4, 0, 0],
[-5, -9, 45],
];
}
/**
* @test
* @dataProvider 掛け算DataProvider
*/
public function 引数1と引数2の積が返ること(int $number1, int $number2, int $expected): void
{
$ret = $this->calculator->multi($number1, $number2);
$this->assertSame($expected, $ret);
}
public static function 割り算DataProvider(): array
{
return [
[6, 3, 2],
[1, 1, 1],
[3, -1, -3],
[-8, -2, 4],
];
}
/**
* @test
* * @dataProvider 割り算DataProvider
*/
public function 引数1を引数2で割った商が返ること(int $number1, int $number2, int $expected): void
{
$ret = $this->calculator->div($number1, $number2);
$this->assertSame($expected, $ret);
}
/**
* @test
*/
public function ゼロで割った場合例外が発生すること(): void
{
$this->expectException(DivisionByZeroError::class);
$this->calculator->div(6, 0);
}
}
編集できたらテストを実行してみましょう。
./vendor/bin/phpunit tests/CalculatorTest.php
全てのテストがパスすればOKです。
解説は以上です。おつかれさまでした。
PHP/Laravelのシステム開発は株式会社パパグラムへぜひご相談ください。