読者です 読者をやめる 読者になる 読者になる

レガシーテストコード改善ガイド #TddAdventJp

はじめに

このエントリーは、TDD Advent Calendar 2013 - Qiita [キータ]の参加エントリーです。前日のエントリーは、@PoohSunny(swimming_pooh)さんによる「2013年のTDDイベントをまとめてみた」でした。

今回が初のAdvent Calendar参加になります。どうぞよろしくお願いします。

というわけで、技術的な話は控えめにして(コードは1行も出てきません!)、自分の経験をふりかえりながら、これまでの私的TDDの反省とか改善点とかを紹介したいと思います。

一言で言えば、このエントリーでの主張は「テストコードもちゃんとリファクタリングしていかないとダメだよね」というものです(タイトルはちょっと仰々しく付けてしまいました…)。もうちょっと詳しく言うと「読みやすく、保守しやすいテストコードを書くために、アンチパターンを知り、定石を積み上げていくことが大事だよね」というものです。

TDDと私

まずは自己紹介も兼ねて、これまでの私とTDD(主にテスト自動化)の関わりについて簡単に紹介したいと思います。

新卒で今の会社(某SI)に入社して、今7年目になります。はじめてテストの自動化に取り組んだのは2年目のとあるプロジェクトなので、なんだかんだでTDDとの付き合いも6年くらいになりました(中にはまったくテストコードのないものもありましたが…)。

最初はJava(JUnit, EasyMock, Selenium)で先輩のものを真似しながらテストコードを書いて、Jenkins(当時はまだHudsonでしたが)上で毎日走らせて…、みたいなことをやりました。当時は使えるテクニックがassertEqualsと慣れないEasyMockのエクスペクテーションだけだったので、とにかく書いたテストを通すことで精一杯で、テストファーストなんて到底できてませんでした。その後のプロジェクトでは、Javaの他、Ruby(Test::Unit, RSpec, Shoulda)とか、最近だとJavaScript(Mocha, PhantomJS)なんかを使って、テストの自動化に取り組んできました。

痛い経験

ある時、数年前に自分が作ったコードをメンテナンスする機会があり、そのテストコードの読みにくさ、保守しにくさに目を覆いたくなることがありました。プロダクトコードの修正はごく僅かなのに、それに伴うテストコードの修正に1日以上かかってしまったんですね。更に悪いことに、たとえテストがグリーンになっても心理的な不安は払拭できず、TDDの大きな恩恵を損ねてしまっていました。

そんな悲しい出来事もあって、最近では、プロダクトコードだけでなく、テストコードの読みやすさ、メンテナンスしやすさに対しても関心を払うようになってきました。そうして、テストコードも、きちんとリファクタリングすることが大切だと改めて感じさせられることになった次第です。

TDDアンチパターンを知る

どんなテストでも、ないよりはあった方が遥かにマシであり、テストコードを書く時は、まずは、繰り返し可能(Repeatable)で独立している(Independent)テストを書くことにこだわるべきという@t_wadaさんの主張*1にはとても強く同意します。

その上で、TDD、自動テストにも「べからず集」みたいなのが確かにあって、これは、TDD Anti-patterns(TDDアンチパターン)として紹介されています。紹介記事はこれ(by James Carr、元ネタ)とか、これ(Stack Overflow)とか。joker1007さんが日本語に訳した記事もあります。

このTDDアンチパターンをはじめて読んだ時、自分の経験に照らしあわせてみると、非常に「あるある」でした。

  • テストメソッド名が何を示しているのか分からず(test_XXX1, test_XXX2)、後で見た時に必要なテストかどうかが分からない(The Test With No Name)
  • モックが多すぎて、テストコードがほとんどエクスペクテーションで埋め尽くされてしまっている(The Mockery, The Giant)
  • バリデーションの網羅テストを、エンティティのユニットテストGUIのテストで両方実施(I'll believe it when I see some flashing GUIs, The Slow Poke)

等々、枚挙にいとまがありません。

ところで残念ながら、TDDアンチパターンでは、解決策は特に触れられていません(ディスカッションはされていますが)。そのため、アンチパターンに陥っている、つまり、よくない自動テストに向かっていってしまっていることは認識できても、どう対処すべきかで迷ってしまうんですよね。

このうち、メンテされないテストコードを示す「Second Class Citizens」パターンについては、きちんとテストコードもリファクタリングしていくことが一番の解決策だと思います。というわけで、こういったアンチパターンに該当するテストコードを総称して「レガシーテストコード」と便宜上呼ぶことにし、リファクタリング(改善)してくためにはどうすればよいか、模索していきました。

レガシーテストコード改善

私が参考にした(している)書籍やWeb上のリソースのうち、テストコードをリファクタリングするためのエッセンスが詰まっているものと、最近の取り組みについて紹介したいと思います。なお、どうしても付き合いの長いJUnit関連の本が多くなってしまうのはご了承ください。内容は他の言語でも通用すると思います。

書籍編

xUnit Test Patterns: Refactoring Test Code

xUnit Test Patterns: Refactoring Test Code

本エントリーで主張しているような内容を更に広く深く扱っている本だと思います*2。ただ、少し古いのと、翻訳版がないのがネックかも。

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)

