前回は、EntityとDaoを作成しました。今回は、Serviceを作成してみましょう。
前回は、「次回はDaoのUnitTest」とか書いちゃいましたけど、ServiceのUnitTestということにしちゃいます。
Serviceの作成
sampleプロジェクトディレクトリでs2baseコマンドを実行して、コンソールを起動します。
そうしたら、以下の様に入力してS2BasePluginServiceという名前でServiceを作成してみましょう。
[ Command list ] 0 : (exit) 1 : dao 2 : dicon 3 : entity 4 : goya 5 : interceptor 6 : module 7 : service choice ? : 7 <-serviceを表す7を入力してエンターキー [ Module list ] 0 : (exit) 1 : sample_module choice ? : 1 <-sample_moduleを表す7を入力してエンターキー service class name ? : S2BasePluginService <-Service名であるS2BasePluginServiceを入力してエンターキー [ generate information ] module name : sample_module service class name : S2BasePluginService service test class name : S2BasePluginServiceTest confirm ? (y/n) : y <-yを入力してエンターキーここもS2Base1.0系とは少し動作が変わっていますね。
S2Base1.0系では、serviceコマンドを実行した場合interfaceとその実装、さらにdiconが作成されていたんですが、
S2Base2.0系では、実装だけが作成されるようになっています。
ちなみにServiceは、sampleプロジェクトのapp/modules/sample_module/serviceディレクトリに、
テストクラスは、sampleプロジェクトのtest/modules/sample_module/serviceディレクトリに出力されます。
ビジネスロジックを実装するServiceをinterfaceで定義していたのはDIする為ですが、
この部分をDIするメリットよりも定義(interface)と実装で2重管理が発生するデメリットの方が大きいかもです。
UnitTestを実行するとしても、プレーンなS2Baseのプロジェクトでは、
Serviceが一番上位の実装ということになります。
この部分のMockを使ってUnitTestをすることって、あんまり需要がなさそうです。
UIを作って、UIとServiceを別々に開発していくのであれば必要ですが、
その場合はInterfaceを定義して、実装の名前を変える(お約束な名前ならHogeServiceImple)とかに変えるだけですし。
S2ContainerからHogeServiceという名前でコンポーネントを取得する、
という規則でコードを書いているなら(普通に作っていくとそうなりますよね。)、
S2Containerから取得する際に使うHogeServiceがinterfaceなら、
デフォルトの動作である自動バインディングでS2Caontianerが実装クラスであるHogeServiceImpleを返してくれます。
Serviceを編集
先程のserviceコマンドで作成されたS2BasePluginServiceは、以下の様な内容になっています。
<?php class S2BasePluginService { public function __construct(){} }何も実装されていないプレーンなクラスです。
ちなみにgoyaコマンドを使ってService、Dao、Entittyを一度に作成した場合は、
Daoを参照するフィールドとSetterメソッドが追加された形でServiceが作成されます。
で、S2BasePluginServiceはS2BasePluginDaoを参照するので、以下の様に編集してS2BasePluginDaoを参照できるようにします。
<?php class S2BasePluginService { /** * S2BasePluginDaoの実装です。 * @var S2BasePluginDao S2BasePluginDao */ private $dao; public function __construct(){} /** * S2BasePluginDaoを設定します。 * @param S2BasePluginDao $dao 設定するS2BasePluignDao */ public functon setS2BasePluginDao(S2BasePluginDao $dao) { $this->dao = $dao; } }追加したフィールドとメソッドにはコメントが入っています。
で、この$daoをDIする為にdiconファイルを作り・・・ません。
S2ContainerApplicationContextを使う場合は、自動的にDIされます。
次に、Serviceのメソッドを実装してみましょう。
<?php class S2BasePluginService { /** * S2BasePluginDaoの実装です。 * @var S2BasePluginDao S2BasePluginDao */ private $dao; public function __construct(){} /** * S2BasePluginDaoを設定します。 * @param S2BasePluginDao $dao 設定するS2BasePluignDao */ public function setS2BasePluginDao(S2BasePluginDao $dao) { $this->dao = $dao; } /** * 全てのS2BasePluginを取得します。 * @return S2Dao_ArrayList S2BasePluginを格納したS2Dao_ArrayList */ public function getAllPlugins() { return $this->dao->findAllList(); } /** * 指定されたS2BasePluginを追加します。 * @param S2BasePlugin $entity 追加するS2BasePlugin * @return int 更新された桁数 */ public function insertPlugin(S2BasePlugin $entity) { return $this->dao->insert($entity); } /** * 指定されたS2BasePluginを更新します。 * @param S2BasePlugin $entity 更新するS2BasePlugin * @return int 更新された桁数 */ public function updatePlugin(S2BasePlugin $entity) { return $this->dao->update($entity); } /** * 指定されたS2BasePluginを削除します。 * @param S2BasePlugin $entity 削除するS2BasePlugin * @return int 更新された桁数 */ public function deletePlugin(S2BasePlugin $entity) { return $this->dao->delete($entity); } }えーっと、S2BasePluginDaoをWrapしただけですね・・・^^;
UnitTestいたしましょう。
実装ができたならテスト。テスト駆動なら既に順序が逆っ!
気を取り直して、テストクラスにメソッドを実装していきましょう。
sampleプロジェクトのtest/modules/sample_module/serviceディレクトリのS2BasePluginServiceTest.phpです。
<?php class S2BasePluginServiceTest extends PHPUnit_Framework_TestCase { private $module = 'sample_module'; private $container; private $service; public function __construct($name) { parent::__construct($name); } public function testA(){ } public function setUp(){ print __CLASS__ . '::' . $this->getName() . PHP_EOL; $moduleDir = S2BASE_PHP5_ROOT . "/app/modules/{$this->module}"; require_once($moduleDir . "/{$this->module}.inc.php"); $this->container = S2ContainerApplicationContext::create(); $this->service = $this->container->getComponent('S2BasePluginService'); } public function tearDown() { print PHP_EOL; $this->container = null; $this->service = null; } }これを、(あくまでDelfino的に)テストしやすい様に編集。
PDOをフィールドにもって、SQLを直接発行できるようにしてみます。
それからsetUp()とtearDown()の中を変更してテスト実行後にロールバックするようにします。
<?php class S2BasePluginServiceTest extends PHPUnit_Framework_TestCase { private $module = 'sample_module'; private $container; private $service; /** * PDOへの参照 * @var PDO */ private $pdo; public function __construct($name) { parent::__construct($name); } public function setUp(){ print __CLASS__ . '::' . $this->getName() . PHP_EOL; $moduleDir = S2BASE_PHP5_ROOT . "/app/modules/{$this->module}"; require_once($moduleDir . "/{$this->module}.inc.php"); $this->container = S2ContainerApplicationContext::create(); $this->service = $this->container->getComponent('S2BasePluginService'); $dataSource = $this->container->getComponent("dataSource"); $this->pdo = $dataSource->getConnection(); $this->pdo->beginTransaction(); } public function tearDown() { print PHP_EOL; $this->pdo->rollBack(); $this->pdo = null; $this->container = null; $this->service = null; } }
じゃあ、まずはS2BasePluginService::getAllPlugins()
public function testGetAllPlugins() {
//Daoで全てのEntityを取得
$daoResult = $this->service->getAllPlugins();
//PDOで全てのカラムを取得
$pdoResult = $this->pdo->query("select * from s2base_plugin");
//レコード数でアサート
$this->assertEquals($daoResult->size(), count($pdoResult->fetchAll()));
//PreparedStatementを設定
$pdoResult = $this->pdo->prepare("select * from s2base_plugin where id = ?");
//ループしてアサート
$iterator = $daoResult->iterator();
while ($iterator->valid()) {
$plugin = $iterator->current();
$pdoResult->execute(array($plugin->getId()));
$this->assertEquals($plugin->getName(), $pdoResult->fetchColumn(1));
$iterator->next();
}
}
更新系はこんな感じで
/**
* S2BasePluginService::insertPlugin()のテスト
*/
public function testInsertPlugin() {
//念の為全レコード削除
$this->pdo->exec("delete from s2base_plugin");
//S2BasePluginを生成
$plugin = new S2BasePlugin();
$plugin->setName("TestPlugin");
//Serviceでinsert
$serviceResult = $this->service->insertPlugin($plugin);
$this->assertEquals($serviceResult, 1);
//PDOで取得
$pdoResult = $this->pdo->query("select * from s2base_plugin where name = ?");
$pdoResult->execute(array($plugin->getName()));
$this->assertEquals($plugin->getName(), $pdoResult->fetchColumn(1));
}
/**
* S2BasePluginService::updatePlugin()のテスト
*/
public function testUpdatePlugin() {
//Serviceで全てのEntityを取得
$serviceResult = $this->service->getAllPlugins();
$iterator = $serviceResult->iterator();
//1件目のレコードを対象にします。
$plugin = $iterator->current();
//update
$plugin->setName("TestPlugin");
$this->service->updatePlugin($plugin);
//PDOで取得
$pdoResult = $this->pdo->query("select * from s2base_plugin where id = ?");
$pdoResult->execute(array($plugin->getId()));
$this->assertEquals($plugin->getName(), $pdoResult->fetchColumn(1));
}
/**
* S2BasePluginService::deletePlugin()のテスト
*/
public function testDeletePlugin() {
//Serviceで全てのEntityを取得
$serviceResult = $this->service->getAllPlugins();
$iterator = $serviceResult->iterator();
//1件目のレコードを対象にします。
$plugin = $iterator->current();
//delete
$this->service->deletePlugin($plugin);
//PDOで取得
$pdoResult = $this->pdo->query("select * from s2base_plugin where id = ?");
$pdoResult->execute(array($plugin->getId()));
$this->assertEquals(count($pdoResult->fetchAll()), 0);
}
ID指定で一意のEntityをとるメソッドがないので苦しいですね^^;テストメソッドの中で問答無用でレコードの更新をしてますが、
S2BasePluginServiceTest::setUp()とS2BasePluginServiceTest::tearDown()の中で
トランザクションを使ってるからこんなことができます。
トランザクションを使わないまま、こんなメソッドを実行したら
大変なことになりますから、ご注意です。
テストの実行
後は作成したテストを実行します。
S2Base1.0系とはテストの実行方法が違うので注意です。
% s2bsae test S2BasePluginServices2baseコマンドの第一引数にtestとつけて、第二引数にテスト対象を指定します。
実行されるテストの対象となる条件は、以下の通りです。
sampleプロジェクトのtestディレクトリ以下にあるもの。
で*Test*.phpというファイル名であるもの。
第二引数で指定されたテスト対象に、正規表現でマッチするもの
S2Base1.0系ではphingに-Dオプションを指定して、
td=(対象とするディレクトリ)もしくは、tt=(対象とするパターン)という風に
ユニットテストを実行していましたから、正規表現を使うことでひとつに統合されたわけですね。
とはいってもphingを使ったS2Base1.0系のテストを実行することもできますから
従来どおりの方法も使えます。
まとめ
S2Base2.0系からServiceの生成内容が変更になっています。
既存の資産の再利用には問題ないでしょうが、S2Base2.0系の実装の方が合理的に思えます。
それから、今更ですけどphingを使わなくてもS2Baseのコンソールが起動できるようになりました。
UnitTestの実行も同様です。UnitTestの実行はS2Base2.0系の方がシンプルにできてますね。
ということで、明日以降はInterceptorを実装したりとかロギングについてとか追っかけてまいります。