ユニットテスト、ここどうするの?
こんにちは、斉藤です。
前々回はTDDというユニットテストを使った開発方法、前回はユニットテスト実行の自動化と、触れてきました。
今回もやっぱりユニットテストについてということで、方法論などをtips形式でお送りします。
* プライベートメソッドのテスト
ユニットテストでは、公開されているメソッドを呼び出して実行します。
クラスを書いたことがある方なら知っている通り、プライベートメソッドを外部から呼び出すことは出来ません。
そのため一般的にこれは、避けた方が良いと言われていますが・・・自分はテストしたい派なので、以下の方法をご紹介します。
- パブリックメソッドでプライベートメソッドを呼び出す
一番簡単な方法ですね。
ただしこの場合だと、公開APIと非公開APIの区別が出来なくなってしまいます。
クラスに手を入れることになり、テストのためだけのコードが混じってしまうことがデメリットです。
例;
<?php
class Hoge
{
public function __construct()
{
}
private function doSomething($a, $b)
{
return $a + $b;
}
// テストでは使うけど、実際には使わない
public function doSomething2($a, $b)
{
return $this->doSomething($a, $b);
}
}
余談ですが、objective-cでは"クラスに手をつけず、クラスの機能を拡張できる"カテゴリーという機能があります。
この機能を使えば、ユニットテストの時のみ、パブリックメソッドを追加する方法が取れます。
- リフレクションでプライベートメソッドを呼び出す
PHP、JAVAなどは、リフレクションという"プログラム自身を読み取る"機能が使えますので、それを使いましょう。
これをテストコードの中のみに記述しておけば、実際に使うときには使われないので安全です。
以下の例では、上記HogeクラスのdoSomethingメソッドをリフレクションで外部から実行しています。
<?php
class HogeTest extends PHPUnit_Framework_TestCase
{
public function testDoSomething()
{
$method = new ReflectionMethod(
'Hoge', 'doSomething'
);
$method->setAccessible(true);
$this->assertEquals(3, $method->invokeArgs(new Hoge(), array(1,2)));
}
}
参考:
http://sebastian-bergmann.de/archives/881-Testing-Your-Privates.html
http://blog.asial.co.jp/751
* void型メソッドのテスト
値を返さないメソッドをどうやってテストするの?って思いますよね?
割と方法が考えられています。
- プロパティが変化するなら、その値に対してassertをかける
そのメソッド内でプロパティの値が変化するなら、その内容でテストをすることが出来ます。
ただし、このプロパティがパブリックである、もしくはアクセスできるメソッドが公開されている場合に限ります。←コメントで、リフレクションを使えば良いということを教えていただきました。
<?php
class Foo
{
private $a;
public function __construct($a)
{
$this->a = $a;
}
public function doSomething()
{
$this->a += 5;
}
public function getA()
{
return $this->a;
}
}
class FooTest extends PHPUnit_Framework_TestCase
{
public function testDoSomething()
{
$foo = new Foo(5);
$foo->doSomething();
$this->assertEquals(10, $foo->getA());
}
}
- メソッド後にassertを置く
そのメソッドが呼び出されたことだけを確認するならば、テスト失敗時に実行が止まることを利用して、メソッドの後にテストを記述します。
実行結果は分からないですが、とりあえずエラー無く実行できた、ということを判断したい場 合にはこれでもテスト可能です。
exceptionを吐く可能性のあるメソッドに対して、有効ですね。
例:
<?php
class Bar
{
private $a;
public function __construct()
{
}
public function doSomething($a)
{
if ($a!=0) {
$this->a = 1/$a;
} else {
throw new Exception();
}
}
}
class BarTest extends PHPUnit_Framework_TestCase
{
public function testDoSomething()
{
$bar = new Bar();
$bar->doSomething(0);
$this->assertEquals(true, true);
}
}
- その内容を必要な人に伝えるようにする
この方法は目からウロコですね。
全てを自動化する、ということは出来ないという例でもあります。
メソッドの内容によっては、システムが自動的にリカバリーすることは難しいといったケースもあります。
そういう時はメールを飛ばすなりで、運用者が気づくようにする、ということが必要になります。
例えば、"ユーザーが使って買い物をする、WEBシステムで二重課金されてしまった"というパターンでは、運用者が手動でキャンセルをする、ということになります。
参考:
http://stackoverflow.com/questions/246038/unit-testing-void-methods
* テストを走らせることを忘れてしまう、面倒だと感じている
手動で繰り返すと、どうしても気を抜いたときに実行しなくなってしまいます。
JenkinsなどのCIツールや、watchmedoなどの監視ツールを使い、自動的に走らせるようにしましょう。
参考:
http://d.hatena.ne.jp/anatoo/20120402/1333377979
http://www.atmarkit.co.jp/fjava/rensai4/devtool21/devtool21_1.html
* そもそもユニットテストを何のために書くか
もちろんメソッドがきちんと動いていることを確認するためですが・・・自分は以下のように考えています。
- 仕様を反映したテストを書くことで、実装が正しいと認識できる
正常系、異常系を記述し、プログラマー自身が実装を判断するためにも、ユニットテストは有効です。
ただしこれについては、自分はこのようなことを言われています。
「いくらユニットテストを書いても、書いた本人が想像できないことはテストできないよ?」
結局ユニットテストといっても、書き手は実装するエンジニアになるので、その質はエンジニア力が求められます。
「ユニットテストが動いているから、実装は正しいんだ」ではなく、「仕様に沿った正常系と異常系をテストする」という考え方をしましょう。
- 自動で実行出来るので、コンピューター任せに出来る
前々回の記事でも書きましたが、shell scriptやCIツールなどを使ってテストを自動実行できます。
手動で実行する手間が省けるだけでも、ユニットテストを頑張って書く甲斐はあります。
- 実装を把握できる
コードの引き継ぎを想定しています。
テストが無ければ、自分が書いたときに正しい、と判断して書いていても他の人が引き継いだ際に、このメソッドが動いているのかどうかが分からない、なんていう事態も起こりうります。
コードの正当性を担保しているユニットテストがあれば、新しい開発者はこれに目を通すことで何が正しいのかをメソッドレベルで確認することが出来ます。
もちろん、引き継ぎ自体もお忘れなく。