90-コードを見る人のためにテストを書く

プログラマが知るべき97のこと」の90個目のエピソードは、テストに関する話です。きのこ本には本当に多くのテストに関するエピソードがあります。テストの書き方、目的、心構えなど様々なテストに関する重要なトピックがありますが、このエピソードでは「誰のためのテストか?」という点について書かれています。56ー未来へのメッセージなどでも書かれていますが、プログラムを現在の自分の自己満足にしてはなりません。テストも同様であり、自分の為や製品の品質の為ではなく「コードを見る人のため」にテストを書く事が良いテストの条件です。

・コンテキスト、出発点、満たすべき事前条件がわかる。
・ソフトウェアがどのように起動されるかがわかる。
・期待される結果と、確認すべき事後条件がわかる。

これらの条件を満たすテストは、良いテストです。一見、当たり前のようにも思えますが、他人がテストコードを読んだとき、これらの事が明確に解るテストコードを書けるようになるには、それなりの技術が必要です。少なくとも、テストコードを書こうとしているプログラマは、これからテストしなければならない事を明確に理解している必要があります。そして、理解しているだけでは不十分であり、それを表現することが必要なのです。頭で理解はしても、それを他人に説明するのは難しい、それはプログラマであれば誰もが知っている事でしょう。
とはいえ、良いコードを書くための技術は、訓練と学習によって身につけることができます。ポイントを押さえ、反復してテストコードを書いていく事で、必ず身につけられる技術です。
自分で良いテストコードを書くために心がけている点を紹介します。まず、何よりも重要な事ですが、テストコードを書く時に何をテストしなければいけないかという点を正確に理解する必要があるため、テスト対象が可能な限りシンプルなものにします。きのこ本の他のエピソードでも何度も紹介されていますが、DRY原則や単一責任原則などコードや設計をシンプルに保つのは良いソフトウェアを開発する為の基本原則と言えるでしょう。テスト対象がシンプルであれば、テストが理解しやすく、書きやすいという単純で強力な事です。
次に心がけるのは、テストの「型」を常に意識することです。テストには「事前条件等の準備」「テストとなる事の実行」「結果や事後条件の検証」「後始末」という3つのステップで行われます*1。テストコードも概ねそのように書かれる事になりますが、この「型」を崩さないように意識するということが重要です。ありがちな読みにくいテストコードとして、事前条件の準備に非常に複雑で長いコードが書かれているものがあります。どこがテストの実施でどこが検証なのか解りにくいだけでなく、コードを読むテンポも崩れるため、読みにくいテストとなるのです。テストコードはシンプルに「準備(Setup)・実行(Exercise)・検証(Verify)・後始末(TearDown)」をコンパクトにまとめます。また、変数名なども「型」を意識して付け、テスト毎に異なることも避けるべきです。期待値となるデータであれば「expected」、実行結果であれば「actual」など解りやすく、名前を統一する事は重要です。

  @Test
  public void someTest() throws Exception {
    // 1. Setup
	// 2. Exercise
	// 3. Verify
	// 4. TearDown
  }
}

最後のポイントは、リファクタリングを恐れないということです。本来のリファクタリングであれば、テストコード*2を作成する事がリファクタリングの前提条件となります。したがって、リファクタリングもどきとなってしまいますが、コードを読みやすくするためにテストコードを変更する事に注力することです。例えば、GAE(Slim3)ではユーザがログインしている状態をエミュレートするために次のようなコードをセットアップする必要があります。

  @Test
  public void someTest() throws Exception {
    // Setup
    tester.environment.setEmail("homuhomu@deathmarch.jp");
    tester.environment.getAttributes().put("com.google.appengine.api.users.UserService.user_id_key", "0000");
	// Test
	// Verify
  }
}

しかし、このコードをヘルパーメソッドとして抽出すると次のように記述できます。

  @Test
  public void someTest() throws Exception {
    // Setup
	whenLoginBy(tester, "homuhomu@deathmarch.jp", "0000")
	// Test
	// Verify
  }
  public static void whenLoginBy(ControllerTester tester, String email, String userId) {
    tester.environment.setEmail(email);
    tester.environment.getAttributes().put("com.google.appengine.api.users.UserService.user_id_key", userId);
  }
}

余計なコードは見えなくなり、テストの中では「homuhomu@deathmarch.jpによってログインされている時」と事前条件が明確になります。このコードは、GAEに関する知識がなくとも意図が伝わるでしょう。
現代のソフトウェア開発では、テストが重要なファクターをしめています。メンテナンスしていくコードもプロダクションコードよりもテストコードの方が負荷がかかっていくものです。言語の選択や設計もテスタビリティ(テストしやすさ)を重視した設計を選択する事は間違った選択ではありません。プログラマにもテストに関するスキルが求められてくるでしょう。テストスキルをあげることで、設計力も確実に向上します。これまであまりテストに関心がなかったのであれば、これを機会にテストを真剣に学習してみることをお勧めします。

プログラマが知るべき97のこと

プログラマが知るべき97のこと

*1:XUnit Test Pattern- Four Phase Test http://xunitpatterns.com/Four%20Phase%20Test.html

*2:すなわちテストコードのテストコード