オープンソースこねこね

Webプログラミングなどについてあれこれ。

テストがないとコードが書けない身体になってしまった。。。くやしいっ!

これまでテストコードなんてほとんど書いてこなかったのですが、この数日でいつのまにかテストを書くようになっていました。ずっと毛嫌いしていたのですが。今回はそんな過去と決別し、テスト環境を用意した過程やテストへの雑感などをつらつらと書き出したいと思います。なおテスト対象はPHPによるWebアプリケーションで、テストフレームワークPHPUnitを使ってます。

なぜテストを書いてこなかったのか?

その昔、Javaの業務システムのプログラマをやっていたとき、プロジェクトでJUnitを使って少しだけテストコードを書いたことがありました。 この時の体験があまり良いものではありませんでした。そのプロジェクトのテストでは以下のような不満がありました。

  • 一度書いたテストがメンテされておらず、古いテストケースのエラーが放置されていた。
  • DBにアクセスする重要なクラスのテストは手間がかかって皆書かなかった。
  • ユーティリティクラスのような、もともとバグが混入する可能性の少ない箇所だけテストがあった。
  • 簡単なテストだけが書かれていて、テストの有用性が体感できなかった。
  • 当時はCIのような、継続的にテストをプロジェクトとして回す仕組みもなかった。

このプロジェクトではテストする以前に、アプリのポータビリティやテストのしやすさなどが考慮されず、テストコードを書くための環境が整備されていませんでした。 このためテストの作成は、書くのが大変、有用性が実感できない、メンテできない、でも(仕事なので)書かなくてはいけない、というネガティブで苦痛な作業でしかありませんでした。

少しずつテスト環境を整備

今回はテストを書きつつ、地道にテストに適した環境を整えていきました。具体的には以下のような作業を行ないました。

  • ComposerでPHPUnitをインストール。
  • phpunit.xml.distにカバレッジレポートやテストから除外するファイル、初期化処理などの設定を記述。
  • テスト起動時にアプリのフレームワークをロードしコンテキストを初期化するスクリプトを作成。
  • テスト用DBの作成。
  • テスト用DBのスキーマ定義を本番のものと一元管理するためにMigrationツールを自作
  • テスト環境構築用にchefのcookbookを修正。
  • 機能テストのためにSeleniumをインストール。chefのcookbookを作成。

この段階では一つテストを書こうとしては詰まって、その都度環境設定をいじったり、ツールを書いたり、PHPUnitのドキュメント読んだりして、作業の支障になる環境要因を1つずつ取り除いて行きました。テストを書くという本来の作業から脇道にそれまくりで地味に大変でした。

テストを書く

環境が整っていくにつれてだんだんとテストが書きやすくなっていきました。 アプリのORMはPropelなので、DBテスト時のデータ初期化はPropelのfixtureを使いました。

Seleniumを使う

MVCのコントローラにロジックが直書きされている箇所はユニットテストが難しいので、Seleniumを使いました。 Seleniumサーバをインストールして起動、PHPUnitSelenium拡張を使ってテストケースを実行します。テストケースはfirefoxのアドオンであるSeleniumIDEでブラウザ操作を記録し、これをPHPUnitのコードに変換するプラグインでエクスポートして作成できました。

今後、コントローラの実装はSeleniumのテストケースでカバーされたら、ロジック部分をよりテスタビリティの高いプレーンなPHPクラスにリファクタリングしていこうかと考えています。

テストを書くと筋の悪い実装を認識できる

実際にテストを書いてみると、今まで気が付かなかったコードのよくない点が、テストが書きにくいという厳然たる事実によって、はっきりと分かるようになりました。

ここでのコードのよくない点というのは、なんらかの環境に強く依存しているコードで、たとえばサーバの現在日時とか特定のファイルパスとかです。これらがメソッド内部に埋め込まれていたりすると、非常にテストが書きづらい。このような環境依存情報をメソッドの引数とかコンストラクタとかでオブジェクト外部から渡せるように分離すればテストがぐっと書きやすくなります。

テストを書くと、プログラムを小さい単位で動作検証しながら実装できます。 そもそも今までも、開発中は動作確認のため、ちょくちょくコードを実行させながらコーディングしていました。PHPのWebアプリ開発だと動作確認はブラウザをリロードして目視することだったのですが、その目視をテストコードで自動化したわけです。 しかも、今までよりもっと小さな単位でプログラムを動作保証させながら開発していくことができる。

このテストを書くことによる「コンパクトに動作確認がとれているコードを積み重ねていく」というスタイルのコーディングが、体験してみるとすごくイイ感じなのでした。ひとつひとつの部品が綺麗に磨き上げられた上で、ものが作られていくような感覚です。

開発中のテストはよりよいコードを書くための「ツール」

私のなかでテストコードは、よいコードを書くためのツールという認識になりました。 ペンで綺麗な直線を引くのに定規を使うように、よいコードを記述するのにテストを書く。 ツールであるテストコードは「実装」と不可分な存在で、これは工数見積とかで見積書の上によくでてくる実装と分けられた「テスト」タスクとは明らかに違います。 その文脈での「テスト」は書かれたコードを対象にするの対して、テストコードは書いている最中のコードを対象にしています。

テストはシステムを保護する

開発中に書いたテストコードは、リグレッションテストとして再利用されます。 その昔、メソッドのインターフェースが変わるような修正をした場合、テストコードも書きなおさないといけないので、作業量が増える! 開発効率が落ちる! だからテストなんて書きたくない! と思ってました。

今、メソッドのインターフェースを変えたときにテストがエラーを出力することに対して、安心感さえ覚えます。それは関連してどこを修正しなければいけないかをテストが知らせてくれているわけで、システムが複雑化して自分の認識が届かないような箇所に対しても、まだすべきことがあるということを本番へのデプロイ前に教えてくれる。

そしてカバレッジをとることでコードがどれだけ手厚く保護されているのかもわかります。

というわけで

今日もテストかきかきしてますっ!

おしまい。