テストの書き方

今やっている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してテストを実行してくれます。コードを変更した影響を自分が意図していない部分についても確認することができます。


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