0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PHPUnitでtrigger_errorをテストする

Last updated at Posted at 2013-10-19

もう過去の遺物と思って避けて通りたいと常々思っているPHP旧バージョンのシステム開発。
残念ながら、古来より存在し、未だに猛威を振るうCMSを利用した開発案件が数多く存在するPHPではレガシーなコードと向き合うことは避けられない。

PHPのレガシーコードでは異常ケースをtrigger_errorを利用してハンドリングするのが定石のようですが、
このケースがモダン?なユニットテストを行う時に問題を起こすのです。

ケース1

以下のようなコードがありました。

class Hoge {
  function action($arg) {
    if (!is_int($arg)) {
      trigger_error('error message', E_USER_ERROR);
      return;
    }

    // some proc...
  }
}

このロジックで$argが整数でないケースのテストを行いたい といったケースを想定します。

PHPUnitでtrigger_errorを使うとどんな問題が???

上記のコードをテストするために以下のようなテストコードを書きました。

class Hoge_Action_Test extends PHPUnit_Framework_TestCase {
  function testArgmentIsNotInteger() {
    $hoge = new Hoge();
    $this->assertNull($hoge->action('hoge'));
  }
}

このテストは失敗します。
PHPUnitは発生するエラーレベルによって例外をスローするため、上記のケースでは例外が発生します。

発生する例外をテストできるようにする

PHPUnitはデフォルトの設定でPHPのエラーをレベル毎に例外へスローするようになっています。
@expectedExceptionを利用して発生するべき例外を指定すればOKです。

class Hoge_Action_Test extends PHPUnit_Framework_TestCase {
  /**
   * @expectedException PHPUnit_Framework_Error
   */
  function testArgmentIsNotInteger() {
    $hoge = new Hoge();
    $hoge->action('hoge');
  }
}

各エラーレベルと対応する例外は以下の通りです。

E_NOTICE | E_USER_NOTICE | E_STRICT

PHPUnit_Framework_Error_Notice

E_WARNING | E_USER_WARNING

PHPUnit_Framework_Error_Warning

E_DEPRECATED | E_USER_DEPRECATED

PHPUnit_Framework_Error_Deprecated

それ以外

PHPUnit_Framework_Error

ケース2

ケース1のコードにset_error_hundler()が追加されています

function shutdown($no, $str, $file, $line, $context) {
  die();
}

class Hoge {
  function __construct() {
    set_error_hundler('shutdown');
  }

  function action($arg) {
    if (!is_int($arg)) {
      trigger_error('error message', E_USER_ERROR);
      return;
    }

    // some proc...
  }
}

この場合ケース1で行ったようなテストは失敗します...

PHPUnitがエラーを例外に置き換えるのはset_error_hundler()を利用して置き換えているのですが、
処理の実行順序が

1.PHPUnitがエラーを例外に置換する処理を set_error_hundler() でセット
2.テストコードがエラーのケースを set_error_hundler() を利用してセット

となり、PHPUnitがせっかく例外に置換する処理を設定してくれているのに、それを上書きするため例外がスローされなくなってしまいます。

どうしましょう?更にset_error_hundlerを上書きしましょう!

テストコード上で更にset_error_hundler()を使いエラー時の処理を上書きしてしまえばテスト出来ます。

class Hoge_Action_Test extends PHPUnit_Framework_TestCase {
  function testArgmentIsNotInteger() {
    $hoge = new Hoge();
    set_error_hundler(function($no, $str, $file, $line, $context) {});
    $this->assertNull($hoge->action('hoge'));
  }
}

インスタンスを作らずにテストする方法とかもあるらしいですが、それは検索すればわりと引っかかるので割愛…
そっちのほうがテストコード的に綺麗な気はしますけどね。

ケース3

うわっ、テスト対象メソッドの中にset_error_hundler()が出てきた!!

function shutdown($no, $str, $file, $line, $context) {
  die();
}

class Hoge {
  function action($arg) {
    set_error_hundler('shutdown');
    if (!is_int($arg)) {
      trigger_error('error message', E_USER_ERROR);
      return;
    }

    // some proc...
  }
}

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

本当にこの処理は必要なのか?
trigger_error()Exceptionにするとか、set_error_hundler()をコンストラクタ、初期処理に移動するとかを検討。
どうしても必要ならテスト時にrunkitとか使うしかないかな...

まとめ

CMS開発じゃなくてフルスクラッチで開発したい(泣)

0
0
7

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?