TDD本買ってみたけどjavaのことはよくわからないPHPerなので、見よう見まねでPHPに置き換えてみる。
なお、写経と言いつつ微妙に手順が変わっている部分もあるがご容赦頂きたい。
下準備
Composerは省略。
PHPUnitをインストールする
$ composer install phpunit/phpunit
vendor/bin/phpunit
でテストが実行できる。
第一章
<?php
namespace Money;
use PHPUnit\Framework\TestCase;
class MoneyTest extends TestCase {
/**
* @test
*/
public function testMultiplication() {
$five = new Dollar(5);
$five->times(2);
$this->assertEquals(10, $five->amount);
}
}
package
記述はPHPに無い(はず)ので、namespace
で置き換えてみた。
この状態でユニットテストを回してみる。
$ vendor/bin/phpunit tests
PHPUnit 6.4.3 by Sebastian Bergmann and contributors.
E 1 / 1 (100%)
Time: 27 ms, Memory: 4.00MB
There was 1 error:
1) Money\MoneyTest::testMultiplication
Error: Class 'Money\Dollar' not found
/home/ubuntu/workspace/learn-tdd/tests/MoneyTest.php:14
ERRORS!
Dollarクラスが無いので当然のようにエラーになった。回避するために仮実装(ロジックの無い空のクラス)を作る。
<?php
namespace Money;
class Dollar {
}
同時に、tests/MoneyTest.php
でsrc/Dollar.php
をrequire
しておく。
tests/MoneyTest.php
require_once(dirname(__FILE__)."/../src/Dollar.php");
この状態でユニットテストを回してみる。
$ vendor/bin/phpunit tests
...
There was 1 error:
1) Money\MoneyTest::testMultiplication
Error: Call to undefined method Money\Dollar::times()
...
エラー内容が変わった。今度はDollar::times()
メソッドが無いと怒られている。追加する。
...
class Dollar {
public function times(int $multiplier) {
}
}
...
もう一度テストを回す。
$ vendor/bin/phpunit tests
...
There was 1 error:
1) Money\MoneyTest::testMultiplication
Undefined property: Money\Dollar::$amount
/home/ubuntu/workspace/learn-tdd/tests/MoneyTest.php:16
...
Money\Dollar::$amount
がなくて怒られているので、追加する。
...
class Dollar {
public $amount;
public function times(int $multiplier) {
}
}
...
もう一度テストを回す。
$ vendor/bin/phpunit tests
...
1) Money\MoneyTest::testMultiplication
Failed asserting that null matches expected 10.
...
ようやくコンパイルエラー(とは言わない?)が消えた。
あとはテストをパスできるように、実装を進めていくだけだ。
テストを通すための最小限の実装を行う。マジで最小限なのでギョッとするがとにかくやってみる。
...
class Dollar {
public $amount = 10;
public function times(int $multiplier) {
}
}
ユニットテストを実行。通った。
$ vendor/bin/phpunit tests
PHPUnit 6.4.3 by Sebastian Bergmann and contributors.
. 1 / 1 (100%)
Time: 101 ms, Memory: 4.00MB
OK (1 test, 1 assertion)
もちろん、これで良い訳がない。コンストラクタに渡した5
も、times()
メソッドに渡した2
も使われていない(というかコンストラクタが無い)。まずはコンストラクタを実装。
class Dollar {
public $amount;
public function __construct($amount) {
$this->amount = $amount;
}
public function times(int $multiplier) {
}
}
テストを実行。エラーになってしまった。
1) Money\MoneyTest::testMultiplication
Failed asserting that 5 matches expected 10.
/home/ubuntu/workspace/learn-tdd/tests/MoneyTest.php:16
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
テストでは10
を期待しているけど、5
が渡されたということらしい。times()
メソッドに渡された2
が使われていないようなので、times()
の中身を実装する。
class Dollar {
public $amount;
public function __construct($amount) {
$this->amount = $amount;
}
public function times(int $multiplier) {
$this->amount = $this->amount * $multiplier;
}
}
ユニットテスト実行。通った。
$ vendor/bin/phpunit tests
PHPUnit 6.4.3 by Sebastian Bergmann and contributors.
. 1 / 1 (100%)
Time: 20 ms, Memory: 4.00MB
OK (1 test, 1 assertion)
ふりかえり
最初にテストが通った段階では、テストコードと実プログラムとでどちらも10
という結果を書いているので、10 = 10
という当然のテストをしている。「これ、なんの意味があるの?」とつい考えてしまう。
問題は、テストと実コードとで10
という数字が重複して現れていることなのだ。
もし実際のプロジェクトで、顧客から「ここには(2 * 5の結果として)10
と表示してください」と言われたとき、10
とハードコードしてしまったとしよう。それは、「意味のある」コードなのだろうか?
時間の制約で、「意味のない」コードをやむなく書いてしまうことは無いだろうか?
それを安全に修正することは出来るだろうか?
というような理解で第一章を終えました。続きはまた。