PHPUnitで始めるユニットテスト

#目次
[ユニットテストとは](#ユニットテストとは)
[インストール方法](#インストール方法)
[簡単なテストコードの書き方と実行方法](#簡単なテストコードの書き方と実行方法)
[テスト結果の確認方法](#テスト結果の確認方法)
[複数データのテスト](#複数データのテスト)
[プライベートメソッドのテスト](#プライベートメソッドのテスト)

#ユニットテストとは
プログラムの小さな機能単位に対して行う動作確認のこと
ある操作を行った時に結果がどうなるかを
順々に試していく作業を自動化するためにある

#インストール方法
``composer``はインストールされている前提で進めていきます。
インストールしてない方はインストールしてから戻ってきてください
これは推奨されていないやり方ですが、

```php
composer global require 'phpunit/phpunit:*'
echo "export PATH=\$PATH:\$HOME/.config/composer/vendor/bin" >> ~/.bash_profile
source ~/.bash_profile
```

``phpunit -version``で確認してみてください
推奨されているやり方は以下の通りです

```php
wget https://phar.phpunit.de/phpunit-|version|.phar
chmod +x phpunit-|version|.phar
./phpunit-|version|.phar --version
```
参考
https://phpunit.readthedocs.io/ja/latest/installation.html

#簡単なテストコードの書き方と実行方法

```User.php
<?php
class User
{
	public $name;

	public __construct($name)
	{
		$this->name = $name;
	}

	public function sayHi(): string
	{
		//return 'Hi ' . $this->name;
		return 'Hi ' . $name;
	}
}
```

```UserTest.php
<?php
require __DIR__ . '/User.php';
class User extends PHPUnit\Framework\TestCasee
{
	public function testConstruct()
	{
		$user = new User('Perry');
		$this->assertEquals('Perry', $user->name);
	}

	public function testSayHi()
	{

		$user = new User('Satoru');
		$test = $user->sayHi();
		$expected = 'Hi Satoru';
		$this->assertEquals($expected, $test);
	}
}
```
これを実行するときは
``phpunit UserTest.php``とすれば良い
ディレクトリ単位でも実行することができる

実際にこれを実行すると

```php
 PHPUnit 7.5.20 by Sebastian Bergmann and contributors.

.E                                                                  2 / 2 (100%)

Time: 24 ms, Memory: 4.00 MB

There was 1 error:

1) UserTest::testCreateMessage
Undefined variable: name

/PATH/User.php:11
/PATH/UserTest.php:13

ERRORS!
Tests: 2, Assertions: 1, Errors: 1.
```
こんな感じで帰ってくる
最初の``.``はテスト通ったよってこと
その次の``E``はエラーを表している
エラーが出た場合は上のように内容が詳細に記される
最初の行ではテストメソッドの名前とエラーの個数が表示される
次の行ではエラーの内容
次に該当箇所が表示され、その次の行で、``UserTest.php``の13行目を実行した時に
エラーが発生したと丁寧に書いてくれています

それではコードを修正しましょう

```User.php
<?php
class User
{
	public $name;

	public __construct($name)
	{
		$this->name = $name;
	}

	public function sayHi(): string
	{
		return 'Hi ' . $this->name;
	}
}
```
これで実行してみましょう

```php
..                                                                  2 / 2 (100%)

Time: 22 ms, Memory: 4.00 MB

OK (2 tests, 2 assertions)
```
いい感じですね！
ちなみに文字リテラルが間違っていた場合は``E``ではなく
``F``が出力されます
スキップされた際は``S``らしいですみたことないけど！

このように``テスト実行->失敗した場合は修正->テスト``を繰り返して進めていきます
今回は``assertEqual``しか使わなかったですが、他にもたくさんあるので調べてみてください
分かりやすい記事があったので説明は省略します
https://qiita.com/rev84/items/12fbd16d210d6a86eff9
https://www.wakuwakubank.com/posts/425-php-php-unit/

#複数データのテスト
データプロバイダを使って複数のデータをまとめてテストしましょう

```Calc.php
<?php
class Calc
{
	private $number1;
	private $number2;

	public function __construct(int $number1, int $number2)
	{
		$this->number1 = $number1;
		$this->number2 = $number2;
	}

	public function calc(string $option = '+'): int
	{
		switch ($option) {
		case '+':
			$answer = $this->number1 + $this->number2;
			break;
		case '-':
			$answer = $this->number1 - $this->number2;
			break;
		case '*':
			$answer = $this->number1 * $this->number2;
			break;
		case '/':
			if ($this->number2 == 0) return false;
			$answer = $this->number1 / $this->number2;
			break;
		default:
			return false;
	        }
	        return $answer;
	}
}
```

```CalcTest.php
<?php
require_once __DIR__ . '/Calc.php';
class CalcTest extends PHPUnit\Framework\TestCase
{
	/**
	 * @dataProvider numberProvider
	 */
	public function testCalc(int $number1, int $number2, string $option, int $expected)
	{
		$calc = new Calc($number1, $number2);
		$test = $calc->calc($option);
		$this->assertEquals($expected, $test);
	}

	public function numberProvider()
	{
		return [
			[1, 2, '+', 3],
			[1, 2, '-', 3],
			[1, 2, '*', 3],
			[1, 2, '/', 3],
			[3, 4, '+', 12],
			[3, 4, '-', 12],
			[3, 4, '*', 12],
			[3, 4, '/', 12],
			[7, 8, '+', 15],
			[7, 8, '-', 15],
			[7, 8, '*', 15],
			[7, 8, '/', 15],
		];
	}
}
```
実行方法は先ほどのコマンドに``--debug``オプションをつけるだけー

実行結果は以下のようになりました

```

.FFFFF.F.FFF                                                      12 / 12 (100%)

Time: 24 ms, Memory: 4.00 MB

There were 9 failures:

1) CalcTest::testCalc with data set #1 (1, 2, '-', 3)
Failed asserting that -1 matches expected 3.

/PATH/CalcTest.php:12

2) CalcTest::testCalc with data set #2 (1, 2, '*', 3)
Failed asserting that 2 matches expected 3.

/PATH/CalcTest.php:12

3) CalcTest::testCalc with data set #3 (1, 2, '/', 3)
Failed asserting that 0 matches expected 3.

/PATH/CalcTest.php:12

4) CalcTest::testCalc with data set #4 (3, 4, '+', 12)
Failed asserting that 7 matches expected 12.

/PATH/CalcTest.php:12

5) CalcTest::testCalc with data set #5 (3, 4, '-', 12)
Failed asserting that -1 matches expected 12.

/PATH/CalcTest.php:12

6) CalcTest::testCalc with data set #7 (3, 4, '/', 12)
Failed asserting that 0 matches expected 12.

/PATH/CalcTest.php:12

7) CalcTest::testCalc with data set #9 (7, 8, '-', 15)
Failed asserting that -1 matches expected 15.

/PATH/CalcTest.php:12

8) CalcTest::testCalc with data set #10 (7, 8, '*', 15)
Failed asserting that 56 matches expected 15.

/PATH/CalcTest.php:12

9) CalcTest::testCalc with data set #11 (7, 8, '/', 15)
Failed asserting that 0 matches expected 15.

/PATH/CalcTest.php:12

FAILURES!
Tests: 12, Assertions: 12, Failures: 9.
```

データセットごとにtestCalc()メソッドが実行されていますね！
テストメソッドであるtestCalc()メソッドの直前に``@dataProvider numberProvider``と記述しましたが
これがデータプロバイダとしてメソッドを使うための指示になっているので書き忘れ厳禁

データプロバイダメソッドの``numberProvider()``メソッドはデータ配列かIteratorインターフェイスを実装したオブジェクトを返すように記述してください

フィクスチャの話
テストを実行するための事前準備をフィクスチャと呼ぶ
PHPUnitには準備用のコードを共有するために
各テストメソッドが実行される前に自動的に実行される``setUp()``メソッドや
各テストメソッドが実行された後に自動的に実行される``tearDown()``メソッドがある
#プライベートメソッドのテスト
``ReflectionMethod``クラスを使う
テストケースを記述するクラスはテストするクラスとは別なので
プライベートメソッドをそのままテストすることができない
そこで``ReflectorMethod``クラスを使うと外部からアクセス可能なように書き換えてテストしてくれるらしい
めっちゃ便利

```php
$r = new ReflectionMethod('テスト対象のクラス', 'テスト対象のメソッド');
$r->setAccessible(true);
$test = $r->invoke('テスト対象のクラスのインスタンス');
```
こんな感じ
ReflectionMethodのインスタンスを作成してメソッドのアクセス権を書き換えて
テスト対象のメソッドを呼び出して結果を取得してるー

```CalcTest.php
//@dataProvider numberProvider
public function testCalc()
{
	$r = new ReflectionMethod('Calc', 'calc');
	$r->setAccessible(true);
	$test = $r->invoke(new Calc(1, 2));
	$expected = 3;
	$this->assertEquals($expected, $test);
}
```
実行結果はこんな感じー

```php
.                                                                  1 / 1 (100%)
Time: 22 ms, Memory: 4.00 MB
OK (1 test, 1 assertion)
```

データベースのテスト方法は長くなるのでまた次の機会にまとめられたらなと思いますー
テストのカバー率やSeleniumを使ってブラウザテストもできるみたいなので
それも合わせてまとめられたらなと思ってますー