テストコードを書いて良かった3つの事

これは[ユアマイスター Advent Calendar 2019](https://qiita.com/advent-calendar/2019/yourmystar)の2日目の記事です。

# はじめに

少し時間は空いてしまいますが、先々月に技術的負債の撲滅の一環として、テストコードを書きました。
それまでしっかりとテストコードを書いたことのなかった自分としては、少しチャレンジングな内容でした。
テストコードをガッツリと書いた今だから思うことは、書いた方がいい。これに尽きます。
今回は、テストコードを書いて **良かった事** を個人の見解からいくつかお話ししたいと思います。
※悪かった事、は特に感じなかったため、良かった事にフォーカスしてます。

## 前提
### 各種バージョン
- PHP
    - 7.1.32
- CakePHP
    - 3.6.14
- PHPUnit
    - 6.5.13

### 用語
- Cake
    - CakePHPを指す

# 1. アウトプットを意識する

```php
  1 <?php
  2 class UsersTable extends Table
  3 {
  4     public function findUserById(Query $query, $options)
  5     {
  6         return $query->where(['id' => $options['id']]);
  7     }
  8 }
  9 
 10 class UsersTableTest extends TestCase
 11 {
 12     public function testFindUserById()
 13     {
 14         $actual = $this->Users->findUserById($this->Users->find(), ['id' => 1]);
 15         $this->assertInstanceOf('Cake\ORM\Query', $actual);
 16     }
 17 }
```

例えば、上記のTableクラスのpublicメソッドと、そのテストコードがあったとします。

テストコードでは、期待値がQueryオブジェクトである事をアサートしてます。

テストコードを書くときは、実行値と期待値を考えなくてはなりません。
あるメソッドに対するテストをするとき、そのメソッドを実行する事で返す（得られる）値、これが **実行値**。
逆に、そのメソッドを実行する事で〇〇という値を返すだろうと想定した値、これが **期待値** です。

この考え方を身に付ける事で、あるロジックを組むときや、ソースコードレビューする時に非常に役に立ちます。

```php
  1 <?php
  2 class UsersController extends AppController
  3 {   
  4     public function index()
  5     {   
  6         $this->loadModel('Users');
  7         
  8         $user = $this->Users->find('UserById', ['id' => 1])->all();
  9         if ($user->count() < 1) {
 10             $this->Flash->error('データが存在しません。');
 11             return $this->redirect($this->referer());
 12         }
 13     }
 14 }
```

上記は、特定のユーザー情報を取得する。もし、データが存在しなければ以前の画面へリダイレクトしてFlashメッセージを表示させるロジックです。

非常にシンプルで簡単なロジックですが、このロジックを組む時にもテストコードで培った考えが役に立ちます。

L:8で、UsersテーブルのfindUserByIdメソッドを呼び出してますが、テストコードが書かれている事・アウトプットに対する考え方、がある事で、以降の処理が直感的に書けるようになります。
findUserByIdメソッドが返す値がわかっているので、そのあとに->[all()](https://book.cakephp.org/3/ja/orm/retrieving-data-and-resultsets.html#id16)を利用してデータを取得し、L:9で、データの有無を確認する時に->[count()](https://book.cakephp.org/3/ja/orm/retrieving-data-and-resultsets.html#id5)を利用してます。
（※L:9の話は例えば、PHPの[count](https://www.php.net/manual/ja/function.count.php)メソッドを利用する事も可能ですが、Cakeを利用してる以上、ORMの[count](https://book.cakephp.org/3/ja/orm/retrieving-data-and-resultsets.html#id5)メソッドを利用することがお作法としてもコードの可読性の観点からも適切だ、と言えるでしょう）

直感的にコードを書ける事はつまり、開発効率を上げる事に繋がります。

また、ソースコードレビューの時も、例えば、PHPの[count](https://www.php.net/manual/ja/function.count.php)メソッドを利用して比較を行っていれば、ORMの[count](https://book.cakephp.org/3/ja/orm/retrieving-data-and-resultsets.html#id5)メソッドを利用する事を指摘することができると思います。

アウトプットを意識する、だけでいろんな点で恩恵を受けられました。

実は、これが１番良かった点じゃないか、と思うくらい良かった点です。



# 2. フレームワークに関する知見が深まる
上記で登場したコードを一例にお話しすると、

```php
 10 class UsersTableTest extends TestCase
 11 {
 12     public function testFindUserById()
 13     {
 14         $actual = $this->Users->findUserById($this->Users->find(), ['id' => 1]);
 15         $this->assertInstanceOf('Cake\ORM\Query', $actual);
 16     }
 17 }
```

assertInstanceOfというメソッドは、Cakeで用意されてるアサーションメソッドです。

このメソッドに辿り着くまで、実は違うメソッドを利用したアサートを行ってました。
それは、PHPUnitが用意してる[assertSame](https://phpunit.readthedocs.io/ja/latest/assertions.html#assertsame)というメソッドと、PHPの[get_class](https://www.php.net/manual/ja/function.get-class.php)メソッドを利用したコードです。

```php
$this->assertSame('Cake\ORM\Query', get_class($actual));
```

これでもやりたい事は満たしますが、ちょっと頑張りすぎというか適切ではありません。

ある一定期間このロジックで書き進めてたのですが、もっと簡単な方法ないのか？と疑問を持ち、調べていく中でassertInstanceOfメソッドを見つけました。

これをキッカケに **Cakeが用意してるコードでやりたい事を満たすコードは存在するだろう** が選択肢に増えました。
つまり、フレームワークに対する関心が高まったのです。

それ以降、やりたい事を満たすために調べるときは、まずCakeのコードやCookbookを頻繁に読むようになりました。
この行動がフレームワークに対する知見を深めました。

また、副産物的要素として、コードの読解力も上がりました。これも結果的に良かった事です。
身近なエンジニアの書いたコードを読んでいると、遭遇するコードのパターンは決まりきってくるので思考に偏りが出ます。
しかしCakeはオープンソースで、世界中のエンジニアがコミットしてます。
そういった様々な人が書いたコードを読むことで、思考の偏りは起こらず、あらゆるパターンをインプットできるので、結果、コードの読解力が上がる、といった恩恵を受けられた訳です。
読解力を上げるには、ただコードを読めば良い、という訳ではなく、様々なコードを読むことが重要、という気づきも得られました。


# 3. プログラムにおける品質の担保
これは当たり前中の当たり前ですが、やはりテストコードで品質は担保できるようになりました。
もちろん100%中、数10%が現実的な担保としての役割だとは思いますが、それでも、最低限の担保としてその役割を担ってくれるのがテストコードだと感じます。

上記で紹介したコードから一例を説明すると、

```php
  1 <?php
  2 class UsersTable extends Table
  3 {
  4     public function findUserById(Query $query, $options)
  5     {
  6         return $query->where(['id' => $options['id']]);
  7     }
  8 }
  9 
 10 class UsersTableTest extends TestCase
 11 {
 12     public function testFindUserById()
 13     {
 14         $actual = $this->Users->findUserById($this->Users->find(), ['id' => 1]);
 15         $this->assertInstanceOf('Cake\ORM\Query', $actual);
 16     }
 17 }
```

例えば、findUserByIdメソッドの中身を以下に変更したとします。

```php
ASIS:
    return $query->where(['id' => $options['id']]);

TOBE:
    return $query->where(['id' => $options['id']])->all();
```

QueryオブジェクトからUsersエンティティーを返す処理に変えたとします。

当然テストは失敗します。

あまり現実的ではないサンプルコードで申し訳ないですが、ここで理解して欲しい事は、テストコードによってアウトプットの変更を検知する事ができる、という点です。
これがつまり、質の担保、を指してます。

担保する量、の話は、テストコードの質、と比例すると自分は思っているので、ネクストチャレンジはテストコードの質を上げる事、だと個人的に思ってますが、それでもこの品質の担保、の側面を実感できた事は、テストコードを書いたからこそ気づけた事であり、良かった点だなと思います。



# まとめ
冒頭でのコメントと重なりますが、テストコードは書いた方が良い、と自分は考えます。
経験上、自身の成長に寄与する事は間違いなく、そして、技術的負債を撲滅できたり、品質の担保に寄与する事ができます。

ちょっとしたチャレンジングな出来事で、とても大きな恩恵を受ける事ができました。
結果論の話になってしまいがちですが、内容はともかく、チャレンジする事が重要ですね。