【PHPUnit】PHPで構築したWebアプリの単体テストを自動化する

# ①自動化に踏み切った経緯

　社内でPHP案件に携わった際、品質確認のために単体テスト～統合テストを行うことになったが、これらを部分改修ごとに手動で実行するのは大変手間であるため、テストの自動化を図った。
　単体テスト以外の自動化はフロントエンド側の操作が絡み、別のツール(Seleniumなど)との連携が必須であるため、今回は単体テストのみに絞って自動化を行う。


# ②環境

- CentOS 6.5
- PHP 5.6.33
- PHPUnit 5.7.21


# ③自動化手順

1. PHPUnitを入手
   - 現環境のPHPバージョンに合ったPHPUnitのパッケージをダウンロードすること
   - 例：PHPのバージョンが5.6の場合

       ```
       # PHPUnitの入手
       $ wget https://phar.phpunit.de/phpunit-5.7.21.phar
       $ chmod +x phpunit-5.7.21.phar
       $ sudo mv phpunit-5.7.21.phar /usr/local/bin/phpunit

       # PHPUnitバージョンの確認
       $ phpunit --version
         PHPUnit 5.7.21 by Sebastian Bergmann and contributors.
       ```

2. PHPUnitでテストコードを書く
    - 参考サイトを見ながら、必要な分だけ記述していく。
      - 共通部分
      
         ```php:User.class.Test.php
        <?php
        // テストを行うクラスファイルを読み込む
        require_once("User.class.php");
    
        // 単体テスト実施用クラス本体
        class UserTest extends \PHPUnit_Framework_TestCase
        {

            // テスト対象のオジェクト(今回はUserオブジェクト)
            private $target = null;

            // 単体テスト実施前に行う処理を記述
            public function setUp()
            {
                // テストを行うクラスのオブジェクトを作成
                $this->target = new User();
            }

        /*
         * 以下、単体テスト項目を記述
         */

        }
        ?>
         ```
       - テスト項目の実装
         - 「テスト対象メソッドを実行した結果が想定値と一致しているかどうかを、"assertEquals"で確認する」という構成が基本。 
         - 下記メソッドを、実施するテストの分だけ作成する。
         
          ```php:User.class.Test.php
          /* 
           * テスト用メソッド名のフォーマット
           *   test[対象メソッド名][True or False]
           *      True: 想定どおりの入力が行われ、正常終了するテスト
           *      False: 想定外の入力が行われるが、正常終了するテスト
           *      Error: 想定外/内に関わらず、異常終了するテスト
           */
          public function testIsUserTrue()
          {
              /*
               * assertEqualsの使用方法
               * assertEquals (引数1, 引数2)
               *   引数1: 想定している値
               *   引数2: 実際にメソッドが出力した値
               */
              $this->assertEquals(true, $this->target->is_user($this->test_id, '1'));
          }

          ```

3. テストの実行
   - 「phpunit [実施するテストファイル名]」で実行。
   - ErrorおよびWarningが発生しなければ、単体テストが全て正常終了したということ。
   - 実行および出力例を下記に示す。

     ```
     # 実行
     $ phpunit User.class.Test.php

     # 出力
     PHPUnit 5.7.21 by Sebastian Bergmann and contributors.
     .........
     Time: 199 ms, Memory: 3.75Mb
     
     OK (9 tests, 9 assertions)
     ```

# ④テストコードのメリット / デメリット

- メリット
  - 一度作成してしまえば、コマンド一つで何度でも全メソッドに対するテストが可能なため、**継続的に開発・テストを繰り返すプロジェクトに有効**
  - 一見正常に動作しているように見えるバグも可視化されるため、バグの早期発見/対処が可能
  - 実装されたクラスの正当性をメソッド単位で保証

- デメリット
  - テストコードの導入が少し手間
  - テストコードの作成 / 維持に対して、アプリ開発と同等もしくはそれ以上の工数がかかるため、**実装 / 改修が一度きりのプロジェクトの場合はコストパフォーマンスが低い**
  - コーディング規約が明確でない場合、他のメンバーが書いたテストコードのカバレッジを把握しづらい

# その他、テスト実施で楽するためのキーワード
  - Selenium
    - Webアプリケーション向けテスティングツール
    - 実施したテストをソースコードとして表現できる
    - 対応言語：C# / Java / Perl / PHP / Python / Ruby
  - JUnit
    - Java用のテスティングフレームワーク
    - PHPにおけるPHPUnitのようなもの
    - 対応言語：Java
  - Jenkins
    - オープンソースのCIプラットフォーム
    - 上記Seleniumと併用すると、Webアプリケーションのテストも可能
    - 対応言語：Java / PHP / Perl / javascript等
  - CircleCI
    - CIプラットフォーム提供サービス
    - 「Webサービス」なので導入が楽(登録するだけで使える)
    - 対応言語：(縛り無し)

# 使ってみた所感

　初回こそ面倒なものの、一度作成さえしてしまえばテストコード改修/実施共にとても楽になる。

　また、(PHPUnitに限定した話ではないが)作成したテストコードの内容を見返すことによって、クラス内メソッドのバグを早期発見/対処できるため、開発アプリの大小に関わらず非常に有用。


　DB操作を行うクラスの場合は、PHPUnit内でmockを利用できるそうなので、次回以降の開発では利用していきたい。

　
# 参考サイト
  - [PHPUnit入門の入門](https://simple-it-life.com/2016/02/25/phpunit/)
  - [PHPUnitとPHPのバージョン対応表](https://blog.shimabox.net/2016/09/15/phpunit-php-support-version/
)