インフィニットループ 技術ブログ

2014年03月24日 (月)

著者 : vol

初心者がテストコードを書くようになった経緯とオススメのテストフレームワーク

初めまして会社の隅っこで働いているvolと窓際で働いてるnagodonです。
今回は機会がありまして別のプロジェクトメンバー同士で技術ブログを書くことになりました。
テストコードを業務で使ったことがなかった二人が探りながらテストコードを書いたお話を、
前半をvol、後半をnagodonが行っていきたいと思います。
よろしくお願いします。


テストコードを書き始めた切っ掛け

私、volのプロジェクトでの切っ掛けは唐突でした。
ちょっと難しい機能を実装する事になり以下の内容で悩んでました。
1.時間足りない・・・
2.仕様が複雑で設計大変
3.分担難しいけど一人じゃ間に合わない・・・
でもやらなくちゃいけないんだよ!!
時間が無いながらも大きな機能から小さい機能を切り出して
一個一個丁寧に設計
設計が出来上がった所、
手動でテストをするにはあまりにも再現が難しいのでテストコードって言うものを書いてみよう!
そんな必然的な感じでスタートしました。
(ちなみにPHPUnitを採用しました。)


テストコードなんて書いてられっか!

今回のこの一件が起こる前までは、そんな事思っていました。
たしかに書いたら良い事ありそうだけど
でも・・・思うんだ・・・

  • 既存のコードがカオスで今から全部なんて到底ムリ!
  • 書く時間ないし、頂いた工数にテスト書く時間なんて含めれない
  • 費用対効果低そう
  • テストコード書くための設計難しいし

私は、こんな事を思って敬遠してましたが意外とそうでもありませんでした


既存のコードがカオスで今から全部なんて到底ムリ!

私もこれを全部やるのかーとすごく気が遠くなりましたが
そこは割りきりました。
動いているのだから
ただ仕様変更やバグが既存部分に出た際は、修正やリファクタリングのついでにテストコードを書くようにしています。


書く時間ないし、頂いた工数にテスト書く時間なんて含めれない

設計考えたりするだけで結構な時間を使いますよね。
ましてや仕様を洗い出さないうちにダラダラとコードを書いたら
コードが重複したり色んな理由でメンテが難しくなってきます。
私の場合はテストコードを書くことによってメンテ性が高いものをより生産しやすくなりました。
結果的には次説明する部分と少々被りますが

  • 実装が早くなる
  • バグが減る
  • テスト時間が減る
  • 追加仕様に瞬時に対応できる

などが体感できました。


費用対効果低そう

効果としてこんなものが得られました。
1.テストが通ってて安心感が得られる
2.テストケースを足すのも容易
3.手動で再現しづらい異常系テストが簡単にできる
4.テストコードをみれが設計がわかる
5.単体テストができる設計に必然的になるのでソースコードが綺麗になる


テストコード書くための設計難しいし

ここはきっと正解はないと思いますが我流をちょっと紹介したいと思います。
せっかくユニット単位で切り出してクラス設計しても、そのクラスの中でSQLを書くと依存関係が強くなり、
単体テストが行いにくくなります。
行う作業としてはビジネスロジックからDBへのアクセス分離しアクセスを行わないようにします。
そこで、私はRepositoryパターンの様なものを採用し実装を行ってみました。
まずDBへのアクセスがまとまった、RepositoryのInterfaceを作ります。
Interfaceを作る理由としてはテスト側で対象のRepositoryのモックを作りやすくするためです。

/**
 * ユーザーに関するRepositoryInterface
 **/
interface UserRepository
{
  public function find($id);     // 一件のユーザーを取得する
  public function add($object);  // 全てのユーザーを取得する
  public function remove($id);   // ユーザーの削除
  public function save($object); // ユーザー情報の更新
}

次に使用する側

class UserService
{
  private $repo; // リポジトリクラスのインスタンス
  function __construct(UserRepository $repo)
  {
    $this->repo = $repo;
  }
  public function add()
  {
    $rikka = new User();
    $rikka->name = 'たかなし りっか';
    return $this->repo->add($rikka);
  }
  public function remove()
  {
    $user = $this->repo->find(1);
    if ('一色 誠' === $user->name) {
      return $this->repo->remove($user->id);
    }
    return true;
  }
  public function save()
  {
    $user = $this->repo->find(1);
    if ('一色 誠' === $user->name) {
      $user->name = 'たかなし りっか';
      return $this->repo->save($user);
    }
    return true;
  }
}

このような実装パターン例だと、比較的簡単に単体テストのし易いコードがかけると思います。
コンストラクタに渡すものをテスト用モックRepositoryに変えることが簡単にできますし
なによりデータベースとの依存をなくせます。
ではnagodonにバトンタッチです!
ではここからは会社の隅っこに働いてるvolに変わって窓際で働いているnagodonのお話をさせて頂ければと思います。
また終わりの方で最近使い始めたテスティングフレームワークの紹介を少しばかりさせて頂きます。


テストコードを書き始めた切っ掛け

自分の書き始めたきっかけは移動したプロジェクトにすでにPHPUnitのテストコードが存在していたことでした。
この時に存在してなかったらテストコードを書くのはまだまだ先だったかもしれません。
テストコードを書いていた同僚に感謝!!!
またこの時、新しい機能の開発担当になったのでテストコード書いてみるチャンスだと思ったのが始まりです。
きっかけ大事!


「テストって、楽しいかも?」

