PHPerだけどTDD本写経する(1)

TDD本買ってみたけどjavaのことはよくわからないPHPerなので、見よう見まねでPHPに置き換えてみる。
なお、写経と言いつつ微妙に手順が変わっている部分もあるがご容赦頂きたい。

## 下準備
Composerは省略。
### PHPUnitをインストールする
```
$ composer install phpunit/phpunit
```
`vendor/bin/phpunit`でテストが実行できる。

## 第一章
```tests/MoneyTest.php

<?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クラスが無いので当然のようにエラーになった。回避するために仮実装（ロジックの無い空のクラス）を作る。

```src/Dollar.php

<?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`がなくて怒られているので、追加する。

```Dollar.php

...

class Dollar {
    public $amount;
    public function times(int $multiplier) {
        
    }   
}

...

```

もう一度テストを回す。

```

$ vendor/bin/phpunit tests

...

1) Money\MoneyTest::testMultiplication
Failed asserting that null matches expected 10.

...

```

ようやくコンパイルエラー（とは言わない？）が消えた。
あとはテストをパスできるように、実装を進めていくだけだ。

テストを通すための最小限の実装を行う。マジで最小限なのでギョッとするがとにかくやってみる。

```src/Dollar.php

...

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`も使われていない（というかコンストラクタが無い）。まずはコンストラクタを実装。

```src/Dollar.php

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()`の中身を実装する。

``` src/Dollar.php

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`とハードコードしてしまったとしよう。それは、「意味のある」コードなのだろうか？
時間の制約で、「意味のない」コードをやむなく書いてしまうことは無いだろうか？
それを安全に修正することは出来るだろうか？

というような理解で第一章を終えました。続きはまた。