XP祭に行った勢いでユニットテストを書く。PHPUnit2で。
すいません。Services_YouTubeの方のテスト手を抜いておりました。。。
TDDではないですね。。。というわけで、早速ユニットテストを書きました。そして、今日のXP祭のスキットにあったMockを使ってみました。使ってみたところは、setterが本当にちゃんとセットされたかどうか確かめるために、getterを実装した子クラスを用意して、それの結果を見てみました。こういうときのためにprotected fieldっていいのかもね。
で、どのテスティングフレームワークを使うか悩むところなのだが、とりあえず、PHPUnit2にしてみた。で、早速使いかたがわからんかったわけだが、(Seleniumの方はPHPUnit2でテストを書いたのに。。。忘れている。。)php/lib/tests/PHPUnit2のいろいろなファイルを見る。つまり、PHPUnit2自体のユニットテストね。当然のことながら、それが一番わかりやすいだろうから。
なるほど。各ディレクトリにAllTests.phpがあって、どのテストスイートをaddしていくか書くわけだ。今回は、addTestは使わずにaddTestSuiteだけにした。だって、そんなにデカいプログラムじゃないから。で、今後バグとかがあがったときには、addTestSuiteでテストスイートを追加するって感じかな。なので、とりあえず、一通りのメソッドのテストということで、YouTubeTest.phpも追加する。
というわけで、ちょっと説明付きで、公開しちゃうよ。つか、普通にインストールしたら見えるようになるわけだが。。。説明付きということで、堪忍してください。
まず、AllTests.phpに関して。
<?php
if (!defined('PHPUnit2_MAIN_METHOD')) {
define('PHPUnit2_MAIN_METHOD', 'AllTests::main');
chdir(dirname(__FILE__));
}
if (!defined('PHPUnit2_INSIDE_OWN_TESTSUITE')) {
define('PHPUnit2_INSIDE_OWN_TESTSUITE', TRUE);
}
require_once 'PHPUnit2/Framework/TestSuite.php';
require_once 'PHPUnit2/TextUI/TestRunner.php';
require_once 'YouTubeTest.php';
class AllTests
{
public static function main()
{
PHPUnit2_TextUI_TestRunner::run(self::suite());
}
public static function suite()
{
$suite = new PHPUnit2_Framework_TestSuite('Services');
/** Add testsuites, if there is. */
$suite->addTestSuite('YouTubeTest');
return $suite;
}
}
if (PHPUnit2_MAIN_METHOD == 'AllTests::main') {
AllTests::main();
}
?>
PHPUnit2自身のテストを見ながら作ったわけだが、最初にPHPUnit2_MAIN_METHODが定義されているかどうか調べている。これは、phpコマンドで実行されたときは定義されてないのだけども、phpunitコマンド(PHPUnit2をインストールしたときにphpコマンドと同じ場所に入る)を使った際には、定義されているのだ。chdirしているところがあるんだけど、なんだかディレクトリ構成を調整するもの。まぁ、ようはphpunitコマンドで実行すればいらないんだけどね。。
次に、PHPUnit2_INSIDE_OWN_TESTSUITEなんだけど、ソース見た訳じゃないので、適当なことを言ってはいけない気がするが、おそらく、上のと同じ。つまり、phpコマンドかphpunitコマンドかってこと。おそらく、addTestSuiteで他のファイルを呼んでいるか、とか?ここは想像のみです。
そして、実行するときは、PHPUnit2_MAIN_METHODが同ファイルで定義された内容と一致するか調べて、一致したら、実行って感じ。これは、phpコマンドから実行されたときのためね。
つーまーり。phpunitコマンド使えばいいんじゃねーの?
今回は、PHPUnit2の実装方法が知りたかったので調子に乗って書いてみただけ。phpunitコマンドを使用するならば、この辺の定数定義や、判別はいらない。あと、PHPUnit2/Framework/TestSuite.phpや、PHPUnit2/TextUI/TestRunner.phpはrequireしなくてもよい。
将来バグが出て、そのテストケースを追加する際には、そのクラスファイルを作ってaddTestSuiteするだけね。
次。YouTubeTest.php
<?php
// All tests require Cache_Lite.
error_reporting(E_ALL|E_STRICT);
require_once '../YouTube.php';
class YouTubeTest extends PHPUnit2_Framework_TestCase
{
const DEV_ID = 'YOUR_DEV_ID';
// {{{ setter tests using Mock class
public function testSetDriver()
{
try {
$youtube = new Services_YouTubeMock(self::DEV_ID);
$this->assertEquals('rest', $youtube->getDriver());
$youtube->setDriver('xmlrpc');
$this->assertEquals('xmlrpc', $youtube->getDriver());
$youtube->setDriver('rest');
$this->assertEquals('rest', $youtube->getDriver());
// throw Exception
$youtube->setDriver("throw exception");
} catch (Services_YouTube_Exception $e) {
$this->assertEquals('Driver has to be "xmlrpc" or "rest"', $e->getMessage());
}
}
public function testSetResponseFormat()
{
try {
$youtube = new Services_YouTubeMock(self::DEV_ID);
$this->assertEquals('object', $youtube->getResponseFormat());
$youtube->setResponseFormat('array');
$this->assertEquals('array', $youtube->getResponseFormat());
$youtube->setResponseFormat('object');
$this->assertEquals('object', $youtube->getResponseFormat());
> // throw Exception
$youtube->setResponseFormat("throw exception");
} catch (Services_YouTube_Exception $e) {
$this->assertEquals('ResponseFormat has to be "object" or "array"', $e->getMessage());
}
}
public function testSetUseCache()
{
$youtube = new Services_YouTubeMock(self::DEV_ID);
$this->assertFalse($youtube->getUseCache());
$this->assertEquals(array(), $youtube->getCacheOptions());
$youtube->setUseCache(true);
$this->assertTrue($youtube->getUseCache());
$this->assertEquals(array(), $youtube->getCacheOptions());
$youtube->setUseCache(true, array('lifeTime' => 18000));
$this->assertTrue($youtube->getUseCache());
$this->assertEquals(array('lifeTime' => 18000), $youtube->getCacheOptions());
}
// }}}
// {{{ user API
public function testGetProfile()
{
try {
$youtube = new Services_YouTube(self::DEV_ID);
$youtube->setUseCache(true);
$data = $youtube->getProfile('ganchiku');
$profile = $data->user_profile;
$this->assertEquals('Shin', $profile->first_name);
$this->assertEquals('Ohno', $profile->last_name);
$youtube->setResponseFormat('array');
$data = $youtube->getProfile('ganchiku');
$profile = $data['user_profile'];
$this->assertEquals('Shin', $profile['first_name']);
$this->assertEquals('Ohno', $profile['last_name']);
} catch (Services_YouTube_Exception $e) {
print $e;
}
}
public function testListFavoriteVideos()
{
try {
$youtube = new Services_YouTube(self::DEV_ID);
$youtube->setUseCache(true);
$data = $youtube->listFavoriteVideos('ganchiku');
$videos = $data->xpath('//video');
$this->assertTrue(is_array($videos));
$youtube->setResponseFormat('array');
$data = $youtube->listFavoriteVideos('ganchiku');
$this->assertTrue(is_array($data['video_list']));
} catch (Services_YouTube_Exception $e) {
print $e;
}
}
public function testListFriends()
{
try {
$youtube = new Services_YouTube(self::DEV_ID);
$youtube->setUseCache(true);
$data = $youtube->listFriends('ganchiku');
$this->assertTrue(isset(
$data->friend_list));
// i have no friends... orz...
$this->assertEquals(0, $data->friend_list);
$youtube->setResponseFormat('array');
$data = $youtube->listFriends('ganchiku');
$this->assertTrue(array_key_exists('friend_list', $data));
} catch (Services_YouTube_Exception $e) {
print $e;
}
}
// }}}
// {{{ video API
public function testGetDetails()
{
try {
$youtube = new Services_YouTube(self::DEV_ID);
$youtube->setUseCache(true);
$data = $youtube->getDetails('rdwz7QiG0lk');
$video = $data->video_details;
$this->assertEquals('YouTube', $video->author);
$youtube->setResponseFormat('array');
$data = $youtube->getDetails('rdwz7QiG0lk');
$video = $data['video_details'];
$this->assertEquals('YouTube', $video['author']);
} catch (Services_YouTube_Exception $e) {
print $e;
}
}
public function testListByTag()
{
try {
$youtube = new Services_YouTube(self::DEV_ID);
$youtube->setUseCache(true);
$data = $y
outube->listByTag('YouTube');
$videos = $data->xpath('//video');
$this->assertTrue(is_array($videos));
$youtube->setResponseFormat('array');
$data = $youtube->listByTag('YouTube');
$this->assertTrue(is_array($data['video_list']));
// Hmm... need to integrate pager parameters
} catch (Services_YouTube_Exception $e) {
print $e;
}
}
public function testListByUser()
{
try {
$youtube = new Services_YouTube(self::DEV_ID);
$youtube->setUseCache(true);
$data = $youtube->listByUser('ganchiku');
// i have not uploaded any videos... orz...
$this->assertEquals(0, $data->video_list);
$youtube->setResponseFormat('array');
$data = $youtube->listByUser('ganchiku');
$this->assertNull($data['video_list']);
} catch (Services_YouTube_Exception $e) {
print $e;
}
}
public function testListFeatured()
{
try {
$youtube = new Services_YouTube(self::DEV_ID);
$youtube->setUseCache(true);
$data = $youtube->listFeatured();
$videos =
"color: #0000BB">$data->xpath('//video');
$this->assertEquals(25, count($videos));
$youtube->setResponseFormat('array');
$data = $youtube->listFeatured();
$this->assertEquals(25, count($data['video_list']['video']));
} catch (Services_YouTube_Exception $e) {
print $e;
}
}
// }}}
}
// {{{ Mock Class FOR GETTER
class Services_YouTubeMock extends Services_YouTube
{
public function getDriver()
{
return $this->driver;
}
public function getResponseFormat()
{
return $this->responseFormat;
}
public function getUseCache()
{
return $this->useCache;
}
public function getCacheOptions()
{
return $this->cacheOptions;
}
}
// }}}
?>
長いね。今回は、Mockクラスを使ってみたよ。でも、この使いかたって正しいのかな。今日のXP祭では、出力してしまうメソッドのテストってことでMockクラスについて述べていたのだけど、私の場合は、本番用には、getterはいらんので、テストMockクラスだけにgetterを追加しただけ。ちょっとヲレ流か?
で、publicメソッドを全部テスト書いてみる。setterのテスト。ここでは、testSetDriver, testSetResponseFormat, testSetUseCacheは、Services_YouTubeを継承したServices_YouTubeMockを使用する。ついでにExceptionも無理矢理スローさせてみる。
で、後は、YouTube APIのメソッド郡の予想と結果を比較しているだけ。ついでに、simplexml_elementかarrayかのテストもしちゃった。で、今回は、たくさんリクエスト飛ばすが嫌だったので。キャッシュを使ってみた。受け取ったら一緒だしね。。。
つーか、listFriendsってメソッドがあるんだけどさ。。一応、コメント書いたんだけどさ。。
// i have no friends... orz...
$this->assertEquals(0, $data->friend_list);
友達いないから0と比較なのよ。。。なんかなー。
あと、listByTagのページャのテストは、してない。想定が難しいので。後で何か方法を考えたらやってみようかな。
というわけで、次は、Seleniumの方の説明でもしようかなー。
【ハウツー】これはすごい! Web案件必須 Selenium – 人気急上昇中自動テストツール (1) 最近人気のSelenium (MYCOMジャーナル)に表面的なことだけ書いてあるので、今のうちならホットかもしれんし。つか、こんな表面的な記事に300もブクマする人がいるの?
そんなんよりもFlickrがJSON形式もしくは、PHPのserialize形式で返すことができるようになった、とかの方が注目するところだと思うんだけどなー。昨日調べていたけど、なんか権限がないって言われて疲れ果てて寝ちゃった。誰か調査して。
つか、ドキュメント書けってな。。。Services_YouTubeの方はたぶんだけど、週に一回ビルドされるらしいので、それで載るかなー。もしくは、私の書き方がダメでまだ載らないかも。。。
*あぁ、あとYOUR_DEV_IDってとこは、適当に書いてね。ちょっと私のDEV_IDを書くのは抵抗があったので。本当のテストファイルには書いてあるけど。。
つーか、ここ見ればよかったね。。
PHPUnit ポケットガイド
つーか、PHPUnit3が出てきているね。なんか6月頃、PHPUnitの開発はPEARではしないって言っていたから独自にやるのかなー。
あ。あと、全然関係ないけど。PEARな方は、ここ読むべしだって。
The State of the PEAR Address
PEARでがんばっているLukasさんの記事。私は一通り読んだけど、訳すの面倒なのでしないつもり。
Shin Ohno 2003-2012