【WIP】PHPUnitで私好みのアサーションを足して「読みやすい書きやすい」テストを

@todo ちゃんと動作検証
@todo 各クラスの詳細・役割分担について書く

現在、業務でAPIアプリケーションを開発しています。
APIということで、「エラーの形式が決まって」います。
その時に、 _viewに入っている変数をとってその中の `$codeがXXX` `$sub_codeがYYY` `$messageがZZZ` みたいなアサーションを、毎回書く？3つも？それって・・・
あまり穏やかじゃないね、と感じた次第です。
※実際にはもう少し複雑。端折っていますが、大体こんなイメージです。

「同じような処理なら」「同じ書き方を」「しかも、なるべく簡潔・一目瞭然に」やるのが美しくないですか？
ということで、「エラー時のレスポンスのアサーション」を丸っとまとめてしまえば楽じゃないですか！！と思ったわけです。

## 作りたいモノ
例えば、TwitterのREST APIのレスポンスのイメージで、その内容を検査するモノ！を作ってみましょう。という想定でススメます。

```json
{
	"errors": 	[
		{"message":"Sorry, that page does not exist","code":34},
		{"message":"User not found.","code":50},
		{"message":"Sorry, you are not authorized to see this status","code": 179}
	]
}
```
※あくまで「例」ですよ！実際にはコレらのエラーが、まして同時に複数個返ってくるっけ？とか突っ込むのは野暮です。
cf: [Error Codes & Responses — Twitter Developers](https://dev.twitter.com/overview/api/response-codes)


コレに対して、

```php
$this->assertErrorCode(34, $errors, '存在しないはずのページの状態を検知できていない');
$this->assertErrorCode(50, $errors, '存在しないはずのユーザーの状態を検知できていない');
$this->assertErrorCode(179, $errors, 'アクセス資格のないリソースにアクセスできてしまっている');
```
とかやれると = レスポンスをいちいちデコードしてkey/valueチェックをして・・って書くよりシュッとするので「テストしたい内容を、コードに明瞭に落とし込める」です。

## 実装概要(PHPUnitのAPI)
公式docは以下にあります。しっかり言及されていますね
->  [カスタムアサーションの作成](https://phpunit.de/manual/current/ja/extending-phpunit.html#extending-phpunit.custom-assertions)
なるほど、どうやら `Constraint` `matcher` 及び `TestCase のサブクラス`というのがキーワードとなりそうですね。
・・とはいえ、個人的には「ちょっと駆け足気味」あるいは「ややオラついた」記述では・・・という印象を受けました。
具体例が欲しいですね。生コードを見てみましょう。

* [Constraint](https://github.com/sebastianbergmann/phpunit/tree/9758eee6421efd997a42fe850f74292a490e9c70/src/Framework/Constraint)
	* アサーションのロジックを、それぞれ独立したクラスとして定義していきます
* [`assertXxx()` の実体](https://github.com/sebastianbergmann/phpunit/blob/master/src/Framework/Assert/Functions.php)
  * これが役割的に「TestCaseのサブクラス」の代わりをしているイメージになります。


オマケ:
私はCakePHPを普段使っているものですから、Cakeでの実装例も併せて紹介しておきます

* [class EventFired extends PHPUnit_Framework_Constraint](https://github.com/cakephp/cakephp/blob/master/src/TestSuite/Constraint/EventFired.php)
* [assertEventFired()](https://github.com/cakephp/cakephp/blob/b1acd2d40ca22e05ec3c98ca96d0678a5b24af39/src/TestSuite/TestCase.php#L154)

## 実装をしてみよう
### インターフェイスとロジック
どんなものがあれば良いでしょう？
「検査対象(json文字列)」に対して「`code` `message` のそれぞれのキーが存在し」「かつ、パラメータとしてアサーションに渡された内容と合致する」ことを保証する、みたいな感じで如何でしょう。
愚直に書き下すならこんな感じに。

```php
$data = json_decode($json);
if (! $json) {
  return false;
}
if (! (property_exists($json, 'code') && property_exists($json, 'message'))) {
  return false;
}

return ($json->code === $expected_code, && $json->message === $expected_message);
```

これを「ロジック」として、アサーションメソッドのロジックはconstraintに組み込むというお作法に従って、クラスを定義してみます。
具体的には、 (`__construct()`と)`matches()``toString()`というpublicメソッドを実装しておけば :ok: ぽいです。

```php
class ApiResponse extends PHPUnit_Framework_Constraint
{
	// 検査対象となる内容を格納する
	private $data;
	
    // expectedとの比較を行う前のフェーズで「失敗」させる事も可能
    public function __constuct(string $json)
    {
    	$data = json_decode($json);
    	if (! $data) {
    		// FailedErrorを投げることで、任意のタイミングでアサーションを失敗させられる
    		throw new PHPUnit_Framework_AssertionFailedError('Failed to parse json.');
   		}
		if (! (is_object($json) && property_exists($json, 'code') && property_exists($json, 'message')) {
  			return false;
		}
		
		$this->data = $data;
    }
    
    public function matches($expected)
    {
    	// 失敗ならfalse、成功ならtrueを返すだけでOK
    	return (
    		$this->data->code === $expected['code']) &&
    		$this->data->message = $expected['message']
    	);
    }
    
    public toString()
    {
    	return sprintf('%s has not valid code and message.', json_encode($this->data));
    }
}
```

ロジックはできました。あとは「呼び出し元」です。
これが「TestCaseのサブクラス」という事になります。

```php
class AssertApiResponse extends  PHPUnit_Framework_TestCase
{
	public function assertApiResponse($code, $messsage, $content, $message = '')
	{
		$this->assertThat(compact('code', 'message'), new ApiResponse($content), $message);
	}
}
```

あとは、こんな形で取得したレスポンスに対するアサーションが可能になります。

```php
$error = $response['errors'][0];
$this->assertApiResponse(34, 'Sorry, that page does not exist', $error, '存在しないはずのページの状態を検知できていない');
```