今回、やりたいことは、ちょっと前の日記にも書いたけど、こんな内容のことを実施したいと思っている。
今更この程度のことかよ!?という内容かも知れないけど、この程度のことができないのも事実なので、淡々と学んでいくしかない。
現在は手動で次のようなコマンドを入力(もちろん履歴とか使うけど)して PHPUnit のテストを実行している。
% phpunit -c phpunit.xml [対象のテストファイル]
phpunit.xml
は、コンフィグファイルであり、普段使う設定が書き込まれている。その設定では、プロジェクト内の全てのテストを実行が指定されている。必ずしも -c
オプションでコンフィグを指定する必要はないのだけど、複数の設定ファイルを使い分けている関係で常用している。
ひとつのテストメソッドをちょっとして修正の動作確認をしたい場合には、全テストはちょっと大げさかつ効率が悪い。ファイル名を引数として渡すことで「いま修正しているファイル」を対象にテスト実行をしている。
更に実行時間を短くしたい場合には、--filter
オプションに修正中のテストメソッド名を指定して、実行対象メソッドも限定しているのだが、今回の「ファイル変更の監視」だと、修正中のメソッドまでは把握できなさそうなので、いったん置いておく。
この記事用に作ったサンプル用の PHPUnit 環境はこんな感じにしている。
% ls -1
composer.json
composer.lock
composer.phar*
phpunit.xml
tests/
vendor/
PHPUnit は Composer を使ってインストール。composer.json
の中身はこれだけ。
% cat composer.json
{
"require": {
"phpunit/phpunit": "3.7.*"
}
}
テストファイルはこんな風にしてみた。
% ls -1
fugaTest.php
hogeTest.php
それぞれのテスト内容は、ダミーなので、とりあえず絶対成功するヤツを書いている。
% cat tests/fugaTest.php
<?php
class FugaTest extends PHPUnit_Framework_TestCase
{
public function testDummy1()
{
$this->assertFalse(false);
}
}
% cat tests/hogeTest.php
<?php
class HogeTest extends PHPUnit_Framework_TestCase
{
public function testDummy2()
{
$this->assertTrue(true);
}
}
これを実行すると、「OK」になる。これで全てのテストが成功している状態。
% ./vendor/bin/phpunit -c phpunit.xml
PHPUnit 3.7.29 by Sebastian Bergmann.
Configuration read from /Users/suzuki/work/grunt/phpunit.xml
..
Time: 36 ms, Memory: 2.50Mb
OK (2 tests, 2 assertions)
さて、ここから本題。
まず npm init
を使って、package.json ファイルを作成してみた。
% npm init
いろいろと質問されてくるので、適当に答えつつ出来上がった package.json の中身はこんな感じ。いろいろ適当なのだけど、とりあえずローカルで使うだけなので、まぁ、いいか、と。
{
"name": "phpunit-watch",
"version": "0.0.0",
"description": "PHPUnit test watch",
"main": "index.js",
"directories": {
"test": "tests"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "BSD-2-Clause"
}
次に Grunt 関連のインストール。grunt-cli
、grunt-contrib-watch
、grunt-phpunit
の3つのパッケージを指定してみる。grunt-contrib-watch
と grunt-phpunit
は npm のパッケージであると同時に、Grunt のプラグインという位置付けでもある。
% npm install grunt-cli grunt-contrib-watch grunt-phpunit --save-dev
--save-dev
オプションを付けると、インストールしたパッケージを package.json
へ記録してくれるそうだ。
{
"name": "phpunit-watch",
"version": "0.0.0",
"description": "PHPUnit test watch",
"main": "index.js",
"directories": {
"test": "tests"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "BSD-2-Clause",
"devDependencies": {
"grunt-phpunit": "~0.3.2",
"grunt-cli": "~0.1.13",
"grunt": "~0.4.2",
"grunt-contrib-watch": "~0.5.3"
}
}
確かに devDependencies
に追記されている。
また、install
したパッケージは、node_modules
ディレクトリ以下にインストールされているのが分かる。
% ls -1
composer.json
composer.lock
composer.phar*
node_modules/
package.json
phpunit.xml
tests/
vendor/
node_modules
% ls -1 node_modules/
grunt/
grunt-cli/
grunt-contrib-watch/
grunt-phpunit/
必要なパッケージのインストールが終わったら、今後は実行用の設定に入る。設定は Gruntfile
という名前のファイルに記述する。JavaScript 版の Gruntfile.js
と CoffeeScript 版の Gruntfile.coffee
が利用できる。
grunt
コマンドの設定ファイル Gruntfile
は、make
コマンドに対する Makefile
のような存在だと思えばピッタリくる模様。
まずは、grunt
コマンドで PHPUnit の実行ができるように設定を書く。今回は、JavaScript 版の Gruntfile.js
にした。
module.exports = function(grunt) {
grunt.initConfig({
phpunit: {
classes: {
dir: './tests/'
},
options: {
bin: './vendor/bin/phpunit',
configuration: './phpunit.xml'
}
}
});
// パッケージの読み込み
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-phpunit');
}
phpunit:
以下に書いてある設定が grunt-phpunit のもの。ここでは、テスト対象のクラスを指定している classes:
と options:
を利用している。
classes:
の dir:
でテスト対象のディレクトリを指定するようだ。phpunit.xml
の中でもテスト対象を指定しているのでダブってしまうのだけど、ここはそういうものとして利用する。
options:
の内容は、PHPUnit のコマンドラインオプションと対比してあるようなので、マニュアルとにらめっこすれば、意味が分かると思う。
この状態で grunt
コマンドにタスク名 phpunit
を指定して実行すると、PHPUnit が走って、先ほどの結果と同じ出力を得られる。
% ./node_modules/.bin/grunt phpunit
Running "phpunit:classes" (phpunit) task
Starting phpunit (target: classes) in tests/
PHPUnit 3.7.29 by Sebastian Bergmann.
Configuration read from /Users/suzuki/work/grunt/phpunit.xml
..
Time: 18 ms, Memory: 2.50Mb
OK (2 tests, 2 assertions)
Done, without errors.
今度は最終目的である「ファイルの変更を監視」をする設定を書く。これは grunt-contrib-watch で実現できた。
module.exports = function(grunt) {
grunt.initConfig({
phpunit: {
classes: {
dir: './tests/'
},
options: {
bin: './vendor/bin/phpunit',
configuration: './phpunit.xml'
}
},
watch: {
scripts: {
// 監視対象のファイル
files: ['./tests/*Test.php'],
// 変更を検知した時に実行するタスク
tasks: ['phpunit'],
options: {
spawn: false,
}
}
}
});
// watch イベントが発生したら、phpunit: classes: dir: の値を動的に変更
grunt.event.on('watch', function(action, filepath, target) {
grunt.config(['phpunit', 'classes', 'dir'], filepath);
});
// パッケージの読み込み
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-phpunit');
}
追記したのは、watch: { ... }
のブロックと grunt.event.on('watch', ... });
のブロック。
watch: { ... }
内で、監視対象のファイルと、変更のあった場合に実行するタスクに phpunit
タスクを指定している。これだけの指定でも、「監視対象のファイルが更新されたら phpunit
タスクを実行する」ということなら実現できる。
でも、いまやりたいのは、「修正(更新)したファイルだけを対象にしたい」ということなので、もうひと工夫しているのが、grunt.event.on('watch', ... });
のブロック。
上記のように書くと、watch
イベントが発生した場合に、phpunit
タスクの classes:
、dir:
を設定しなおしてくれるようだ。つまり、テスト対象のディレクトリ(ファイル)の設定を「変更のあったファイル(filepath
)」としてくれるのだ。
その動的な設定変更の後で phpunit
タスクが実行されるので、目的であった「修正(変更)があったテストファイルのみテスト実行する」がこれで実現できる。
では、実行してみよう。
監視するには、引数にタスク名 watch
を付けて grunt
を起動する。
% ./node_modules/.bin/grunt watch
Running "watch" task
Waiting...
この状態で、エディタでテストケースを修正すると、その変更を検知して、phpunit
タスクを実行してくれる。
例えば、fugaTest.php
ファイルを更新してみよう。
<?php
class FugaTest extends PHPUnit_Framework_TestCase
{
public function testDummy1()
{
$this->assertFalse(false);
$this->assertTrue(true); // このアサーションを追加
}
}
すると、先ほど watch
状態になっていたコンソールには、このような出力が出た。
% ./node_modules/.bin/grunt watch
Running "watch" task
Waiting...OK
>> File "tests/fugaTest.php" changed.
Running "phpunit:classes" (phpunit) task
Starting phpunit (target: classes) in tests/fugaTest.php
PHPUnit 3.7.29 by Sebastian Bergmann.
Configuration read from /Users/suzuki/work/grunt/phpunit.xml
.
Time: 16 ms, Memory: 2.50Mb
OK (1 test, 2 assertions)
Running "watch" task
Completed in 0.108s at Sun Feb 02 2014 16: 18 :07 GMT+0900 (JST) - Waiting...
ファイルの変更が検知され、
>> File "tests/fugaTest.php" changed.
そのファイルを対象に実行されている様子が見える。
Starting phpunit (target: classes) in tests/fugaTest.php
実際に使う場合のシナリオは、ひとつのコンソールで grunt watch
させた状態で置いておき、その隣でエディタを使ってテストケースを編集・保存、テストの結果を見つつ、更に修正という形になるかな。
今回の Grunt 関連のまとめ。
なお、Grunt のプラグインには、シェルコマンドを実行するgrunt-shell もあったので、こちらを使えば専用プラグインが用意されていない場合でも好きなことができそうだ。
これで Grunt の使い方の第一歩は把握できたので、仕事なりその他なりで活用していきたいと思う。
【追記】
「ファイルを監視してタスクを実行する」なら Guard や watchr でもいいんじゃないか?的なコメント(?)を貰った。
今回は「Grunt 勉強しなきゃ → おや、watch
なんてできるんだ → おや、phpunit
のプラグインもあるんだ」という流れがあったので、Grunt 以外の実現方法を調べようという考えには至らず、それらの存在に気がついていなかったという。。。
watchr とかの記事をググると、2011年ころの記事が多くヒットしたりして、自分の技術とか知識とかがいかに遅れているかという実感を得ちゃうねぇ。。。がんばろ。