2006/10/01

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 $youtube->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 $data->xpath('//video');
            
$this->assertEquals(25count($videos));

            $youtube->setResponseFormat('array');
            
$data $youtube->listFeatured();
            
$this->assertEquals(25count($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さんの記事。私は一通り読んだけど、訳すの面倒なのでしないつもり。

はじめまして。
アカホシと申します。

youtubeのapiについて調べていましたら、
幸いにも、こちらにたどり着きました。

Services_YouTubeを拝見させていただいたのですが、
ぜひ私のサイトでも利用させていただきたいと思っております!

ひとつ質問があるのですが、
Services_YouTubeはPHP5のみ対応ですか?

よろしくお願いします!

コメント by アカホシ — 2006/10/07

> アカホシさん

コメントどうもです。
Services_YouTubeに関してですが、ぜひぜひ使ってみてください!
以下のURLからダウンロードできます。
http://pear.php.net/package/Services_YouTube/

ただ、PHP5のみなのです。最近、このパッケージをPEARに登録したのですが、近々PEARは、PHP5系のパッケージしか受け付けなくなるとありまして、PHP5で書いてみたのです。一応、PHP4で動くパッケージがあるのですが、キャッシュの機能とかが実装してないのです。もし参考程度に作り直したいとの要望がありましたら、一度、アップロードしますが、PHP4の方は、機能追加したりすることは考えていませんので、あまり役に立たないかもしれません。

コメント by shin — 2006/10/07

>shinさん

おはようございます。

実はServices_YouTubeはダウンロードさせていただきアップロードして、
さあやろうと思ったときに、

「php4未対応?」

と感じたしだいでございます(PHPSPOTさんのブログからきました)。

php4をぜひともいただきたいのですが、再アップしていただけないでしょうか。
よろしくお願いいたします!!

コメント by アカホシ — 2006/10/07

> アカホシさん

なるほど。PHP4で動くものを探しているのですね。

こちらがPHP4のリンクとなります。
http://www.ganchiku.com/pear/Services/YouTube4.phps
一応、固めたものは、以下のリンクです。
http://www.ganchiku.com/pear/Services/YouTube4.tgz

ただ、PEARのパッケージ管理に沿っていませんので、手動でrequire or includeするという感じになります。
また、PEAR::XML_RPCとPEAR::XML_Serializerが必要になります。正直なところ手を込んだことをしていませんので、こちらのソースはあまり期待しないでくださいね。

というより、やっぱりまだまだWebな業界では、PHP4がメインなのかなー、と思っています。(私も会社では、PHP4しか使っていませんので。。)PEARに挙げたものは、「PHP4未対応」というか、PHP5のいいところを、使ったものです。それは、 アクセス修飾子や、Exception、クラス定数です。PHP4とPHP5を共存させることもできるのですが、そうすると今挙げたPHP5の利点が使えないのです。私自身、今後、PHP5に移行していくことを考えていますので、PHP4対応は残念ながら捨ててしまっています。後方依存性を考慮するべきか、新しい利点を使っていくかで、悩むところですが、私は、PHP5の利点を使うことを選択していますので、今後の開発もPHP5のE_STRICT対応のものを作っていく予定です。

コメント by shin — 2006/10/07

shinさん

アップしていただきまして、
ありがとうございました!

早速テストしているところです。

$response = $youtube->listByTag(”[タグ]“, 1, 10); // タグによるリストを得る

↑に日本語を入れるとWarningが出てしまうのですが、
どこに問題があるのか教えていただけないでしょうか?

お時間のあるときで結構ですので、
教えていただければ幸いです。

よろしくお願いします!

私はまだまだphpを使い始めたばかりなので、
とりあえずは4でいくと思いますが、
shinさんのブログで5につきましても勉強させていただきます!

コメント by アカホシ — 2006/10/08

> アカホシさん

PHP4の方はほとんどテストしていませんでした。すいません。うーん。Warningの詳細を教えていただけないですか?つまり、行数などです。

私の環境で少し動かしてみたところ、PHP5の方では問題なく動いていたようにみえました。しかし、PHP4では、検索結果が何も引っかからなかったところとかが問題があるのかもしれません。

ところで、検索する「タグ」の文字コードは何でしょうか?日本語を使用する際には、UTF-8でお願いします。

私のサイトが勉強になればとても光栄です。努力します。

では、Warningの詳細を教えていただければ、もう少し調査してみますので、よろしくお願いします。

コメント by shin — 2006/10/09

Leave a comment

Bloglines feedburner