エンジニア的なネタを毎週書くブログ

東京でWebサービスの開発をしています 【英語版やってみました】http://taichiw-e.hatenablog.com/

JUnit実践入門 読書レポート Part1 & 第7章

ほぼ発売と同時に買ったにもかかわらず、ずっと積読本になっていた「JUnit実践入門」をようやく読んでいます。

ここ最近で、バグによるリリーストラブルがあったり、単体レベルのバグがQAでボロボロ出てきたりということがあったため、改めてテストコードの品質(テストとしての品質はもちろん、可読性やメンテナンス性も含めて)について考えていたのですが…
 
もやもやしてたこと、ほとんど書いてあるじゃん!
 
ぐぅ、もっと早く読んでいればよかった。
 

レポートの前に、現状の問題

自分の書いたものも含めてなのですが、いまの身の回りにあるテストコードの多くにおいて、可読性が低く、何をしているテストコードなのかわかりにくい という問題が見られます。
(そうでないものも多くあります)
自分なりに原因分析をすると、以下のような原因が見られます。
  • テストメソッド名に意味がなく、コメントも無いので、何をするテストなのかよくわからない(最近は結構改善されてきた)
  • テストメソッドが長く、どれが前処理で、どれが実行で、どれが検証なのか、一目でわかりにくいものがある
  • プロダクトの特性上、メソッドの入力値や期待値が巨大なオブジェクトとなることが多く、別のファイルで定義されたjsonXMLを読み込んでオブジェクトを用意している。しかしそのため…
  • テストの内容を把握する際に2ファイル開く必要があり、レビューの時に面倒
  • 同じようなJsonファイルが散乱し、スキーマ変更時などに、大量のJsonを直すハメになる
  • ファイルのIOが多く発生するので(特にローカルで動かす時に)速度のボトルネックになってる模様
  • テストメソッドが長く、どれが前処理で、どれが実行で、どれが検証なのか、一目でわかりにくいものがある
  • カバレッジはとっているが、入力値、期待値の網羅性がわかりにくい
 
で、改善したいんだけどどうするがいいのかなぁ と思っていたところ、「JUnit実践入門」のPart1(第1章〜第3章)と第7章を読んだ時点でかなりの収穫がありましたので、そちらを中心にレポートを書かせていただきます。
 

Setup(前処理)、Execute(実行)、Verify(検証)、TearDown(後処理)を明確にする

第2章の冒頭に、以下のようなことが書かれています。
ソフトウェア開発によるテストの定義は「【ある条件下】において【ソフトウェアの振る舞いを記録】し、その記録が【期待される結果となっていることを検証】するプロセス」。
なので、JUnitによる自動ユニットテストも、この3点、
  1. 「ある条件」の定義
  2. テスト対象のプログラムを動かして「振る舞いを記録」
  3. 振る舞いと期待値を比較して「検証」
  4. (必要なら)後処理。
からなっています。
これがテストコード上できれいに表現できていないと、可読性の低いテストコードになってしまう可能性があります。
わかりやすい方法として、第3章でやっているように、Setup, Execute, Verify というコマンドを各テストメソッドに書く方法があるそうです。これはいっそテストコードのコーディングルールにしてしまうのもありだと思いました。
 

適切な方法でテストフィクスチャをセットアップする

そもそもテストメソッドの行数が多くなければ、前述のようにコメントを入れたりする必要もないのですが、中には10行を超えるようなテストメソッドもあります。
条件の定義、実行、検証しかしていないのになぜこんなに長くなるかというと、メソッドの入力値や期待値に使われるオブジェクトを作るのに手間がかかっているのです。
 
この問題に対するまさに「回答」が「第7章 テストフィクスチャ」に書かれています。
テストフィクスチャ(Test Fixtures)とは、入力するデータ、テスト実行のための予備操作、データベースなどの外部リソース、検証に必要なデータなど、テストに必要なデータやオブジェクトの状態のことを言うそうです。
ユニットテストの事前準備フェーズでは、これらのフィクスチャをセットする必要がありますが、これがなかなか大変で、テストコードが長くなってしまう傾向にあるようです。
第7章では、用意するテストフィクスチャの複雑さや出現の仕方によって、複数のセットアップ方法を紹介しています。
 

インラインセットアップ

テストメソッド内でSetterなどを使ってオブジェクトを用意する方法です。セットアップの量が少ないのであれば、テストメソッドの可読性が良くなるメリットがあります。(上から順に読めばいいので)
一方、前述したとおり、この行数が増えると、準備・実行・検証の区別がつきにくくなり、一気に読みにくくなります。
 

暗黙的セットアップ

Beforeアノテーションを使ってセットアップを共通化する方法です。
この方法によりテストメソッドはテストの実行と検証が中心となるため、何をテストしようとしているのか明確になります。
(本には書かれていませんが、修正が必要になった時の修正が1回で済むようになるのも大きいと思います)
欠点は、テストクラス内の各テストケース固有の事前処理は記述できないことです。
 

生成メソッドでのセットアップ

複数のテストクラスで共通に使われる事前処理を、独立したメソッドにまとめる手法です。
暗黙的セットアップに比べて、使いたい箇所で使えるメリットがあります。
複数のクラスで使えるよう、独立したクラスで、public staticで作るのがいいそうです。
 

外部リソースからのセットアップ

生成メソッドの利用はテストメソッドの可読性は上がるのですが、結局生成メソッド自身は読みにくいコードのままになることが多いです。これはsetterやputでインスタンスに値をセットするJavaでは越えられない壁… ということで、コードでセットするのを諦めて外のファイルにデータ定義してしまったのがこの方法です。
XML, Json, YAML, CSV などでデータを記述したファイルを別で用意し、それを読み込むことでセットアップする方法です。筆者のオススメはYAMLとのこと。シンプルながらxmlと同等の記述力があるとのことです。フィクスチャがリスト構造で、かつデータが多いならcsvがオススメとのこと。
 
この方法のメリットは、データがJavaでゴリゴリ書くのに比べて読みやすいこと。一方デメリットは、テストデータが、Javaでないファイルに置かれるため、テストコードを追いずらくなることです。
 
前述の通り、今、私の周辺ではこの「外部リソースからのセットアップ」をメインで使っていまして、まさにこのデメリットに悩んでいるところです。
外に記述されたデータそのものは読みやすいのですが、IDEでそのファイルを開くのがほんの少し面倒なんですね。
 
で、本文では、最後に、Javaのプログラム内で、setterなどを使わずにオブジェクトを定義する方法を二つ紹介しています。その中でぜひ試して見たいと思ったのがGroovyを使った方法。
 
static Book createBookObject(){
  new Book(
    title: "Refactoring",
    price: 4500,
    author: new Auhor (
      firstName:"Martin",
      lastName:"Fowler",
    ),
  )
}
 
こんな風に、まるでJsonのように記述できるそうで、これはぜひ試してみたいと思いました。
 
 
【送料無料】JUnit実践入門 [ 渡辺修司 ]

【送料無料】JUnit実践入門 [ 渡辺修司 ]
価格:3,465円(税込、送料込)