背景
現在私が参画しているプロジェクトは、プロダクトコードに対するテストコードがありません。
※正確に言うと、ものによってはあるけど全体に対する割合で言うと0.5~1割程度だったので、ほぼないと言える状態。
そのため、
- デッドコードが存在するプロダクトコード(品質)
- 責務が曖昧で長すぎるメソッド(品質)
- そもそもどこからも使われていないクラスの存在(品質)
- デグレ(品質)
- 保守対応(リファクタリング、業務影響のない軽微なバグの改修)の際のリグレッションテスト工数増大(コスト)
- テストコードを書かない文化
といったように様々な角度からの負がありました(挙げようと思えばもっとあるかも)。
目的
上記背景にある負を少しでも解消することを目的にテストコード導入推進を行います。
前提
- 言語:java
- テスティングFW:JUnit(色々検討した結果これに決定)
- CI:リリースブランチへマージのタイミングでテストが自動で走る仕組みは既にある
- 要員:4名~6名
- 期間:明確な定義なし、とりあえず2016/6~2016/12くらい
やったこと
以下の流れで検討を進めます。
1. スコープ精査
全てのコードに対してテストコードを書く必要はないので(テストコードが不要なクラスも必ずあるので)、
テストコードを書くもの、書かないものを決めます。とにもかくにもまずはこれです。
スコープを精査する時の考え方として、
- 「テストコードを書いてそれ相応の恩恵が受けられるかどうか(費用対効果的な観点)」
を軸として決めます。
※ちなみに、DTOのようなデータの入れ物、インターフェース、等のようにロジックが存在しないものはそもそも不要と判断。ただし、DTOでもsetter、getterがあるようなものは書く必要がありと判断(このあたりはプロジェクトによって判断に違いは出てくると思います)。
2. 優先度決め
いきなり全てのコードに対してテストコードを書くことは出来ないので、どこから書いていくか優先度を決めます。
優先度を決める際の判断軸として、
- 「利用頻度の高いもの(複数のクラスから参照されているようなもの)から順に」
- 「改修頻度の高いものから順に」
という2軸で判断して優先度を決めました。その結果、共通ライブラリ系のコードから着手していく流れになりました。
3. 見積もり
どうやって見積もんの?って感じですが、着手するにあたり偉い人たちに規模感の報告が必要っぽかったので見積もりをします。
見積もりロジックとしては、
1ケース書くのにかかる時間 × 総ケース数
になりますが、当然のことながらどちらも精緻なものは出せません。
※1ケース書くのにかかる時間なんてものによる、総ケース数を算出するなんてそもそも、、、
プロダクトの規模にもよりますが、ほとんどの場合見積もりは無理です。出せたとしても確度はかなり低いものになります。それでも、見積もりが必要な状況だったので以下の算出方法でふんわり出しました。
必要な数字 | 算出方法 |
---|---|
1ケース書くのにかかる時間 | 複雑度・難易度が低・中・高のクラスのテストコードを作成し、それらの平均時間を1ケースを作成するのにかかる時間とする。ゴールはC0、C1カバレッジ100%とする。 |
総ケース数 | Eclipse Metrics Plugin (Team in a Box)を利用して算出されたメソッドのサイクロマチック数(想定されるケース数)を総ケース数とする。 |
※サイクロマチック数について
if文やwhile文などを基にしたソースコードのロジックの複雑さを計測します。
ソースコードの経路数を表すマッケーブのサイクロマチック数や、ネストの深さなどがあります。
複雑度を低くすることで、保守性や信頼性が向上します。
※http://www.atmarkit.co.jp/ait/articles/0606/10/news016.html より引用
※Eclipse Metrics Plugin (Team in a Box)について
http://osdn.jp/projects/sfnet_eclipse-metrics/downloads/Eclipse%203.0.0/2.7.0.zip/com.teaminabox.eclipse.metrics_2.7.0.zip
からDLしたcom.teaminabox.eclipse.metrics_2.7.0.zipを利用。
ちなみに、上記方法で算出したら102.6人月という壊滅的な数字になりました(そもそもプロダクト自体が大きいので、、)。
4. マイルストーン作成
要員と規模感がわかったのでマイルストーンをふんわり作成します。
5. ユニットテスト導入ガイドラインの策定
ここでは、以下の3ステップを検討します。
1. ユニットテスト作成方針
テストコードを作成するうえでの基本方針を決めます。内容としては以下のようなものです。
テストコードの作成基準
そのクラスで定義されているメソッド全てに対してテストコードを書く必要はないので、どういったものに対してテストコードを書くのかを明確化します。
※大体の場合、ここでprivateメソッドはどするか問題が出てきます。私のチームでは基本的にprivateメソッドに対するテストコードは不要としています。
テストコード作成完了の定義
大体の場合、ここでカバレッジはどうするか問題が出てきます。私のチームではC0、C1ともに100%と定義しました。ただ、テストの難易度や工数、その他正当な理由があってテストが出来ないコードは存在するので、あくまで目安としておいているだけです。ここでカバレッジだけを追い求めては目的が変わってきてしまうので注意した方がいいです。
2. テストコードコーディング規約
テストコードを作成するうえで(テストコードの)基本的なコーディング規約を決めます。
3. 開発ルール
テストコード作成着手~プルリクレビュー~リリースまでのフローを軽く決めます。
- ブランチ運用
- プルリクの粒度(細かいサイクルでプルリク出すかまとめて出すか等)
- レビュールール
- その他もろもろ
6. テストコード量産
実際にテストコードを書き始めます。
思ったこと
「テストのしやすいコード」、とはよく言ったものでホントその通りなんだなと改めて感じます。
例えばテストコードのことを考えずコードを書いてしまうと、
- 1つのメソッドで何でもかんでもやろうとしてしまいメソッドの責務が曖昧になりがち
- 無駄にネストが深いロジックになりがち
- モジュール間で密結合になりがち
- その他もろもろ負
なんてことが往々にして発生します(現にテストコードを書いていて見受けられました)。
なので、TDDのようにテストファーストでやれとは言わないまでも、普段からテストのしやすさを意識するのは非常に重要なんだと再認識することができます。
今後の課題
テストコード導入前に方針決めたりルール決めたり等の準備をしたとしても、実際に書き始めると小さな課題がチラホラ出てきます。ただ、少しずつブラッシュアップしてけいばいいだけの話なので、あまり問題にしていません。
※テストコードのみならず、何かやろうとして万全の準備をしても、ほとんどの場合大小様々な課題にぶち当たります。
今後の課題は、そういった小さな課題を迅速に解決しながら、いかにして
- テストコードの重要性を理解してもらい
- テストコードを書くのが当たり前な文化に
していくことなんだと思います。