過去のブログを掘り起こしたらこんなエントリを見つけました。
単体テスト(ユニットテスト/UT)で考えること - kntmr-blog
どうすれば、メンテし易く保守性の高いソースコードが書けるか、というようなことを考えていた頃のエントリだと思われます。所謂、『テスト駆動開発 (TDD)』と言えるレベルのものではないですが、改めてここで自分なりの方針を少し整理してみようかと。
前提
仕事では Java を使うことが多いため、Java/JUnit あたりのサンプルを記載していますが、これらの考え方は言語やフレームワークに依存するものではないと考えています。
背景
これまでに参画したプロジェクトにおいて、次のようなケースにたびたび遭遇した。
設計もそこそこに突貫で作るようなことが多く、結合試験のフェーズでモグラ叩きのようにデバッグしたり、デグレと闘いながら機能追加したりと、プロジェクトによってマチマチではあるが、かなり綱渡りな状況であったと思われる…。
モノによってはユニットテストコードはあるものの、1メソッド内にモック化や assert のコードが大量に書いてあったり、とても継続的にメンテできるようなものではない。そもそもテストとして機能していないことすらある。(丸々コメントアウトされてたり)
課題
早い段階でバグの芽を摘み、変更に強く、拡張に優しい開発を実現するにはどうすればよいか、開発中に抱える不安感をどうやって解消すればよいか。
そのような思いから、自分なりにきちんとユニットテストコードを書いて、品質が担保できていることをある程度自動化して保証することを目指してみようかと。
課題は、どうすればテストし易いコードになるか、ということ。
アプローチ
コードを書くときはテストし易い粒度でメソッドを分割する。
1つのメソッドの処理が長くなるとテストはやり辛くなる。テストし辛いコードはメンテすることが困難になる。メソッドの命名に悩んだり、メソッド名の文節が極端に多い場合、その処理には何かしらの複雑さが含まれている可能性があると考える。ただし、処理の目的を明確にするために細かく命名することは問題ない。
実装にコードを書き始めるときは、最初にスケルトンコードを書いて全体構成を整理する。メソッドのアクセス修飾子はデフォルト (パッケージプライベート) にして、プロダクションコードのクラスとテストクラスを同じパッケージに配置する。
TDD の世界では「テストから先に書く」ということを言われているが、そのあたりのアプローチがきちんとイメージできていない…。とりあえず、プロダクションコードを書いてからテストを書くようにしている。ただし、プロダクションコードに合わせてテストを書いていたら身も蓋もないので、テストコードを書くときは設計書なりをインプットにすること。
テストコードでは基本的にすべての分岐を通す。また、テストコードはテスト対象のクラス内で閉じるようにする。テスト対象クラスが他のクラスに依存する場合、そのクラスはモックにする。入力値として、境界値や空、および null のケースをテストする。
原則、1つのテストメソッドは、3フェーズ、もしくは4フェーズで構成する。(大抵は tear down を除いた3フェーズで事足りているが…)
- set up
- exercise
- verify
- tear down
モック化や assert が極端に多い場合には、プロダクションコードのリファクタリングを検討する。
修正や機能追加
修正や機能追加でプロダクションコードを変更する場合、以下のアプローチを取る。
- テストを実行してパスすることを確認する
(まず、この時点でテストが正しく動作することを確認する)
- テストコードを目的の仕様に合わせて変更する
- テストを実行して落ちることを確認する
(想定通りに落ちることを確認する)
- プロダクションコードを変更する
- テストを実行してパスすることを確認する
ここで気になるのは、テストコード自体が間違っていないか、ということ。人間なのでどうしてもミスをすることはある。(コピペミス然り…)
ミスを起こしにくくする、もしくはミスがあってもすぐにリカバリーできるように、テストコードやプロダクションコードをシンプルに保つ。テストし易いコードを書くように常に意識することが大事。
特長
このようなアプローチを取ると開発にテンポが生まれる。(ような気がする)
また、テストがパスしている間は大丈夫、という安心感を得られることは大きい。
その他
『学習テスト』という考え方があります。
gihyo.jp
テスティングフレームワークを使って、API やライブラリの使い方を調べるというアプローチです。例えば…
Boolean.valueOf("true");
Boolean.valueOf("TRUE")
Boolean.valueOf("");
Boolean.valueOf(null);
こういうときに簡単なテストコードを書いてみます。
@Test
public void test() {
assertTrue(Boolean.valueOf("true"));
assertTrue(Boolean.valueOf("TRUE"));
assertTrue(Boolean.valueOf("True"));
assertFalse(Boolean.valueOf("false"));
assertFalse(Boolean.valueOf("FALSE"));
assertFalse(Boolean.valueOf("False"));
assertFalse(Boolean.valueOf("hoge"));
assertFalse(Boolean.valueOf(""));
assertFalse(Boolean.valueOf(null));
}
昔は、検証用に Eclipse のプロジェクトを用意して、main
から叩いて標準出力とかで確認したりしてましたが、今は JUnit を使ってやっています。
上の記事にも書いてありますが、API やライブラリの使い方を調べる以外に、メソッド単位でロジックを切り出してテストコードから検証します。ある程度、検証できたものはそのままプロダクションのコードに移したりします。
Java 9 を使えば、API やライブラリの検証程度なら JShell で十分なのかもしれません。(たぶん)
まとめ
当然、これが正解ではないですが、自分でも何が正解なのか分からないんですね…。とりあえず、自分がやれることを考えてやってみた結果です。
ただ、もっといいやり方がないものか、とは思います。以上です。