CakePHP1.3でテストのカバレージを取得する方法

 2010/10/31

以前CakePHP1.2についての手順を書いたが、基本的な流れは変わらない。 今回の環境はCentOS5.5。

必要なものをインストール

xdebugのインストール

最新は2.1.0で、以前はバージョン指定してモジュール取ってきて自前コンパイルしないと動作しなかったが、現在のバージョンは問題ない。

pecl install xdebug

SimpleTestの拡張機能のインストール

僕の環境だけかもしれないが、SimpleTestまるごとをチェックアウトして、CakePHPに突っ込んだところエラーが出て動作しなかったので、拡張機能のみ別途インストールする。

svn co https://simpletest.svn.sourceforge.net/svnroot/simpletest/simpletest/trunk/extensions/coverage

取得したらcoverageフォルダをsimpletest/extensions/に配置する。

DB/sqliteからpdoを使用するようにハックする

困ったことに、SimpleTestのカバレージ測定ツールはDB/sqliteを使うようになっているが、CentOS5上のPHP5で動作させるには問題が一杯あるので、ちょっとハックする。 また標準だと日本語が文字化けするのでそいつも修正する。

Index: coverage.php
===================================================================
--- coverage.php	(リビジョン 1999)
+++ coverage.php	(作業コピー)
@@ -178,10 +178,10 @@
     static function isCoverageOn() {
         $coverage = self::getInstance();
         $coverage->readSettings();
-        if (empty($coverage->log) || !file_exists($coverage->log)) {
-            trigger_error('No coverage log');
-            return False;
-        }
+        //if (empty($coverage->log) || !file_exists($coverage->log)) {
+        //    trigger_error('No coverage log');
+        //    return False;
+        //}
         return True;
     }

Index: coverage_utils.php
===================================================================
--- coverage_utils.php	(リビジョン 1999)
+++ coverage_utils.php	(作業コピー)
@@ -20,10 +20,10 @@
     }

     static function requireSqlite() {
-        if (!self::isPackageClassAvailable('DB/sqlite.php', 'SQLiteDatabase')) {
-            echo "sqlite library is required to be installed and available in include_path";
-            exit(1);
-        }
+//        if (!self::isPackageClassAvailable('DB/sqlite.php', 'SQLiteDatabase')) {
+//            echo "sqlite library is required to be installed and available in include_path";
+//            exit(1);
+//        }
     }

     static function isPackageClassAvailable($includeFile, $class) {
@@ -111,4 +111,4 @@
         return isset($val) ? $val : $default;
     }
 }
-?>
\ No newline at end of file
+?>
Index: coverage_data_handler.php
===================================================================
--- coverage_data_handler.php	(リビジョン 1999)
+++ coverage_data_handler.php	(作業コピー)
@@ -6,7 +6,7 @@
 /**
  * @todo	which db abstraction layer is this?
  */
