前回
No.2 ComposerでPHPUnitをインストールしてユニットテストを試してみた話〜PHPUnitインストール編〜
の続きです。
テストケース作成
http://www.elp.co.jp/staffblog/?p=5890
こちらのサイトを参考にしながら進めていきました。
まず、前回作成したテキトーなディレクトリ(自分はホームディレクトリからテキトーにdevelopというディレクトリを作りました)、その中にsrcとtestというディレクトリを作ります。
mkdir develop
cd develop
mkdir src
mkdir test
その後、srcディレクトリ内にテスト対象となるclassを作成します。
<?php
namespace Sample;
class Sample {
// $a + $bを返す
public function Add($a, $b) {
return $a + $b;
}
// $a - $bを返す
public function Sub($a, $b) {
return $a + $b;
}
// int型を返す
public function Int($c) {
return gettype($c));
}
}
?>
ここでは足し算、引き算の結果とInt型を返すだけの単純な関数を作り、それをテストしていきます。
引き算の返り値となっているのは、あえて間違いを返すためです。
テスト対象を作成したら、次にテストケースを作成していきます。
<?php
require_once('vendor/autoload.php');
class SampleTest extends PHPUnit\Framework\TestCase {
public function test_add() {
$sample = new Sample\Sample();
$this->assertEquals(10, $sample->Add(4, 6));
}
public function test_sub() {
$sample = new Sample\Sample();
$this->assertEquals(1, $sample->Sub(7, 6));
}
/**
*@test
*/
public function int() {
$sample = new Sample\Sample();
$this->assertEquals('integer', $sample->Int("aiueo"));
}
}
?>
このテストケースでは、Sample::Addメソッドと Sample::Subメソッド、Sample::Intメソッドが正しく意図したとおりに動作しているかをテストします。
テストケースとして認識させるためには、PHPUnit_Framework_TestCaseクラスを継承し、testから始まるpublicなメソッド、もしくはtestをつけたくない場合、@testアノテーションを付与します。
ユニットテスト実践
ターミナルで
vendor\bin\phpunit test\
を実行すると、testディレクトリ内のファイルをテストケースとしてテストを実行してくれます。
結果、
PHPUnit 5.7.22 by Sebastian Bergmann and contributors.
.FF 3 / 3 (100%)
Time: 34 ms, Memory: 3.25MB
There were 2 failures:
1) SampleTest::test_sub
Failed asserting that 13 matches expected 1.
/Users/kosuke.aizawa/develop/test/SampleTest.php:13
2) SampleTest::int
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'integer'
+'string'
/Users/kosuke.aizawa/develop/test/SampleTest.php:21
FAILURES!
Tests: 3, Assertions: 3, Failures: 2.
はい!
無事、2個の間違いを指摘してもらえました。
引き算の答えの違い、そして型の違い。
Failed asserting that 13、て部分は今のままだと13が返ってきてるよ、てことかな。
vendor\bin\phpunit test\
ここでコケる方はこのコマンドを実行しているカレントチレクトリを確認してみてください!
vendorディレクトリが配下にないディレクトリで実行するとエラーが出ます。
よく分からなかったらフルパスで入力するのが無難かもしれないですね!
pwd
でカレントディレクトリのフルパスが分かるので試してみてください!
エピローグ(なぜユニットテストを行うのか)
ここまでユニットテストをやってみての感想は少し回りくどくて面倒くさいな、という印象。
しかし、各言語でユニットテスト用のツールまで作られているということはきっと大きなメリットがあるはず!
調べてみた。
そもそもユニットテストはどこかのタイミングで一気に実施するものではなく、メソッドを作る前にテストを作り、テストを実施しながら1つのメソッドを作り上げていくのが理想とされていて、これをテスト駆動開発やTDD(Test Driven Development)というらしいです。
僕が以前社内でシステムを作った時は
仕様を決める→実装
という流れで進めていきましたが、テスト駆動開発では
仕様を決める→テストケースの作成→実装
というように先にテストケースが来るらしい。
先にテストケースを作る…???
ナニヲイッテイルンダ…??
上記参考にさせて頂いた例に則って説明すると、例えば会員データを取得するメソッドをテスト駆動で開発してみるとする。
仕様としては、至極単純で、数値のIDを渡したら連想配列で会員データを返してくれる、というもの。
まずはテキトーにクラスとメソッド名を決めちゃいます。
クラスをMemberTableクラス、メソッドをgetMemberメソッドとします。
$memberData = MemberTable::getMember(1);
$this->assertType('array', $memberData);
$memberDataが配列かどうかテストしています。
このままだとMemberTableクラスもgetMemberメソッドも実装していないのでうまくいくはずありません。
class MemberTable
{
static public function getMember($memberId)
{
return array();
}
}
一旦、空の配列でも返しておくか。
とりあえずテストは通るけど引数のIDの意味ないですね。
えいやっ!
memberData = MemberTable::getMember(1);
$this->assertType('array', $memberData);
$this->assertArrayHasKey('id', $memberData);
$this->assertEquals(1, $memberData['id']);
$memberDataの中にidというキーがあるか、またその値が1であるかをテストしています。
じゃあ実際にgetMemberメソッドを実装してみよう!
と、まぁこんな流れになるようです。
つまり、テストケース作成→コード実装→テストケース作成→コード実装の工程を少しずつ繰り返し、常に失敗するテストケースの作成を先行してコードを組み立てていくのがテスト駆動開発ということです。
いやいや、こんなんvar_dump($memberData)したら一発やんけ!
と思った方。
僕も数分前までそう思っていました。
確かにPHPのような軽量言語はコードを書きながらvar_dump()で出力したものをブラウザで確認した方が早いこともあります。
ですが僕は知ってしまったんです!
というか書いてありました笑
「テスト駆動開発は、ただ単にテストを先に作成することが目的ではありません。重要なのは、クラスやメソッドを設計するためのワークスペースとしてテストケースを使用することです。
どのようなコードを書くにしても、通常は、メモや頭の中である程度仕様を策定すると思いますが、テスト駆動開発ではその仕様策定の試行錯誤をテストケース上に書くのです。つまり、テスト駆動の過程ででき上がったテストケースは、その機能を正確かつ具体的に表現した仕様書そのものになるのです。」
なるほど!!
確かにここで配列が返ってきてidというキーには1という値が入って…という仕様を頭の中で組み立てていたものがスッキリした気がします。
またテストケースを作成することで、共通の処理が分かりやすくなってコードの冗長性も解消されそうな気がします!
社内の偉い人も「慣れて来たらテストから書く」と言っていました。
まだまだ浅い自分の考えを思い知りました。。
上司によるとまだまだユニットテストの捉え方が違うとのことなので随時更新していきます!
とりあえず備忘録的な感じで投稿!