Ethnaを業務で使うために(4) テストの整備
「Ethnaを業務で使うために(3) テスト関連のディレクトリ構造の変更 - maru.cc@はてな」の続きです。
前回は、テスト関連のファイルが作成されるディレクトリ変更を行いましたが、次に実際にテストが動くようにします。
移動した状態を元に書きますので、前回の記事を参考にしてください。
ethnaコマンドでテストファイルが作成されるディレクトリの変更は行いましたが、このままでは、テストファイルが読み込まれないので、テストが動きません。
なので、そのための準備を行います。
行う作業
- Common_UnitTestCaseを作成
- 特殊な処理をしたい場合や、共通の setUp、tearDown をかけるようにします
- テストのスケルトンファイルも修正します
- Common_UnitTestManagerクラスを改造
- AppManagerのテストも自動で行われるようにします
- Common_UnitTestReporterクラス作成
- 文字コード周りの修正や、今後のためにReporterを作成します
- UnitTestのテンプレートも今後いじるので、testディレクトリの下のを使うようにします
- Common_Controllerの変更
- Common_View_UnitTestの作成
- 設定されているアプリケーションIDとは違う共通マネージャを呼ぶために改造します
- Common_View_UnitTestを使うための記述を追加します
- etc/mm-ini.phpを作成
- 最後にdebugをtrueにして動作確認です
Common_UnitTestCaseを作成
現行の Ethna_UnitTestCase には、連続したテストをした場合に、ActionError や Validator が、ひとつ前の状態を残しているという動作をします。
そのため、テストを実行する順番に依存したり、意図しない動作を起こしてしまうことがありますので、その対応をします。
また、AppAction や Viewの中で、$_SERVER や $_GET、$_POSTを直接操作している場所のテストで直接グローバル変数の中身を書き換える場合に、戻すための処理を共通で入れておいてしまいます。
test/Common_UnitTestCase.php を作成します。
<?php /** * Common_UnitTestCase.php * * @author * @package Common * @version $Id: app.unittestcase.php Exp $ */ /** * UnitTestCase実行クラス * * @author * @access public * @package Common */ class Common_UnitTestCase extends Ethna_UnitTestCase { var $_tmp = array(); /** * テストの初期化 * * @access public */ function setUp() { $this->_tmp['server'] = $_SERVER; $this->_tmp['get'] = $_GET; $this->_tmp['post'] = $_POST; $this->_tmp['file'] = $_FILE; } /** * テストの後始末 * * @access public */ function tearDown() { $_SERVER = $this->_tmp['server']; $_GET = $this->_tmp['get']; $_POST = $this->_tmp['post']; $_FILE = $this->_tmp['file']; } /** * アクションフォームの作成と関連付け * * @access public */ function _createActionForm($form_name) { parent::_createActionForm($form_name); // action_errorの初期化 $ae =& $this->ctl->getActionError(); $ae->clear(); unset($ae->action_form); // Validatorの初期化 unset($this->ctl->class_factory->object['plugin']->obj_registry["Validator"]); } } ?>
これを使われるようにスケルトンファイルを変更します。
また、初期テストとして、テスト未作成だというのが漏れないようにエラーを追加しておきます。
skel/skel.action_test.php
<?php /** * {$action_path} * * @author {$author} * @package Common * @version $Id: $ */ /** * {$action_name}フォームのテストケース * * @author {$author} * @access public * @package Common */ class {$action_form}_TestCase extends Common_UnitTestCase { /** * @access private * @var string アクション名 */ var $action_name = '{$action_name}'; /** * テストの初期化 * * @access public */ function setUp() { parent::setUp(); $this->createActionForm(); // アクションフォームの作成 } /** * テストの後始末 * * @access public */ function tearDown() { parent::tearDown(); } /** * アクションフォームのテストケース * * @access public */ function test_validate() { $this->assertNull('error',"テストケース未作成"); } } /** * {$action_name}アクションのテストケース * * @author {$author} * @access public * @package Common */ class {$action_class}_TestCase extends Common_UnitTestCase { /** * @access private * @var string アクション名 */ var $action_name = '{$action_name}'; /** * テストの初期化 * * @access public */ function setUp() { parent::setUp(); $this->createActionForm(); // アクションフォームの作成 $this->createActionClass(); // アクションクラスの作成 $this->session->start(); // セッションの開始 } /** * テストの後始末 * * @access public */ function tearDown() { $this->session->destroy(); // セッションの破棄 parent::tearDown(); } /** * アクションクラスのテストケース * * @access public */ function test_action() { $this->assertNull('error',"テストケース未作成"); // {$action_name}アクション実行前の認証処理 $forward_name = $this->ac->authenticate(); $this->assertNull($forward_name); // {$action_name}アクションの前処理 $forward_name = $this->ac->prepare(); $this->assertNull($forward_name); // {$action_name}アクションの実装 $forward_name = $this->ac->perform(); $this->assertEqual($forward_name, '{$action_name}'); } } ?>
同じく、ViewTestも修正します。
skel/skel.view_test.php
<?php /** * {$view_path} * * @author {$author} * @package Common * @version $Id: $ */ /** * {$forward_name}ビューの実装 * * @author {$author} * @access public * @package Common */ class {$view_class}_TestCase extends Common_UnitTestCase { /** * @access private * @var string ビュー名 */ var $forward_name = '{$forward_name}'; /** * テストの初期化 * * @access public */ function setUp() { parent::setUp(); $this->createPlainActionForm(); // アクションフォームの作成 $this->createViewClass(); // ビューの作成 } /** * テストの後始末 * * @access public */ function tearDown() { parent::tearDown(); } /** * 遷移前処理のテストケース * * @access public */ function test_view() { $this->assertNull('error',"テストケース未作成"); // {$forward_name}遷移前処理 $this->vc->preforward(); } } ?>
Common_UnitTestManagerクラスを改造
今回の目玉です。
マネージャのテスト対応の Common_UnitTestManager を修正します。
まずは、app以下にあるので、test以下に移動します。
$ mv app/Common_UnitTestManager.php test/
ちょっと長いですが、中身をごっそり以下のように書き換えます。
<?php /** * Common_UnitTestManager.php * * @author * @package Common * @version $Id: $ */ include_once 'Common_UnitTestReporter.php'; include_once 'Common_UnitTestCase.php'; /** * Commonユニットテストマネージャクラス * * @author * @access public * @package Common */ class Common_UnitTestManager extends Ethna_UnitTestManager { /** * @var array 一般テストケース定義 */ var $testcase = array( /* * TODO: ここに一般テストケース定義を記述してください * * 記述例: * * 'util' => 'app/UtilTest.php', */ ); /** * Common_UnitTestManagerのコンストラクタ * * @access public * @param object Ethna_Backend &$backend Ethna_Backendオブジェクト */ function Common_UnitTestManager(&$backend) { parent::Ethna_UnitTestManager($backend); // マネージャのテスト取得($this->testcase優先) $testmanager = $this->_getTestManager(); if (is_array($testmanager) && count($testmanager)) { $this->testcase = array_merge($testmanager, (array)$this->testcase); } $this->ctl->_includeDirectory($this->ctl->getDirectory('test_action')); $this->ctl->_includeDirectory($this->ctl->getDirectory('test_action_cli')); $this->ctl->_includeDirectory($this->ctl->getDirectory('test_action_xmlrpc')); $this->ctl->_includeDirectory($this->ctl->getDirectory('test_view')); } /** * マネージャテストクラスを取得する * * @access private * @return array */ function _getTestManager() { $testcase = array(); $manager_path = $this->ctl->directory["test_manager"]; $prefix = join($this->ctl->plugin_search_appids, '|'); foreach (glob($manager_path.'/*Test.php') as $file) { if (preg_match('/^(('.$prefix.')_.+Manager)Test.php/i', strtolower(basename($file)), $mathces)) { $testcase[$mathces[1]] = '/'.$manager_path.'/'.basename($file); } } return $testcase; } /** * ユニットテストを実行する * * @access private * @return mixed 0:正常終了 Ethna_Error:エラー */ function run() { $action_class_list = $this->_getTestAction(); $view_class_list = $this->_getTestView(); $test =& new GroupTest("Ethna UnitTest"); // アクション foreach ($action_class_list as $action_name) { $action_class = $this->ctl->getDefaultActionClass($action_name, false).'_TestCase'; $action_form = $this->ctl->getDefaultFormClass($action_name, false).'_TestCase'; $test->addTestCase(new $action_class($this->ctl)); $test->addTestCase(new $action_form($this->ctl)); } // ビュー foreach ($view_class_list as $view_name) { $view_class = $this->ctl->getDefaultViewClass($view_name, false).'_TestCase'; $test->addTestCase(new $view_class($this->ctl)); } // 一般 foreach ($this->testcase as $class_name => $file_name) { if(!strlen($target) || substr_count($class_name,$target)){ $dir = $this->ctl->getBasedir().'/'; if(file_exists_ex($file_name)){ include_once $file_name; }elseif(is_file($dir . $file_name)){ include_once $dir . $file_name; }else{ trigger_error("マネージャーテスト取得失敗"); } $testcase_name = $class_name.'_TestCase'; $test->addTestCase(new $testcase_name($this->ctl)); } } // ActionFormのバックアップ $af =& $this->ctl->getActionForm(); //出力したい形式にあわせて切り替える $reporter = new Common_UnitTestReporter(); $test->run($reporter); // ActionFormのリストア $this->ctl->action_form =& $af; $this->backend->action_form =& $af; $this->backend->af =& $af; return array($reporter->report, $reporter->result); } } ?>
Common_UnitTestReporterクラス作成
Common_UnitTestManagerですでに読み込んでいますが、出力形式を変更したりとか、後ほど使うことになるので、Ethna_UnitTestReporter を継承して新規作成します。
追加する機能としては、mbstring.http_output を変更します。
うちでは、携帯案件が多いので、http_output を SJISにしているのですが、それとは関係なく、テストでは、指定された文字コードで出力するようにします。
また、パスしたテストも一覧で確認したい時のために、unitetest.php?all=1 というパラメータ時にメッセージを出すような機能を追加します。
test/Common_UnitTestReporter.php 新規作成
<?php /** * Common_UnitTestReporter.php * * @author * @package Common * @version $Id: Exp $ */ /** * Common_UnitTestReporter * * @author * @access public * @package Common */ class Common_UnitTestReporter extends Ethna_UnitTestReporter { var $_character_set; var $report; var $result; /** * Common_UnitTestReporterのコンストラクタ * * @access public * @param string $character_set キャラクタセット */ function Common_UnitTestReporter($character_set = 'EUC-JP') { ini_set('mbstring.http_output', $character_set); parent::Ethna_UnitTestReporter($character_set); } /** * パス * * @access public * @param string $message メッセージ */ function paintPass($message) { parent::paintPass($message); if (!$_GET['all']) { array_pop($this->report); } } } ?>
テスト用のテンプレートは、デフォルトでは、lib/Ethna/tpl/unittest.tpl が使われますが、今後変更したい場合に、libの下をいじるのはよくないので、testディレクトリの下にテンプレートを持ってきてしまいます。
$ cp lib/Ethna/tpl/unittest.tpl test/
Common_View_UnitTestの作成
ここまで準備してきた Common_UnitTestManager ですが、残念ながらそのままでは読み込んでくれません。
Ethnaデフォルトの機能として、アプリケーションIDのついた UnitTestManager を読み込む仕様なので、Commonという名前で呼び出したい場合には、拡張が必要です。
他のプラグインのように、$plugin_search_appids を見てくれればいいのですが。。
ついでに、Smartyのデリミタも、JSの括弧と被らないようにサイト側で変えている場合がありますので、テスト用テンプレートでは「{ }」になるように設定します。
とりあえず、アプリケーションIDのところだけ、直値で指定しちゃったViewを作成します。
test/Common_View_UnitTest.php 新規作成
<?php /** * Common_View_UnitTest.php * * @author * @license http://www.opensource.org/licenses/bsd-license.php The BSD License * @package Ethna * @version $Id: $ */ /** * __ethna_unittest__ビューの実装 * * @author * @access public * @package Ethna */ class Common_View_UnitTest extends Ethna_ViewClass { /** * 共通値を設定する * * @access protected * @param object Ethna_Renderer レンダラオブジェクト */ function _setDefault(&$renderer) { $smarty =& $renderer->engine; $smarty->left_delimiter = "{"; $smarty->right_delimiter = "}"; } /** * 遷移前処理 * * @access public */ function preforward() { // タイムアウトしないように変更 $max_execution_time = ini_get('max_execution_time'); set_time_limit(0); if (!headers_sent()) { // キャッシュしない header("Expires: Thu, 01 Jan 1970 00:00:00 GMT"); header("Last-Modified: " . gmdate("D, d M Y H:i:s \G\M\T")); header("Cache-Control: no-store, no-cache, must-revalidate"); header("Cache-Control: post-check=0, pre-check=0", false); header("Pragma: no-cache"); } $ctl =& Ethna_Controller::getInstance(); // cores $this->af->setApp('app_id', $ctl->getAppId()); $this->af->setApp('ethna_version', ETHNA_VERSION); // include $file = sprintf("%s/%s_UnitTestManager.php", $ctl->getDirectory('test'), 'Common'); include_once $file; // run $r = sprintf("%s_UnitTestManager", 'Common'); $ut =& new $r($this->backend); list($report, $result) = $ut->run(); // result $this->af->setApp('report', $report); $this->af->setApp('result', $result); // タイムアウトを元に戻す set_time_limit($max_execution_time); } } ?>
Common_Controllerの変更
最後に、いま作った Common_View_UnitTest が使用されるように、Controllerを修正します。
app/Common_Controller.php 新しいメソッドを追加
268行目あたり追記します。
修正前
/**#@-*/
}
?>
修正後
/**#@-*/ /** * Ethnaマネージャを設定する * * @access protected */ function _activateEthnaManager() { if ($this->config->get('debug') == false) { return; } parent::_activateEthnaManager(); // forward設定 $this->forward['__ethna_unittest__'] = array( 'forward_path' => sprintf('%s/test/unittest.tpl', BASE), 'view_name' => 'Common_View_UnitTest', 'view_path' => sprintf('%s/test/Common_View_UnitTest.php', BASE), ); } } ?>
etc/mm-ini.phpを作成
最後に、iniの設定をします。
今回は、「mm」というアプリケーションIDで作成していますので、以下のようにcommonファイルの名前を変更します。
もちろん、他の名前で作成している場合には、そちらを使用してください。
$ mv etc/common-ini.php etc/mm-ini.php
UnitTestを動かすためには、debug設定を true にする必要があります。
etc/mm-ini.php 13行目あたり
// debug
// (to enable ethna_info and ethna_unittest, turn this true)
'debug' => true,
動かしてみる
実際にテストを動かしてみます。
まずは、マネージャの作成。
$ sh ethna.sh add-app-manager sample file generated [/path/to/common/lib/Ethna/skel/skel.app_manager.php -> /path/to/common/app/manager/Mm_SampleManager.php] app-manager script(s) successfully created [/path/to/common/app/manager/Mm_SampleManager.php]
マネージャのテスト作成。
ethnaコマンドにテスト作成のコマンドが無いので、手で作成します。ethna add-app-manager-test とか追加したいですね。
まず、ディレクトリ作成
$ mkdir test/manager
test/manager/Mm_SampleManagerTest.php 新規作成
<?php /** * Mm_SampleManagerTest.php * * @author {$author} * @package Mm * @version $Id: Exp $ */ /** * mobile_indexビューの実装 * * @author {$author} * @access public * @package Mm */ class Mm_SampleManager_TestCase extends Common_UnitTestCase { /** * @access private * @var string マネージャ名 */ var $manager_name = 'sample'; /** * @access private * @var object マネージャ */ var $manager; function setUp() { parent::setUp(); $this->manager = $this->backend->getManager($this->manager_name); //セッションの開始 $this->session->start(); // セッションの開始 } /** * テストの後始末 * * @access public */ function tearDown() { $this->session->destroy(); // セッションの破棄 parent::tearDown(); } /** * テスト */ function test_sample(){ $this->assertNull('test'); } } ?>
Action と Viewも作成。
元々あるIndexアクションは、アプリケーションIDが Commonなのでいったん削除してから新規作成します。
$ rm app/action/Index.php $ rm app/view/Index.php
新規作成
$ sh ethna.sh add-action index file generated [/path/to/common/skel/skel.action.php -> /path/to/common/app/action/Index.php] action script(s) successfully created [/path/to/common/app/action/Index.php] $ sh ethna.sh add-action-test index file generated [/path/to/common/skel/skel.action_test.php -> /path/to/common/test/action/IndexTest.php] action test(s) successfully created [/path/to/common/test/action/IndexTest.php] $ sh ethna.sh add-view index file generated [/path/to/common/skel/skel.view.php -> /path/to/common/app/view/Index.php] view script(s) successfully created [/path/to/common/app/view/Index.php] $ sh ethna.sh add-view-test index file generated [/path/to/common/skel/skel.view_test.php -> /path/to/common/test/view/IndexTest.php] view test(s) successfully created [/path/to/common/test/view/IndexTest.php]
ここまで作業して、各ディレクトリの中は、以下のようになっていると思います。
$ ls app/ Common_ActionClass.php Common_Controller.php Common_UrlHandler.php action action_xmlrpc manager view Common_ActionForm.php Common_Error.php Common_ViewClass.php action_cli filter plugin $ ls app/action Index.php $ ls app/view/ Index.php $ ls etc/ mm-ini.php $ ls test/ Common_UnitTestCase.php Common_UnitTestReporter.php action manager unittest.tpl Common_UnitTestManager.php Common_View_UnitTest.php info.php unittest.php view $ ls test/action/ IndexTest.php $ ls test/manager/ Mm_SampleManagerTest.php $ ls test/view/ IndexTest.php
test/unittest.php にアクセスして動作確認をしてみます。
うちの開発環境の場合、ユーザディレクトリ内の public_htmlにおいているので、直接アクセスできますが、そうではない場合には、公開ディレクトリに unittest.php を、コピーかシンボリックリンク作成などをして確認してみてください。
以下のようにエラーが発生すれば成功です。
次回
コントローラー周りの整備を書きたいと思います。