PHPUnit データプロバイダと例外テスト

SATOOPHP,PHPUnit,データプロバイダ,例外テスト,単体テスト

こんにちは、SATOOです。

7月に入りました。
まだ梅雨は明けていません。じめじめした天気&空気が続いています。
いよいよビールのおいしい季節が到来、といったところですが、根っからの日本酒党の私は、今日も浮気をせず日本酒です。
先日、ウナギを食べたときは誘惑に勝てませんでしたが・・・。

さて、今回は「PHPUnit」ネタです。
「PHPUnit」とは、いわゆる「xUnit」系(xにはJとかPHPとか入ります)の単体テスト自動化ツールです。
PHPの関数やクラス単位のテストを行うためのツールとして有名です。

今回は、「PHPUnit」でテストを書く際に有用なメソッドのメモ。
知ってるよ、という内容かとは思いますが、お付き合いくださいませ。

データプロバイダ

引数をチェックして、半角英数字だった場合だけTRUEを返すメソッド(isAlphanum())があるとします。

class AlphaNum {
	public static function isAlphanum($value) {
		if (preg_match("/^[a-zA-Z0-9]+$/", $value)) {
			return true;
		}
		return false;
	}
}

このメソッドのユニットテストケースを書くとして、以下のパターンでのテストを書きたいとします。

  • 半角英字
  • 半角数字
  • 半角英数字
  • 全角英字
  • 全角英数字
  • 半角・全角混淆
  • 半角記号

普通に書くとすると、次のようになります(require文は省いています)。

class AlphaNumTest extends PHPUnit_Framework_TestCase {
	// 半角英字の場合
	public function testIsAlphanum1() {
		$this->assertTrue(AlphaNum::isAlphanum('abc'));
	}
	// 半角数字の場合
	public function testIsAlphanum2() {
		$this->assertTrue(AlphaNum::isAlphanum(10));
	}
	// 半角英数字の場合
	public function testIsAlphanum3() {
		$this->assertTrue(AlphaNum::isAlphanum('a10'));
	}
	// 全角英字の場合
	public function testIsAlphanum4() {
		$this->assertFalse(AlphaNum::isAlphanum('abc'));
	}
	// 全角英数字
	public function testIsAlphanum5() {
		$this->assertFalse(AlphaNum::isAlphanum('abc10'));
	}
	// 半角・全角混淆
	public function testIsAlphanum6() {
		$this->assertFalse(AlphaNum::isAlphanum('10aabc10'));
	}
	// 半角記号
	public function testIsAlphanum7() {
		$this->assertFalse(AlphaNum::isAlphanum('&&"\'%$#!=-~^'));
	}
}

こう書いても良いのですが、同じようなテストコードはまとめて書いたほうが無駄の少ないテストコードになります。
そういうときに活躍するのが「データプロバイダ」です。
上のコードは、以下のように書きなおすことができます。

class AlphaNumTest extends PHPUnit_Framework_TestCase {
	public static function forTestIsAlphanum() {
		return array(
			array('abc', true),				// 半角英字
			array(10, true),				// 半角数字
			array('a10', true),				// 半角英数字
			array('abc', false),			// 全角英字
			array('abc10', false),		// 全角英数字
			array('10aabc10', false),	// 半角・全角混淆
			array('&&"\'%$#!=-~^', false)	// 半角記号
		);
	}

	/**
	 * @dataProvider forTestIsAlphanum
	 */
	public function testIsAlphanum($value, $expected) {
		$this->assertEquals($expected, AlphaNum::isAlphanum($value));
	}

実際のテストは、testIsAlphanum()が実行しており、forTestIsAlphanum()はtestIsAlphanum()にテストしたい引数を渡すだけです。
forTestIsAlphanum()が渡す配列の数だけ、テストが実行されます。

データプロバイダを使用するためには、テストメソッドにアノテーションとして、データプロバイダとして使用するメソッドを書きます。
上の例では、

	/**
	 * @dataProvider forTestIsAlphanum
	 */

この部分がアノテーションです。

ちなみに、上記の例では期待値も引数として渡していますが、例外クラスを渡すことで「期待する例外」のテストも可能です。

setExpectedException

例外が発生することを期待する引数のテストをしたいときに使うと便利です。

class AlphaNum {
	public static function isAlphanum($value) {
		if (is_null($value)) {
			throw new Exception();
		}
		if (preg_match("/^[a-zA-Z0-9]+$/", $value)) {
			return true;
		}
		return false;
	}
}

上記のコードでは、引数の値がnullだった場合例外が投げられます。
このテストを追加すると、以下のようになります。

class AlphaNumTest extends PHPUnit_Framework_TestCase {
	public static function forTestIsAlphanum() {
		return array(
			array('abc', true),				// 半角英字
			array(10, true),				// 半角数字
			array('a10', true),				// 半角英数字
			array('abc', false),			// 全角英字
			array('abc10', false),		// 全角英数字
			array('10aabc10', false),	// 半角・全角混淆
			array('&&"\'%$#!=-~^', false),	// 半角記号
			array(null, new Exception())	// null
		);
	}

	/**
	 * @dataProvider forTestIsAlphanum
	 */
	public function testIsAlphanum($value, $expected) {
		if ($expected instanceof Exception) {
			$this->setExpectedException(get_class($expected));
		}
		$this->assertEquals($expected, AlphaNum::isAlphanum($value));
	}

期待値がExceptionインスタンスだった場合、setExpectedException()メソッドをコールして、期待される例外が発生することをテストします。

という感じで、今日のところはさようなら。