2006/08/23
テスト完了後にSVNのコミットをするPhingTask
どっかで見掛けたんだけど、phpのデプロイにsvnを使ってコミットにフックしてテストを流すというヤツ(mixiだっけかな)
僕の場合は逆で、ローカルでテスト完了後(エラー無し、すべてのテスト通過後)にsvnにコミットしてる。phingで自動化して。(サーバテストは別に行う)
phingにはPHPUnit2のRunnerもあるので、そいつを使ってテストを全て流す。
PHPUnit2のTestRunからsvnへのコミット(デプロイとは違うけど)は以下のようなTask
<?php
require_once 'PHPUnit2/Framework/TestListener.php';
require_once 'phing/Task.php';
/**
* @author nowel
*/
class TestAndDeployTask extends Task {
private $listener = array();
private $commits = array();
private $deploy = false;
private $complete = true;
public function init(){
}
public function main(){
$suites = array();
foreach($this->listener as $listener){
$suites = $listener->getTestSuites();
}
$results = array();
foreach($suites as $suite){
$result = new PHPUnit2_Framework_TestResult();
$result->addListener($listener);
$suite->run($result);
$results[] = $result;
}
foreach($results as $res){
if($res->wasSuccessful() && $res->allCompletlyImplemented()){
continue;
}
$this->testsNotComplete($res);
}
if($this->complete){
$this->allCompletlyImplemented();
}
}
public function allCompletlyImplemented(){
$this->log("All Completlyment!!");
$timer = new Timer();
$timer->start();
$this->log("[start] SVN Commit");
foreach($this->commits as $commit){
try{
$commit->init();
$commit->main();
} catch(Exception $e){
$this->log("[exception] " . $e->getMessage());
}
}
$this->log("[end] SVN Commit");
$timer->stop();
$this->log("time: " . $timer->getElapsedTime());
}
public function testsNotComplete(PHPUnit2_Framework_TestResult $result){
$this->complete = false;
foreach($result->failures() as $failure){
$this->log("[failure] " . $failure->exceptionMessage());
}
}
public function setDeploy($boolean){
$this->deploy = $boolean;
}
public function createListener(){
$listener = new TestListenerTask($this->project);
$this->listener[] = $listener;
return $listener;
}
public function createCommit(){
$commitSvn = new SvnCommitTask($this->project);
$this->commits[] = $commitSvn;
return $commitSvn;
}
}
?>
テストの結果がwasSuccessfulであり、allCompletlyImplementedの場合にsvnでコミットをします。
それ以外はコミットさせない。手動でやっちゃうのは反則。
あと、ここでは、Listenerでやってますが、標準のじゃないです。PhingのPHPUnit2対応のListenerがあまりカッコよくない(というか日本語じゃない)ので以下のようなListenerを用意してます。
<?php
require_once 'phing/types/FileSet.php';
require_once 'PHPUnit2/Framework/TestListener.php';
/**
* @author nowel
*/
class TestListenerTask implements PHPUnit2_Framework_TestListener {
const DIR_SEP = DIRECTORY_SEPARATOR;
private $project = null;
private $filesets = array();
private $suiteTimer = null;
private $timer = null;
public function __construct(Project $project){
$this->project = $project;
}
public function addFileSet(FileSet $fileset){
$this->filesets[] = $fileset;
}
public function getTestSuites(){
$suites = array();
foreach($this->filesets as $fileset){
$ds = $fileset->getDirectoryScanner($this->project);
$ds->scan();
$files = $ds->getIncludedFiles();
foreach($files as $file){
$path = $this->project->getBasedir()->getPath() . self::DIR_SEP;
$path .= $ds->getBaseDir() . self::DIR_SEP . $file;
$this->project->log('[require_once]' . basename($path));
require_once $path;
$filePath = basename($path);
$class = StringHelper::root($filePath, '.class.php');
if($filePath == $class){
$class = StringHelper::root($filePath, '.php');
}
$suites[] = new PHPUnit2_Framework_TestSuite(new ReflectionClass($class));
}
}
return $suites;
}
public function addError(PHPUnit2_Framework_Test $test, Exception $e){
echo $test->getName() . "エラーが発生したよ。: Message[" . $e->getMessage() , "]" . PHP_EOL;
}
public function addFailure(PHPUnit2_Framework_Test $test, PHPUnit2_Framework_AssertionFailedError $e){
echo $test->getName() . "テストに失敗したよ。: Message[" . $e->getMessage() , "]" . PHP_EOL;
}
public function addIncompleteTest(PHPUnit2_Framework_Test $test, Exception $e){
echo $test->getName() . "テストが完了しなかったよ。: Message[" . $e->getMessage() , "]" . PHP_EOL;
}
public function startTestSuite(PHPUnit2_Framework_TestSuite $suite){
echo PHP_EOL;
echo "テストシート[" . $suite->getName() . "]を開始します。(" . $suite->countTestCases() . ")" . PHP_EOL;
$this->suiteTimer = new Timer();
$this->suiteTimer->start();
}
public function endTestSuite(PHPUnit2_Framework_TestSuite $suite){
echo "テストシート[" . $suite->getName() . "]を終了します。" . PHP_EOL;
$this->suiteTimer->stop();
echo "このシートの経過時間: " . $this->suiteTimer->getElapsedTime();
echo PHP_EOL;
echo PHP_EOL;
}
public function startTest(PHPUnit2_Framework_Test $test){
echo "テスト[" . $test->getName() . "]を実行します。(" . $test->countTestCases() .")" . PHP_EOL;
$this->timer = new Timer();
$this->timer->start();
}
public function endTest(PHPUnit2_Framework_Test $test){
echo "テスト[" . $test->getName() . "]を終了します。" . PHP_EOL;
$this->timer->stop();
echo "このテストの経過時間: " . $this->timer->getElapsedTime();
echo PHP_EOL;
}
}
?>
ほとんどただのTaskにListenerのインタフェース付けただけだけど、日本語と1つのテスト単位と1つのシート単位に経過時間もみれるので、メソッド単位のテストなんかでは重たい部分が把握できます。
んで、Svnへはこんな感じでSvnBaseTaskを上書きて使ってます
<?php
require_once 'phing/tasks/ext/svn/SvnBaseTask.php';
/**
* @author nowel
*/
class SvnCommitTask extends SvnBaseTask {
const DIR_SEP = DIRECTORY_SEPARATOR;
private $filesets = array();
private $command = null;
private $options = array();
private $svnArgs = array();
private $username;
private $password;
public function __construct(Project $project){
$this->project = $project;
}
public function main(){
$this->setup('ci');
echo $this->run(array("message" => "Phing Test Complete at " . date("Y/m/d H:i:s")));
}
public function setup($mode){
$this->command = $mode;
$this->options = array('fetchmode' => VERSIONCONTROL_SVN_FETCHMODE_ASSOC, 'svn_path' => $this->getSvnPath());
$this->svnArgs = array('username' => $this->username, 'password' => $this->password);
}
public function run($swiches){
$this->svnstack = PEAR_ErrorStack::singleton('VersionControl_SVN');
$svn = VersionControl_SVN::factory($this->command, $this->options);
$swiches = array_merge($this->svnArgs, $swiches);
$args = array($this->getWorkingCopy());
if ($output = $svn->run($args, $swiches)){
return $output;
} else {
if (count($errs = $this->svnstack->getErrors())) {
$err = current($errs);
throw new BuildException("Failed to run the 'svn " . $this->mode . "' command: " . $err['message']);
}
}
}
public function setUserName($username){
$this->username = $username;
}
public function setPassWord($password){
$this->password = $password;
}
public function getUserName(){
return $this->username;
}
public function getPassWord(){
return $this->password;
}
}
?>
build.xmlには以下な感じで書いておく(workingCopyは予めチェックアウトで)
<?xml version="1.0"?>
<project name="s2service.php5" default="test" basedir=".">
<taskdef name="tad" classname="phing.task.TestAndDeployTask" />
<taskdef name="listener" classname="phing.task.TestListenerTask" />
<taskdef name="commit" classname="phing.task.SvnCommitTask" />
<target name="test">
<php expression="require_once('test/enviroment.inc.php')"/>
<tad deploy="false">
<listener>
<fileset dir="test">
<include name="**/*Test.class.php" />
</fileset>
</listener>
<commit svnPath="/usr/bin/svn"
repositoryUrl="svn://path/to/repos" workingCopy="src"
username="username" password="password">
</commit>
</tad>
</target>
</project>
phingのデフォルトのターゲットがtestであるので、以下のように実行するとパパっといきます。
# build.xmlの書き方とかはs2container.php5/s2baseを読めば十分通用すると思います。
> phing
Buildfile: /workspace/service/soap/deploy/build.xml
s2service.php5 > test:
[php] Evaluating PHP expression: require_once('test/enviroment.inc.php')
[require_once]HogeTest.class.php
テストシート[HogeTest]を開始します。(2)
テスト[testHoge1]を実行します。(1)
テスト[testHoge1]を終了します。
このテストの経過時間: 0.00046
テスト[testHoge2]を実行します。(1)
テスト[testHoge2]を終了します。
このテストの経過時間: 0.00013
テストシート[HogeTest]を終了します。
このシートの経過時間: 0.00144
[tad] All Completlyment!!
[tad] [start] SVN Commit
[SvnCommit] Commit files :
送信しています src/org/seasar/aaa.php
ファイルのデータを送信中です.
リビジョン 23 をコミットしました。 [tad] [end] SVN Commit
[tad] time: 2.84013
BUILD FINISHED
Total time: 3.1066 seconds
ディレクトリとしては以下を想像
> ls build.xml phing src test doc
S2PHPだと、pear packageタスクも用意してあるんで、ほんとphingって打つだけで全て流れてくれて便利。
後はあれ、サーバへのデプロイをどうにか自動化したい。
# そういうこと言うとリモートデバグとかに発展するので今日はここまで。
Trackback
No Trackbacks
Track from Your Website
http://blog.xole.net/trackback/tb.php?id=486

Comment
No Comments