[PHP][PHPUnit] 指定されたディレクトリ以下のすべてのテストを実行するスクリプト

対象

この記事で対象にしているのは、PHP 5.1.6(コマンドライン版)、PHPUnit 3.0.0です。

問題点

PHPUnitは、PHP用のテストツールです。

このツールでは、テストを追加する場合、テストケースを書くだけではなく、ツールにテストケースがあることを教える必要があります。例えば、PHPUnit ポケットガイド第7章 テストの構成では、以下のコードが例としてあげられています。

<?php
(略)
    public static function suite()
    {
        $suite = new PHPUnit_Framework_TestSuite('PHPUnit Framework');
 
        $suite->addTestSuite('Framework_AssertTest');
        // ...
 
        return $suite;
    }
(略)
?>

しかし、これは2度手間であり、うっかりテストツールを追加することを忘れることもありえるでしょう。できれば、RubyTest::Unitのように、ファイルをディレクトリに置くだけでテストの対象になるようにしたいです。

解決方法

以下のスクリプトを使用します。

<?php

require_once 'PHPUnit/Framework.php';
require_once 'PHPUnit/TextUI/TestRunner.php';

class AllTests { 

  public static function main($directories) {
    $suite = self::suite($directories);
    PHPUnit_TextUI_TestRunner::run($suite);
  }

  public static function suite($directories) {
    $suite = new PHPUnit_Framework_TestSuite();

    foreach ($directories as $directory) {
      $tests = self::collect_tests($directory);
      $suite->addTestFiles($tests);
    }

    return $suite;
  }

  private static function collect_tests($directory) {
    $tests = array();

    $dir = dir($directory);
    while (($entry = $dir->read()) !== false) {
      if (($entry === '.') || ($entry === '..')) {
        continue;
      }

      $path = sprintf('%s/%s', $directory, $entry);
      if (is_dir($path)) {
        $tests_in_path = self::collect_tests($path);
        $tests = array_merge($tests, $tests_in_path);
      }
      else {
        if (ereg('^.*Test\\.php$', $entry)) {
          array_push($tests, $path);
        }
      }
    }

    return $tests;
  }
}

$directories;
if (1 < $argc) { 
  $directories = array_slice($argv, 1);
}
else {
  $directory = dirname($argv[0]);
  $directories = array($directory);
}
AllTests::main($directories);

# vim: tabstop=2 shiftwidth=2 expandtab
?>

このスクリプトがtests/harness.phpであるとき、このスクリプトをコマンドラインから以下のようにして実行します。すると、スクリプトがあるディレクトリ(この例の場合はtestsディレクトリ)以下にあるすべてのテストを実行します。

$ $ php tests/harness.php             [~/projects/PhosphorusRider]
PHPUnit 3.0.0 by Sebastian Bergmann.
(略)
3) testGetToken2(ScannerTest)
Exception: This method is not implemented.
/home/tom/projects/PhosphorusRider/tests/SchemaParser/ScannerTest.php:115
/home/tom/projects/PhosphorusRider/tests/harness.php:10
/home/tom/projects/PhosphorusRider/tests/harness.php:57

FAILURES!
Tests: 4, Errors: 3.

テストケースのファイルの名前は、"*Test.php"である必要があります。

コマンドラインには、対象とするディレクトリを指定することもできます。その場合は、指定されたディレクトリ以下にあるすべてのテストが実行されます。

解決方法までの経緯

PHPUnitのAPIドキュメントPHPUnit_Framework_TestSuiteクラスの項目をみると、PHPUnit_Framework_TestSuiteクラスには、ポケットリファレンスでは触れられていないaddTestFilesメソッドがあることが分かります。今回は、これを利用することにしました。

テストは、ディレクトリの処理ではありがちな再帰をして、集めています。

<?php

  private static function collect_tests($directory) {
    $tests = array();

    $dir = dir($directory);
    while (($entry = $dir->read()) !== false) {
      if (($entry === '.') || ($entry === '..')) {
        continue;
      }

      $path = sprintf('%s/%s', $directory, $entry);
      if (is_dir($path)) {
        $tests_in_path = self::collect_tests($path);
        $tests = array_merge($tests, $tests_in_path);
      }
      else {
        if (ereg('^.*Test\\.php$', $entry)) {
          array_push($tests, $path);
        }
      }
    }

    return $tests;
  }

?>