読者です 読者をやめる 読者になる 読者になる

【fuelphp】phpunitでのテスト自動化入門〜使い方メモ

php fuelphp phpunit

はじめに

ずっとやろうやろうと思ってて放置していたテストです
残念なことに自分が携わってきた案件ではこれが使われていた案件はありませんでした。

・自分は心配性な方なので割とデバッグは入念にやるためか
・たまたま案件に恵まれていただけなのか
はわかりませんがテスト不足で痛い目にあった記憶があまりありませんでした(たぶん)

けど
・お金が関わってる処理とか
・ロジックがやたら複雑になってしまっているとか
を改修するとデグレードしてないかと心配はやはり耐えません

どこかでテストコードを書く事でこの心配が減らせるみたいなことを目にしてなるほどなぁと思ったのを覚えてます

導入

今回は何故か初めからphpunitはインストールしてありましたがなければインストールする

phpunitインストール

> wget https://phar.phpunit.de/phpunit.phar
> chmod +x phpunit.phar
> sudo mv phpunit.phar /usr/local/bin/phpunit
> phpunit --version
PHPUnit 4.8.23 by Sebastian Bergmann and contributors.

composer.jsonphpunitを追加
versionはphpunitと合わせる

{
    ・・・
    "require-dev": {
        "phpunit/phpunit": "4.8.*",
        ・・・
    }
    ・・・
}

composer update

php /project_path/composer.phar update

テスト対象

簡単ですが、以下のようなコントローラをテストしてみます
・EmailとパスワードにてDBを検索して情報があればセッションに保存するコントローラ
・ログインしている場合のみ見れるページのコントローラ

fuel/app/controller/member.php

class ValidationException extends Exception {}

class Controller_Member extends Controller
{
    // ログイン処理
    public function action_login()
    {
        try {
            $email = Input::post("email");
            $password = Input::post("password");

            // 入力チェック
            if (!$email || !$password) {
                throw new ValidationException;
            }

            // Emailとパスワードで会員情報検索 ※例なので簡易版なので暗号化してない等々はお見逃しで
            $member = Model_Db_Member::getByEmailPassword($email, $password);
            if (empty($member)) {
                throw new ValidationException;
            }

            // ログインに成功したらセッションに保存                                                                                                                                                                
            Session::set("member", $member);

            // 会員ページへリダイレクト                                                                                                                                                                            
            $this->redirect("/member/index");

        } catch(ValidationException $e) {
            $view = View::forge('member/login/form);
            $view->set("error", "Emailまたはパスワードが正しくありません");
            return $view;
        }
    }

    // 会員専用ページ
    public function action_index()
    {
        // セッションから会員情報取得
        $member = Session::get("member");

        // 未ログインの場合はログインページへリダイレクト                                                                                                                                                          
        if (empty($member)) {
            $this->redirect("/member/login/form");
        }

        // テンプレート                                                                                                                                                                                            
        $view = View::forge('member/index');
        $view->set("member", $member);
        return $view;
    }

    // リダイレクト
    private function redirect($url)
    {
        if (isset($_SERVER["FUEL_ENV"]) && $_SERVER["FUEL_ENV"] == "test") {
            // testではリダイレクトしない                                                                                                                                                                          
        } else {
            Response::redirect($url);
        }
    }
}

テストコードの作成

@group・・・後でテストを実行するときにグループ単位で実行したいときに使う
@test・・・これがないとテストのメソッドにtestXXXの形でtestが必要になるのでつけておく

fuel/app/tests/controller/member.php

<?php
/**                                                                                                                                                                                                                
 * @group App                                                                                                                                                                                                      
 */
class Test_Member extends TestCase
{
    /**                                                                                                                                                                                                            
     * @test                                                                                                                                                                                                       
     */
    public function ログイン成功のテスト()
    {
        // ログイン成功する(はずの)パラメータをセット
        $_POST["email"] = "xxx@xxx.com";
        $_POST["password"] = "xxxxxxxx";

        Session::destroy();

        $response = Request::forge("member/login")
        ->set_method("POST")
        ->execute()
        ->response();

        // 処理後にセッションに保存されていればログイン成功なのでテスト成功と見なす
        $member = Session::get("member");
        $this->assertTrue(!empty($member));
    }

    /**                                                                                                                                                                                                            
     * @test                                                                                                                                                                                                       
     */
    public function ログイン失敗のテスト()
    {
        // ログイン失敗する(はずの)パラメータをセット
        $_POST["email"] = "xxxx@xxx.com";
        $_POST["password"] = "";

        Session::destroy();

        $response = Request::forge("member/logindo")
        ->set_method("POST")
        ->execute()
        ->response();

        // 処理後にセッションに保存されていなければログイン失敗なのでテスト成功と見なす
        $member = Session::get("member");
        $this->assertTrue(empty($member));
    }

