CodeIgniter のユニットテストクラスのテストを PHPUnit から実行する

PHPUnit から CodeIgniter のユニットテストを実行する方法です。CIUnit は使っていません。

PHPUnit のインストール

$ sudo pear channel-discover pear.phpunit.de
$ sudo pear channel-discover components.ez.no
$ sudo pear channel-discover pear.symfony-project.com
$ sudo pear install phpunit/PHPUnit

CodeIgniter のユニットテストを実行するテストケースの作成

まず、autoloader を作成します。

tests/autoload.php https://gist.github.com/1231528

<?php

define('BASEPATH', dirname(__FILE__) . '/../system/');
define('APPPATH', dirname(__FILE__) . '/../application/');

function autoload_core($class_name)
{
	$file_name = $class_name;
	if (substr($class_name, 0, 3) === 'CI_')
	{
		$file_name = substr($file_name, 3);
	}
	$file = BASEPATH . 'core/' . $file_name . '.php';
	if (file_exists($file))
	{
		require_once $file;
	}
}

function autoload_model($class_name)
{	
	$file_name = strtolower($class_name);
	$file = APPPATH . 'models/' . $file_name . '.php';
	if (file_exists($file))
	{
		require_once $file;
	}
}

spl_autoload_register('autoload_core');
spl_autoload_register('autoload_model');

続いて、PHPUnit の設定ファイルを作成します。

tests/phpunit.xml https://gist.github.com/1231533

<?xml version="1.0" encoding="UTF-8"?>

<phpunit 
  colors="true" 
  stopOnFailure="false">
  <php>
    <server name="SERVER_NAME" value="http://example.com" />
  </php>
  <testsuites>
    <testsuite name="CI Unit Testing Class Tests">
      <file>./CI_Unit_Test.php</file>
    </testsuite>
  </testsuites>
</phpunit>

CodeIgniter のユニットテストを実際に実行するテストを作成します。

コンストラクタの $this->test_folder に CodeIgniter のユニットテストが保存されているフォルダを、$this->test_uriユニットテストURI (コントローラ名より前の部分)を設定します。

get_ci_unit_test() メソッドでテストの URI にアクセスし結果を取得しています。まず、ユニットテストが保存されているフォルダ内の *.php ファイルを解析し、「test」で始まるメソッド名の URI にアクセスします。テストメソッドの命名規則が異なる場合は、この部分のロジックを変更してください。

ユニットテストURI にアクセスし、ページを取得し、「Failed」と「Passed」を探して保存しています。ユニットテストのテンプレートは英語であることを前提としています。

test() メソッドが PHPUnit のテストメソッドになります。失敗するアサーションがあった場合はメソッドが終了しますので、成功するメソッドを先に実行するようにしています。

tests/CI_Unit_Test.php https://gist.github.com/1231531

<?php

require 'autoload.php';

class CI_Unit_Test extends PHPUnit_Framework_TestCase
{
	private $test_folder;
	private $test_uri;
	private $count = 0;
	private $tests = array();
	
	public function __construct()
	{
		$this->test_folder = APPPATH . 'controllers/tests';
		$this->test_uri = 'http://localhost/ci/index.php/tests/';
		
		if (is_dir($this->test_folder) === FALSE) {
			exit("$this->test_folder is not a folder!\n");
		}
		if (parse_url($this->test_uri) === FALSE) {
			exit("$this->test_uri is not a valid URI!\n");
		}
		
		$this->get_ci_unit_test();
	}

	private function get_ci_unit_test()
	{
		$passed = 0;
		$failed = 0;
		
		foreach (glob("$this->test_folder/*.php") as $filename) {
			require $filename;
			$filename = basename($filename, '.php');
			$class = ucfirst($filename);
			$methods = get_class_methods($class);
			$tests = array();
			
			// access to all methods which begin with test
			foreach ($methods as $method) {
				if (substr($method, 0, 4) === 'test') {
					//echo "\n$class::$method()\n";
					
					$test_uri = $this->test_uri . $filename . '/' . $method;
					$test_name = ' ' . $test_uri;
					
					$page = file_get_contents($test_uri);
					if ($page === FALSE) {
						exit("Can't get $test_uri\n");
					}
					
					$lines = explode("\n", $page);
					
					foreach ($lines as $line) {
						if (preg_match('!<span style="color: #C00;">Failed</span>!u', $line))
						{
							$tests[$test_name][] = 'Failed';
							$failed = 1;	// If there is a failed test, the test method ends
						}
						else if (preg_match('!<span style="color: #0C0;">Passed</span>!u', $line))
						{
							$tests[$test_name][] = 'Passed';
							$passed++;
						}
					}
					
					if ( ! array_key_exists($test_name, $tests)) {
						exit("No test results: $test_uri\n");
					}
				}
			}
		}
		
		$this->count = $passed + $failed;
		$this->tests = $tests;
	}
	
	public function count()
	{
		return $this->count;
	}

	public function test()
	{
		// Passed tests
		foreach ($this->tests as $test_name => $tests) {
			foreach ($tests as $test) {
				$expected = 'Passed';
				$actual = $test;

				if ($actual === $expected) {
					$this->setName($test_name);
					$this->assertSame($expected, $actual);
				}
			}
		}
		
		// Failed tests
		foreach ($this->tests as $test_name => $tests) {
			foreach ($tests as $test) {
				$expected = 'Passed';
				$actual = $test;

				if ($actual !== $expected) {
					$this->setName($test_name);
					$this->assertSame($expected, $actual);
				}
			}
		}
	}
}

テストの実行

$ cd tests
$ phpunit