テスト・コードと行き当たりバッタリ設計の実例(その1)
はじめに
統合開発環境(本記事ではEclipseを想定する)を使ったJavaでのテスト・コードについて書いてみたい。
テスト・コードというと、テスト駆動開発という言葉を思い出し、まずはテストを先に書いて、次に実装を書くものと考えている人も多いかと思う。
またテストという言葉から、品質を上げるために書くものと考えている人もいるかもしれない。
テスト・コードというのは、テストだけではない他の価値もあると、私は思っている。テスト・コードを書いている人は、実際にそう思っている人も多いだろう。
よって、テスト・コードというコードという言葉の前にテストという単語を置いただけのネーミングで、その実態をすべて表しているかは疑問である。しかし、本記事ではいい名前も思いつかないので世間に合わせて、そのコードをテスト・コードと呼ぶことにする。
テスト・コードを書いたことがあまりない人は、教条的にテスト・ファーストを守ろうとしたり、とにかく全テスト・パターンを網羅しようと意識しすぎたりして、億劫になっているかもしれない。でも、そんなふうに肩肘はらずに、もっとリラックスしてテスト・コードとつきあっていけばいいのではないだろうか?
「テスト・ケースも漏れたら漏れたで仕方ない。書かないよりはマシ」くらいの気持ちで、慣れないうちは書き始めればいいと思う。完璧主義者にはつらい心持ちだろうが、とにかく書きつづけて慣れていかないと、その良さは実感できないだろう。コストを払ってまでテストを書いたほうが良いかと聞かれれば、私の個人的見解ではコストを払ってでも良いと答える。
ただし、品質をあげるためだけではない。場合によっては、あまり品質向上に付与しないテスト・コードもあると思う。その場合でも、テスト・コードを書くべきだ。工数がないなら、ないなりに書けば良い。たとえば、調査で分かったことは調査結果としての記録コード(つまり、テストとしての意味は薄く備忘録的だが、一般的にはテスト・コードとよばれるもの)にしておけばいいと思う。自分の場合のテスト・コード(特にJava on Eclipse)に対しては以下の効用があると思っている。
- 条件もれに気づきやすい。nullチェックや、境界値など、メソッドの引数に対する考慮漏れに気づくことが多い。
- 呼ぶものと呼ばれるものの分離が自然と行われる。よって、コンポーネント化が自然と行われる。疎結合になる傾向が強くなる。
- プログラムを起動するエントリー・ポイントとして活用する(通常は、static void mainメソッドがそれに相当すあたるが、EclipseプラグインのJUnitを使えばmainメソッド以外からでもプログラムが起動可能になる。その他のIDEも同等の機能を持つかもしれないが私はEclipseしか使わないので、よく知らない)
- 最初にがんばって設計し、一発で実装を決めていかなくても、そこそこの良い実装になる。昔の設計→実装というよりは、設計と実装を何度も行ったりきたりする往復しつつ、コードが洗練されていく。
自分が、どのようにテスト・コードとつきあっているのかという実例を、これからJava言語で書いたプログラムを例にとって、プログ記事として書いて行こうと思う。もちろん、最初からきっちりと設計せずに、だらだらと書いていくのにコードが徐々に洗練されいく様子も書ければいいなとは思うが、はたしてうまくいくか。ちなみに、この記事連載ではユニット・テストのテスト・コードの話に限定する予定である。
これから作るコードの動機となるコード
とりあえず、下記のようなコードを対象としてみよう。
package hoge; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class Sample { static class SomeBean { String str; Integer i; public SomeBean(String str, Integer i) { this.str = str; this.i = i; } public String getStr() { return str; } public void setStr(String str) { this.str = str; } public Integer getI() { return i; } public void setI(Integer i) { this.i = i; } } public static void main(String[] args) { List<SomeBean> l = new ArrayList<SomeBean>(); l.add(new SomeBean("hoge1", 77)); l.add(new SomeBean("hoge2", 78)); l.add(new SomeBean("hoge3", 79)); System.out.println(l); } }
を実行すると
[hoge.Sample$SomeBean@1a758cb, hoge.Sample$SomeBean@1b67f74, hoge.Sample$SomeBean@69b332]
と表示される。見てもらえば分かるとおり、リストの中は、SomeBeanオブジェクトのハッシュ値が表示されていて、SomeBeanのプロパティの中身("hoge1"とか77とか)が分からない。EclipseなどのIDEを使えばデバッガが利用できるのでSomeBeanの中身は、すぐに分かる。
しかし、いつでもデバッガが使えるとは限らない。また、データが大量(上記コードの例で言えば、リストの要素数、つまりSomeBeanのインスタンスが大量に100個とかになってくるとデバッガでインタラクティブに見ていくには、そうとうしんどい。では、どうすべきか?
究極的には、printで出力するのが王道である。簡単に言えば、ログとか標準出力に、SomeBeanのプロパティを出力すれば良い。
単純にSomeBeanにプロパティ値を表示するように、toString()メソッドをオーバライドすればことは解決する…
のか?
この方法だと、クラスごとにtoStringをオーバーライドにしていかなければいけなくなる。さらに、Beanのプロパティに別のBeanがあって、そのBeanのプロパティにさらに違うBeanがあって、みたいなBeanが数珠つながりでカスケードしているような構造の場合は、どうやって出力すれば良いのか?悩みは尽きない。
ここまでの成果
githubにソースコードが進化していく過程を記録しておこうと思う。とりあえず、ここまでの成果は、以下の通り。
https://github.com/foobardam/SpyPrinter/tree/cbb5fd00999b061a6fb05115f0bd4ddb82450784
Eclipseのプロジェクトが入っている。
次回は
この悩みを解決するには、汎用的なprintメソッドを作ればいいのである。さて、次回は、このネタを題材に、汎用printメソッドを作っていこうと思う。もちろん、自分がどのようにテスト・コードを作っていくのかというのも書いてみたい。