実際にテストコード書いてみると最初は調べるばかりで書いていてもこのテストコードの書き方あってるのか?
このコードでちゃんとテストできるのか?とか疑問だらけでした。
書いてる時は割と苦痛を感じたのが本音です。
でもコードを書き終えてテストランナー走らせてGreenになった瞬間はよっしゃ!って感じになりましたね。
この喜びが自分にとって後々のテストコードを書くモチベーションへと繋がりました。
モチベーション大事!


テストからの恩恵

volが書いてますが、テストすることによって様々な恩恵を受けられますし、
コードへの不安をテストにする事でその不安を解消しコードへの自信もつけれます。
テストを書く=コストも当然かかりますがコストに見合う恩恵をテストは与えてくれます。
一度テストコードを書けば後にそのテストコードが対象とするクラスの仕様変更があった場合でもそのテストを使って影響を調べられるので、確認のコストを下げる恩恵も受けれます。
もちろんテストコードも保守していく必要があるので大変な場面もあります。
でもこの恩恵は裏切らずにずっと与えてくれるのでめげずに保守していきましょう。


テストを支えてくれるフレームワーク

テスト書く上でテスティングフレームワークの存在はかかせません。
そもそもなかったら全て自前で実装しないといけないわけですが、とてつもない労力が必要だと思います。
PHP界隈だとxUnit系のPHPUnitが一番有名ですね。
私のプロジェクトもPHPUnitを使ってます。
ただPHPUnitは単体ではできないこともあります。
例えばシナリオテストやDB周りのユニットテストはextensionを別途インストールしなければなりません。
そこで最近みつけたモダンなテスティングフレームワークを紹介したいと思います。
密かにPHPUnitから移行しようと考えています。


モダンテスティングフレームワーク Codeception

Codeceptionは単体テスト、機能テスト、受け入れテストを最初からサポートするフルスタックなテスティングフレームワークです。
読みやすい、書きやすい、デバッグが容易ということを意識されて作られてるのでとても使いやすいです。
またPHPUnitを継承して作られてるので移行もしやすいですし、PHPUnitのassertメソッドをそのまま使えます。
これだけでも移行する価値がありますね!
ひな形の作成も最初からサポートされてるのでさくっと作れます。
導入方法については今回は割愛させて頂きます。
参考リンク貼らせて頂くのでそちらを参考にして頂ければと思います。


Codeceptionのいいところ

なんといってもCodeceptionの良いところは受け入れテストや機能テスト、APIテストがとても読みやすい形でかけるところです。
弊社は主にサーバサイドの開発が多くAPIの確認をすることが日常茶飯事なので、読みやすい、テストが書きやすいというのは大変重要です。
今までは独自にブラウザから値を設定してcurlを使ってリクエストしてレスポンスを確認するということをしていたのですが、Codeceptionを使うことで複雑なリクエストも簡単に書けるようになりました。
こんな感じで素のPHPで書けます。

<?php
$I = new ApiGuy($scenario);
$I->wantTo('APIテスト');
/**
 * {"parameter":{"p1":"v1","p2":"v2"}}というjsonリクエストを受け取って受け取ったものをそのままJson文字列として出力するAPIの場合
 */
// ヘッダーの宣言
$I->haveHttpHeader('Content-Type', 'application/json');
// APIへPOST
$I->sendPOST('api/test', array('parameter' => array('p1' => 'v1', 'p2' => 'v2'));
// ステータスコードの確認
$I->seeResponseCodeIs(200);
// レスポンスボディのフォーマットの確認
$I->seeResponseIsJson();
// レスポンスボディのJsonの中身の確認
$I->seeResponseContains('"response":{"p1":"v1","p2":"v2"}');

またAPIのレスポンスの一部の値を取得して次のAPIのパラメータにするということも可能です

<?php
$I = new ApiGuy($scenario);
$I->wantTo('APIテスト');
$I->haveHttpHeader('Content-Type', 'application/json');
$I->sendGET('api/list');
$I->seeResponseCodeIs(200);
$I->seeResponseIsJson();
$I->seeResponseContains('"list"');
// grabXXX系のメソッドを使うと色々な値が取得できます
// grabDataFromJsonResponseメソッドはjsonレスポンスから引数の場所の値を取得できます
$id = (string) $I->grabDataFromJsonResponse('response.list.0.id');
$I->haveHttpHeader('Content-Type', 'application/json');
$I->sendPOST('api/edit', array('parameter' => array('id' => $id));
$I->seeResponseCodeIs(200);
$I->seeResponseIsJson();
$I->seeResponseContains('"response":{"p1":"v1"}');

素晴らしいですね!
このように自然言語に近い形で記述ができるのでとてもわかりやすいです。
まだ日本語の情報が少ないのが残念ですが、公式サイトにある参考コードみるだけでもわりとかけるので是非一度試してみてください。
かなりおすすめです!
参考リンク:
Codeception
Modern Testing in PHP なCodeceptionを触ってみた1
Codeceptionを触ってみた — tan-yuki@blog


おわりに

いかがでしたでしょうか?
この経験談をきっかけにテストする方が一人でも増えれば幸いです。
テストを始めるきっかけは色々あると思いますが、テストを行うことによって受ける恩恵は必ずあります。
テストを書いて不安を消し去って自分のコードに自信を持って良いソフトウェア開発をしていきましょう。

お知らせ

iPhone向けアプリ「勇者と1000の魔王」好評配信中です!!
昔懐かしい8ビット風RPGです。ぜひ遊んでみてください。

勇者と1000の魔王[ドットRPG]
カテゴリ:ゲーム>ロールプレイング
価格:無料(一部有料アイテムあり)

ブログ記事検索

このブログについて

このブログは、札幌市・仙台市の「株式会社インフィニットループ」が運営する技術ブログです。 お仕事で使えるITネタを社員たちが発信します!