    /**                                                                                                                                                                                                            
     * @test                                                                                                                                                                                                       
     */
    public function ログインページ閲覧可のテスト()
    {
        Session::destroy();

        // ログイン成功する(はずの)パラメータをセット
        $_POST["email"] = "xxx@xxx.com";
        $_POST["password"] = "xxxxxxxx";

        // ログイン処理を行う
        $response = Request::forge("member/login")
        ->set_method("POST")
        ->execute()
	->response();

        // 会員専用ページへアクセス(見れるはず)
        $response = Request::forge("member/index")
        ->execute()
	->response();

        //  処理後に変数に会員情報がセットされていればテスト成功と見なす
        $this->assertTrue(!empty($response->body->member));
    }

    /**                                                                                                                                                                                                            
     * @test                                                                                                                                                                                                       
     */
    public function ログインページ閲覧不可()
    {
     	Session::destroy();

        // ログイン失敗する(はずの)パラメータをセット
        $_POST["email"] = "xxx@xxx.com";
	$_POST["password"] = "";

        // ログイン処理を行う
	$response = Request::forge("member/login")
	->set_method("POST")
	->execute()
	->response();

        // 会員専用ページへアクセス(見れないはず)
	$response = Request::forge("member/index")
	->execute()
	->response();

        //  処理後に変数に会員情報がセットされていなければテスト成功と見なす
	$this->assertTrue(empty($response->body->member));
    }
}

テスト実行

> php /project_path/oil test --group=App

php oil test --group=App
Tests Running...This may take a few moments.
PHPUnit 4.8.27 by Sebastian Bergmann and contributors.

........

Time: 2.17 seconds, Memory: 21.00MB

OK (4 tests, 7 assertions)

成功!!

おわりに

まだ実案件で導入したわけではなくて触りだけなので、実際にやりはじめると色々問題も出てきそうだけどとりあえず触りの段階としてはokとします。
本当はviewとかもやらないとダメなのかな・・・

自分なりにまとめると
1. モデルとコントローラは効果がありそうなのでなるべく導入する
2. 構文エラーや存在しないクラスやメソッド・関数を使用している箇所が発見できるので、if文とかは網羅する
3. viewはやり方まだよくわかってないのとそこまで致命的にならないでしょとういことで今まで通り手動かつ目でテストでいいのかな。。。

テストパターンの質はもちろん大切だとは思うけど、2.だけでもけっこう意味ある気がした

おまけ(一瞬詰まったところ)

phpunitとcomposer.jsonに記述するphpunitのバージョンが一致しなくてエラーとなった

php oil test --group=App
Tests Running...This may take a few moments.
PHPUnit 3.7.38 by Sebastian Bergmann.

Configuration read from /var/www/html/example.com/fuel/app/phpunit.xml

PHP Fatal error:  Class PHPUnit_Util_DeprecatedFeature_Logger contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (PHPUnit_Framework_TestListener::addRiskyTest) in /var/www/html/example.com/fuel/vendor/phpunit/phpunit/PHPUnit/Util/DeprecatedFeature/Logger.php on line 201
PHP Stack trace:
PHP   1. {main}() /usr/bin/phpunit:0
PHP   2. PHPUnit_TextUI_Command::main() /usr/bin/phpunit:44
PHP   3. PHPUnit_TextUI_Command->run() /usr/share/php/PHPUnit/TextUI/Command.php:101
PHP   4. PHPUnit_TextUI_TestRunner->doRun() /usr/share/php/PHPUnit/TextUI/Command.php:150
PHP   5. spl_autoload_call() /usr/share/php/PHPUnit/TextUI/Command.php:235
PHP   6. Composer\Autoload\ClassLoader->loadClass() /usr/share/php/PHPUnit/TextUI/Command.php:235
PHP   7. Composer\Autoload\includeFile() /var/www/html/example.com/fuel/vendor/composer/ClassLoader.php:301
PHP   8. include() /var/www/html/example.com/fuel/vendor/composer/ClassLoader.php:412

Fatal error: Class PHPUnit_Util_DeprecatedFeature_Logger contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (PHPUnit_Framework_TestListener::addRiskyTest) in /var/www/html/example.com/fuel/vendor/phpunit/phpunit/PHPUnit/Util/DeprecatedFeature/Logger.php on line 201

Call Stack:
    0.0003     230368   1. {main}() /usr/bin/phpunit:0
    0.2099     617056   2. PHPUnit_TextUI_Command::main() /usr/bin/phpunit:44
    0.2099     617680   3. PHPUnit_TextUI_Command->run() /usr/share/php/PHPUnit/TextUI/Command.php:101
    0.9668   15829872   4. PHPUnit_TextUI_TestRunner->doRun() /usr/share/php/PHPUnit/TextUI/Command.php:150
    0.9875   15979328   5. spl_autoload_call() /usr/share/php/PHPUnit/TextUI/Command.php:235
    0.9876   15979384   6. Composer\Autoload\ClassLoader->loadClass() /usr/share/php/PHPUnit/TextUI/Command.php:235
    0.9876   15979384   7. Composer\Autoload\includeFile() /var/www/html/example.com/fuel/vendor/composer/ClassLoader.php:301
    0.9879   16000928   8. include('/var/www/html/example.com/fuel/vendor/phpunit/phpunit/PHPUnit/Util/DeprecatedFeature/Logger.php') /var/www/html/example.com/fuel/vendor/composer/ClassLoader.php:412

以上です