内容はJUnitに特化していますが、ボリュームもちょうどよく、読みやすかったです。ユーティリティメソッドからマルチスレッドやServletのテストに至るまで、幅広い演習問題がついているので、色んな場面で適用できるテストの書き方が身につくと思います。

実践テスト駆動開発 テストに導かれてオブジェクト指向ソフトウェアを育てる (Object Oriented SELECTION)

実践テスト駆動開発 テストに導かれてオブジェクト指向ソフトウェアを育てる (Object Oriented SELECTION)

タイトルの通り、実績的なTDDの本です。TDD全般を扱った本ですが、その中でも「第IV部 持続可能なテスト駆動開発」が本エントリーのテーマに一致しています。本の中でも第IV部は、短いページの中に内容が凝縮されているので、オススメです。なお、本のタイトルからは分からないですが、書籍上の言語はすべてJavaです。

レガシーコード改善ガイド (Object Oriented SELECTION)

レガシーコード改善ガイド (Object Oriented SELECTION)

最後に、あえて言うまでもないですが、本エントリーのタイトルn元ネタになっている本です。やはりプロダクションコードもテストコードも、(様々な特性の違いはあるとは言え)よいコードやリファクタリングの基本原則は共通する部分が多いです。

Web編

書籍ではなくWeb上の言えば、やはり最近は「「これであなたもテスト駆動開発マスター!?和田卓人さんがテスト駆動開発問題を解答コード使いながら解説します〜現在時刻が関わるテストから、テスト容易性設計を学ぶ #tdd|CodeIQ MAGAZINE」ですかね。テスト対象は同じでも、テストコードの書き方は十人十色であることに、テストコードを書くことの奥深さを感じました。

実践編

ユニットテストにおいては、以下の点を心がけると、少しずつテストコードも整理していくことができると思いました。

  • テストコードの中で条件分岐、ループを排除していく
  • 重複したコード(インスタンス生成部分、セットアップ部分、モックのエクスペクテーション部分など)はメソッドとして切り出す(状況に応じて、基底のテストクラスやテストヘルパーなどに切り出して利用する)
  • 1つのテストが最低でも1画面で収まるようにする
  • テストコード内に2行以上のコメントが必要であれば、テストコードがよくないことを疑う

また、OSSのテストコードを読んでみるのも勉強になりました。実際に読んでみると、「ケースが思ったよりも少ない」と感じることがほとんどでした。でも、これは決して「ケースが足りない」のではなく、「無駄がない」ということなんですよね。テストケースの数を増やすだけなら、コピペでいくらでも簡単にできますが、テストの実行時間やメンテナンスのことを考えると全くメリットがありません*3

また、テストコードの読みやすさという点で、最近の取り組みで効果があったかなと感じたのは、テストコードをペアプログラミングすることです。テストコードは得てして書き方がバラバラになりがちなので、均質化に役立つのはもちろんのこと、ケースの洗い出し方などの思考も分かるし、学ぶところが多かったです。

おわりに

TDD Advent Calendar参加にあたり、「何か書きます」とは言ったものの、本当に「何か」以上の内容ではない話になってしまったかもしれません…。本当は具体的なコードとか色々と載せる予定だったのですが…。いざ書くとなると色々知識不足を痛感します…。とは言え、おかげ様で今年は例年以上に色んなAdvent Calendarを楽しませていただいています。

さて、明日は@d_nishiyama85さんです。よろしくお願いします。

*1:テストを書く文化を育てる戦略と戦術」より。ご覧になっていなければ是非。

*2:この本があればこのエントリーが要らないとかなんとか

*3:リリース判定時に上位層を納得させるために、テスト密度を許容値に底上げしたい場合は別ですが…。