-require_once 'DB/sqlite.php';
+//require_once 'DB/sqlite.php';

 /**
  * Persists code coverage data into SQLite database and aggregate data for convienent
@@ -22,20 +22,21 @@

     function __construct($filename) {
         $this->filename = $filename;
-        $this->db = new SQLiteDatabase($filename);
+        // $this->db = new SQLiteDatabase($filename);
+        $this->db = new PDO("sqlite:".$filename);
         if (empty($this->db)) {
             throw new Exception("Could not create sqlite db ". $filename);
         }
     }

     function createSchema() {
-        $this->db->queryExec("create table untouched (filename text)");
-        $this->db->queryExec("create table coverage (name text, coverage text)");
+        $this->db->query("create table untouched (filename text)");
+        $this->db->query("create table coverage (name text, coverage text)");
     }

     function &getFilenames() {
         $filenames = array();
-        $cursor = $this->db->unbufferedQuery("select distinct name from coverage");
+        $cursor = $this->db->query("select distinct name from coverage");
         while ($row = $cursor->fetch()) {
             $filenames[] = $row[0];
         }
@@ -49,7 +50,7 @@
             $relativeFilename = self::ltrim(getcwd() . '/', $file);
             $sql = "insert into coverage (name, coverage) values ('$relativeFilename', '$coverageStr')";
             # if this fails, check you have write permission
-            $this->db->queryExec($sql);
+            $this->db->query($sql);
         }
     }

@@ -65,10 +66,10 @@
         $sql = "select coverage from coverage where name = '$file'";
         $aggregate = array();
         $result = $this->db->query($sql);
-        while ($result->valid()) {
-            $row = $result->current();
+        while ($row = $result->fetch()) {
+            // $row = $result->current();
             $this->aggregateCoverage($aggregate, unserialize($row[0]));
-            $result->next();
+            // $result->next();
         }

         return $aggregate;
@@ -107,19 +108,19 @@
     function writeUntouchedFile($file) {
         $relativeFile = CoverageDataHandler::ltrim('./', $file);
         $sql = "insert into untouched values ('$relativeFile')";
-        $this->db->queryExec($sql);
+        $this->db->query($sql);
     }

     function &readUntouchedFiles() {
         $untouched = array();
         $result = $this->db->query("select filename from untouched order by filename");
-        while ($result->valid()) {
-            $row = $result->current();
+        while ($row = $result->fetch()) {
+            // $row = $result->current();
             $untouched[] = $row[0];
-            $result->next();
+            // $result->next();
         }

         return $untouched;
     }
 }
-?>
\ No newline at end of file
+?>
Index: templates/file.php
===================================================================
--- templates/file.php	(リビジョン 1999)
+++ templates/file.php	(作業コピー)
@@ -39,7 +39,7 @@
 < ?php foreach ($lines as $lineNo => $line) { ?>
     <tr>
        <td><span class="lineNo">< ?php echo $lineNo ?></span></td>
-       <td><span class="<?php echo $line['lineCoverage'] ?> code">< ?php echo htmlentities($line['code']) ?></span></td>
+       <td><span class="<?php echo $line['lineCoverage'] ?> code">< ?php echo htmlentities($line['code'], ENT_QUOTES ,'UTF-8') ?></span></td>
     </tr>
 < ?php } ?>

パッチはこちら

カバレージ測定ツールの実行

面倒なのでシェルにしてみた。(若干Hudson前提) startコマンドを実行すると、カバレージ測定の準備が完了する。この時点でテストを実行する(test)と、xdebugの結果がすべてDBに登録される。 実行完了後は、カバレージ測定を終了させ(close)、レポートを作成する(report)

#!/bin/sh

root_dir="/var/lib/hudson/jobs/sample_project/workspace"
simpletest_coverage_dir="$root_dir/trunk/source/vendors/simpletest/extensions/coverage"

php_option="-d include_path=.:/usr/share/pear:$simpletest_coverage_dir -d auto_prepend_file=$simpletest_coverage_dir/autocoverage.php"

start() {
 php $php_option $simpletest_coverage_dir/bin/php-coverage-open.php --include=$root_dir/trunk/source/app/controllers/.*\.php$ --include=$r
oot_dir/trunk/source/app/models/.*\.php$ --exclude='$root_dir/trunk/source/.*/tests/.*' --exclude='$root_dir/trunk/source/.*/.svn/
.*' --maxdepth=1
}

close() {
 php $php_option $simpletest_coverage_dir/bin/php-coverage-close.php
}

test() {
 php $php_option $root_dir/trunk/source/cake/console/cake.php testsuite app all
}

report() {
 php $php_option $simpletest_coverage_dir/bin/php-coverage-report.php
 cp -Rp coverage-report $root_dir/
}

case "$1" in
 start)
       start
       ;;
 close)
       close
       ;;
 report)
       report
       ;;
 test)
       test
       ;;
 all)  start; test; close; report;
       ;;
 *)
       echo "start|test|close|report|all"
esac

こんな感じで用意しておくと、シェルで自由自在に叩ける。

できあがりの図

こんな感じでレポートが吐かれる。リンクをクリックすると、そのモジュールのソースが色分けで表示される。

最後に

僕の環境では、上述したシェルをHudsonのジョブに組み込んで、毎日カバレージレポートを自動で出力している。カバレージが低い箇所で複雑度が高そうな箇所から順にテストを補強したりしている。

PHPUnitだとClover互換のXMLを出力してくれるので、もっとグラフィカルにHudsonに統合できるのだが、残念ながらSimpleTestにはその機能はない。ただカバレージの測定結果はsqliteのデータベースに保存され、その後レポート出力しているので、XML出力用のreporterを作ればいいんじゃないかと思う。

 2010/10/31

サイト内検索


著作

寄稿

Latest post: