テストの書き方

今やっているRailsのプロジェクトでは、メンバーが皆RailsというかRubyは初めてで、テストもあまり書いたことがないということだったので、テストを書くのが後回しになってしまった。


TDD(Test Driven Development)大好きな僕としては残念な限りだが、テストを書くにはその対象についてある程度知らなければならず、そのためにはテスト対象のコードを書いてみる必要がある。納期の短いプロジェクトではテスト対象を知るためのお勉強の時間なんて取れるわけないので、リーダーの判断は極めて妥当だと思います。自動テストなんてやらないっていうプロジェクトも結構ある気もするし。

単体テスト

まずtest/unitsの単体テストについて考えてみます。通常app/modelsにあるモデルをテストするためのものだけど、別にlibの下にあろうが、pluginで提供されるクラスや機能をテストしようが構いません。
単体テストプログラマが自分のコードに自信を持つために書くものだから。自分のコードの中で(チームの内外問わず)他の人の書いた部分の使い方が不安だったら、コードを書いてみるべきです。まあ、チーム内なら話を聞いた方が早いですよね。


で、今度は聞かれる方の立場になってみましょう。どういう風に使ったらいいの?と聞かれたとき、カッチリしたドキュメントを見せるよりも、具体的なコードを見せて説明した方が早い場合が多いです。単体テストは「こんな風に使われることを想定してます」ということを実際に動くコードで表現できるわけです。

テストファースト

さて「こんな風に使われること」って何でしょうか?操作方法とその操作の出力、つまりは(Javaとかのinterfaceよりも広い意味での)インタフェースですね。TDDじゃない開発だと、まずテスト対象を実装してからテストを書くわけですが、インタフェースを決めてモックオブジェクトを利用して開発を進める、ということもあります。

その場合、実装がテストに間に合わずモックオブジェクトを使ったままテストを進めるということもありえますよね?あとで本物のオブジェクトが出来上がったときにモックオブジェクトを置き換えて再度同じテストすればOKなはずです。つまりインタフェースが決まっていればテストを先に用意して実装を後回しにする、ということも可能なわけです。


なぜこれが可能なのか、ちょっと考えてみましょう。
非TDDの場合に実装と呼んでいた作業は、実は2つの作業を行っています。「インタフェースの決定」と「メソッド等の実装」です。またテストも「テストの記述」と「テストの実行」に分けることができます。
これらを順番付ける明らかな条件は、

  • インタフェースの決定 -> メソッドなどの実装
  • テストの記述 -> テストの実行

ですね。更に考えるとインタフェースの決定がされてなければ、テストの記述を行うことはできないので、

  • インタフェースの決定 -> テストの記述

という条件も必要です。テストの実行は、メソッドの実装がされていなくても失敗するだけで実行することはできます。というわけで、メソッドの実装をできるだけ後回しにしようとすると、

  • インタフェースの決定 -> テストの記述 -> 繰り返し(テストの実行 -> 実装)

とすることも可能です。


Javaとかだとインタフェースの決定はテスト対象のクラスやメソッドを作成することを意味しますが、Rubyの場合、テストに書くだけでOKです。そのままでもテストを実行でき、クラスやメソッドがないよとエラーになるだけです。この違いは結構大きいんじゃないかと個人的には考えています。


まとめますと、TDDと非TDDだと具体的な作業として以下の違いがあります。
非TDD: インタフェースの決定 -> メソッドなどの実装 -> テストの記述 -> 繰り返し(テストの実行 -> 修正)
TDD: インタフェースの決定 -> テストの記述 -> 繰り返し(テストの実行 -> メソッドなどの実装)

TDDの嬉しいところ

作業の順番を変えると何が嬉しいのか?僕が一番に思うのは、余計な事を実装しないようになる、ということです。シンプルな実装に集中できるようになる、と言ってもいいかもしれません。テストを先に書くと、実装という作業の終了条件が明確になります。これは結構精神的なサポートとしては大きいです。いつも自分の作業が明確に把握するべきかもしれませんが、僕は疲れてたりすると「やめ時」が分からなくなって余計なコードを書いていることが結構あります。自分の作業を具体的に管理する上で重要だと思います。


2番目に思いついたのは、自信がもてるようになることです。
期待されている責務を果たしているかどうかをテストに書いておき、テスト対象がそれをパスすることを実行して確かめることで、自分の理解がちゃんとテスト対象に反映されていることを確認できます。これは、腕に自信のあるプログラマなら常に持っていることかもしれませんが、1週間、1ヶ月、1年と時間が経ってしまうと、僕なんかは全然自信がなくなってしまいます。自信がなくなったときに、テストを実行することで改めて自信を感じることができます。

ただし、ここで注意しなければならないのは、テストが保障するのはテストを書いた人の意図だけで、それが仕様と必ず合っているかどうかは別の話です。もし仕様を間違って理解している場合に、どう間違っているのかを確認する材料としてテストの記述は非常に有効です。


3番目は、自動で実行できる、といことでしょうか。
ZenTestなどを使えばコードやテストに変更があったときに自動でテストを実行してくれますし、CI(Continuous Integration)ツールを使えば、SVNリポジトリなどから自動でコードをUPDATEしてテストを実行してくれます。コードを変更した影響を自分が意図していない部分についても確認することができます。


他にも色々ありそうですが、今は思いつかないっす・・・

Railsでのテスト

RailsでのテストはJavaの素のJUnitでのテストとは違う部分がちょっとあります。
大きな違いはフィクスチャです。素のJUnitではもちろんDBに接続することなんて考慮されてませんが、Railsのテストはdatabase.ymlに記述されているDBに接続します。ですので、DBへの接続をテスト時にどうするかとか、面倒なことを気にしなくてOKです。

フィクスチャはテストの各メソッドの実行前にテーブルを一旦削除してからINSERTされるデータを記述します。フィクスチャの形式としては、YAMLCSVが使用可能です。動的なデータなデータ(例えばその日の日付とか)は、ERbと同じ形式で <%= %>という風に記述することができます。


Railsで用意されているテストには、unit、functional、integrationの3種類あります。

unit

基本的にモデルをテストするものですが、モデル以外のクラスをテストに使っても構いません。一番簡単なテストなので、できるだけここでテストできるようにコードを書いておくと便利です。つまりコントローラやビューではなく、モデルで実装できる部分はできるだけモデルに書いておくと、テストを書くのが少し楽になります。

functional

コントローラのテストを行うものですが、画面遷移についてはテストできないので、僕はあまり書きません。integrationの方が便利だと思います。

integration

HTTPリクエストに相当するコードをgetやpostメソッドを使って画面遷移やレスポンスをDOMオブジェクトとしてテストできます。便利なのは、外部からのアクセスとしてテストするのが普通ですが、実はモデルのコードを直接実行できるため、結構融通の利くテストを書くことができます。


というわけでブラウザ依存の部分(JavaScriptとか)はテストできないのでAjaxバリバリなアプリはテストが難しくなるかもしれませんが、その場合はfunctionalのテストをAjaxリクエストごとに書くと良いと思います。

書き方

テストのメソッド名はxUnit系のと一緒で"test"で始まるようにしてください。
使うメソッドは、unitなら基本的にassert_equal を覚えておけばOKです。他のメソッドもそんなに難しくないので、舞波本とかを参考に使ってみるといいでしょう。functionalやintegrationなら assert_response, assert_redirect, assert_template, assert_tag を使います。それ以外にも便利なメソッドもありますが、まあボチボチ使えばいいでしょう。

便利なツール

ZenTestのautotestが激しく便利です。インストールや使い方も簡単なので、是非使ってみてください。
http://rails.office.drecom.jp/takiuchi/archive/96

関連するトピック

xUnit系のテストの書き方だとあまり仕様書っぽくなりませんが、RSpecなどのBDD(Behavior Driven Development)ツールを使うと、より(英語として)自然な文章でコードを書くことができます。開発がどんどん進んでいる最中みたいなので、チェックしてみると面白いかもしれません。
http://rspec.rubyforge.org/
http://kakutani.com/trans/rspec/TUTORIAL_ja.html