前回に引き続き、残りの仕様を実装していきましょう。
4番目の仕様「商品コードを指定して、カートに入っている数量を取得できる」
次は4番目の仕様
です。ここでは、以下の3つのテストを考えてみました。
- Cartオブジェクトを作成した直後は0を返す
- 商品コード「001」を1個追加すると、getAmount('001')は1を返す
- カートに入っていない商品が指定された場合、数量として0を返す
テストを追加したテストケースは次のようになりました。
さて、それではgetAmountメソッドを実装していきます。もうお気づきの方も多いと思いますが、現時点で商品情報をCartクラス内に保持する実装をしていません。テスト内容も貯まってきていますので、そろそろ本格的に実装し始めても良い頃でしょう。
まず、1番目の仕様に対するテストを実装した際、Cartクラス内の商品情報は、キーに商品コード、値に数量を持つハッシュでとすることにしましたので、それを実装しましょう。また、addメソッドも併せて修正しておきます。修正したCartクラスは以下のようになりました。
テストを実行してみましょう。
今までのテストを含め、全てテストをパスしています。しかし、1つの商品コードに対してのテストしか行っていません。複数の商品の場合の不具合はないのでしょうか?それもテストしてみましょう。以下のテストケースは、1つのカートに複数の商品を追加・数量変更し、それぞれの個数が正しく返ってくるかどうかのテストを追加しています。
大丈夫そうです。これで4番目の仕様も実装完了とします。
5番目の仕様「すべての商品コードと数量をまとめて取得できる」
1番目の仕様を実装する際に追加したgetItemsメソッドですが、テストをまだ追加していませんでしたので、テストを追加しておきましょう。ここでは、以下の2つのテストを考えてみました。
- カートに複数の商品を追加した場合の商品種の数
- getItemsメソッドで取得したハッシュの内容のチェック
テストを追加したテストケースは次のようになりました。
おっと。getItemsメソッドは、1つ目の仕様を実装した際の仮実装のままでした。ここで、Cartクラスを修正して再度テストします。
6番目の仕様「クリアすると、カートは初期状態になる」
次は最後の仕様
です。仕様に対するテストはどの様になるでしょうか?とりあえず、
- clearメソッドを呼び出すと、コンストラクタを呼び出した場合と同様の動作をする
ということで良さそうです。早速テストケースにテストを追加し、Cartクラスにclearメソッドの定義を追加しましょう。
それでは、clearメソッドを実装していきます。clearメソッドの動作はコンストラクタと同様の動作をすれば良いので、コンストラクタのコード内容をそのままコピー&ペーストして実装することにします。
テストにパスしました。「これで実装完了」といきたいところですが、重複するコードは可能な限り避けたいものです。そこでコンストラクタのコードを変更し、clearメソッドを呼び出すことにしましょう。
テスト結果も問題ないようです。
不足している仕様とテストを追加する
さて、挙げられた仕様はすべて実装しましたが、ここで不足している仕様やテストがないかどうかを確認してみましょう。まず、カートに入っている商品の数量に制限がありません。数量が負数になることはないと考えられますので、下限は0としましょう。一方、数量の上限はシステムの要件で異なることが多いですので、ちょっと非現実的ですが、ここではPHP_INT_MAX(2147483647)としておきましょう。また、数量が上限・下限を越えた場合の処理はどうでしょうか?ここでは、上限を超えた場合はSPL拡張モジュールで定義されているOutOfRangeExceptionを投げ、下限を越えた場合はエラーは発生せず、数量が0になることにしましょう。まとめると、以下の3つを新しい仕様として追加します。
- カートに入っている商品の数量は0以上PHP_INT_MAX以下
- 数量の上限を超過した場合、OutOfRangeExceptionを投げる
- 数量の下限を超過した場合、数量は0になる
現在の仕様では「数量が0」である商品もカートの中に存在することができてしまいます。この場合、カートから商品を取り除いた方が良いと考えられますので、これも仕様として追加しておきましょう。これにより、上の3つ目の仕様は
- 数量が下限もしくは下限を超過した場合、その商品はカートから取り除かれる
となります。
それでは、新しく挙げた仕様を実装します。前者2つはaddメソッドのテストとして、3つ目はgetItemsメソッドのテストとして、それぞれテストケースに追加することにします。
テストが通るように、Cartクラスを実装していきます。
うまくいきました。
テストのコードカバー率を確認する
さて、Cartクラスも少しずつ大きなコードになってきていますが、すべてのコードが実行されているのでしょうか?ここで、実行されたコードのカバー率(コードカバレッジ)を確認してみましょう。PHPUnit3はPHP拡張モジュールであるXdebugと組み合わせることで、コードのカバー率を確認することができます。XdebugはPECLパッケージとして提供されていますので、インストールは以下の手順で行うことができます。
インストール後、正しくインストールされているかどうか確認します。
また、phpunitコマンドのヘルプを表示すると、追加オプションである「--coverage-xml」「--report」が使用可能になっていることが分かります。
Xdebugを正しくインストールできたら、早速コードカバー率の結果を出力させてみましょう。
実行後、カレントディレクトリ直下にreportディレクトリが作成され、HTMLファイルやCSSファイルが生成されていることを確認してください。また、index.htmlをブラウザで開くと次のように表示されていると思います。
また、「Cart.php」のリンクをクリックすると、行単位の結果が表示されます。
これらの結果を見ると、すべてのコードが実行されており、コードカバー率は充分であると判断できます。これでCartクラスもほぼ完成です。
Cartクラスとテストケースのリファクタリング
最後の修正でaddメソッドのネストが若干深くなってしまいましたので、ここでaddメソッドのリファクタリングをしておきましょう。
テストが自動化されていない場合、一般的に一度テストしたコードを変更するには「勇気」が必要となります。なぜなら、再度すべてのテストを行う必要があるからです。これには多くの時間と手間がかかります。また、品質の低下にも気を付ける必要があります。しかし、これまで様々なテストを作成し、自動的にテストを行えるようにしてきました。実行にはそれほどの時間も手間もかかりません。また、すべてのテストをパスする限り、品質の維持を保証することができます。このことは開発者にとって、非常に大きな助けになります。
前置きがちょっと長くなりましたが、以下がaddメソッド内のif文を組み替えたコードになります。
リファクタリング後、テストを実行して問題がないことを確認します。
ついでに、テストケースもリファクタリングしておきましょう。
これまで作成したすべてのテストでは、最初にCartクラスのインスタンス化を行っていますが、それぞれのテストを開始する前に、自動的にCartクラスのインスタンス化を行えると、スッキリしそうです。PHPUnitでは、こういう場面で使用できるsetUpメソッドが用意されています。また、setUpメソッドと対となり、テスト終了時に自動的にコールされるtearDownメソッドも用意されています。
メソッド | 意味 |
void setUp() | これをオーバーライドして、実行するテストに関連するオブジェクトの作成を行います。テストケース内で各テストが実行されるたびに、setUp() が毎回コールされます。 |
void tearDown() | これをオーバーライドして、実行するテストに関連する、もう必要なくなったオブジェクトの後始末を行います。テストケース内で各テストが実行されるたびに、setUp() が毎回コールされます。一般に、tearDown() で明示的に後始末する必要があるのは外部リソース (例えばファイルやソケットなど) だけです。 |
それでは、setUpメソッドを使ってテストケースを書き直してみます。
phpunitコマンドを実行して問題がないことを確認します。
これで完成です。
次回は、ダミーのオブジェクト(モックオブジェクト)を使ったテストについて解説・実践していきます。