TL;DR - 最初の一人はつらいけど後続はそうでもないので先駆者は自覚と誇りを持ってオールグリーンを維持しよう
このエントリはMarionette.js ベースで3ヶ月開発したアプリのカバレッジ推移をまとめてみた - @kyanny's blogというエントリの続きにあたります。未読の方は先にそちらを一読されることをおすすめします。
Marionette.js ベースで3ヶ月開発したアプリのカバレッジ推移をまとめてみた - @kyanny's blogの結論で触れたように、今回テストを書くことにこだわったのは、「クライアントサイド JavaScript (AltJS) のテストを書くのは本当に難しいのか?」という問いに対する自分なりの回答を実践して検証してみたかったという理由があったからだ。
以前から「クライアント JavaScript (CoffeeScript や他の AltJS を含む) のテストを書くのは難しい、大変だ、割に合わない」などという意見を見聞きしてきたが、本当にそうなのか?という疑問があった。賛同できる部分もあるが、できない部分もある。感情的にも、ちょっと厳しい言い方をすれば、「テストを書かない理由を並べたてるのは甘え。つらくてもテストを書くのがイマドキの常識。お前それ t-wada さんの前でも言えんの?」みたいなことを思っていた。
そういう背景があり、今回クライアントサイドアプリケーションを「そこそこの」テストカバレッジを維持しつつ開発してきたわけだが、「テストを書くことの難しさ」にもいろいろあることがわかった。以下、経験を踏まえて得られた知見を紹介する。
Finding: チーム開発で得られた知見
件のアプリを開発していた3ヶ月間、自分でもコードもテストも書いてきたし、チームメイトのコードやテストをレビューもしてきた。それで気がついた、というか改めてわかったのは、「あるパターンのテストを最初に書くときが一番難しい」ということだ。逆にいえば、「あるパターンのテストケースがすでに存在すれば、それを真似たテストケースを増やすのは簡単」ということ。
Marionette/Backbone なアプリケーションなら、テストのパターンはおのずと決まってくる。 Model でむずかしいのはビューとのイベントのやりとりや Ajax 絡みのところだが、誰かが一度 Sinon.JS の Spy なり fakeServer なりを使ったテストケースを書けばあとはパラメータだけ書き換えて使い回すだけでいい。 View でむずかしいのも Spy の使い方や DOM イベント絡みのところだが、そこも誰かが上手に spy/mock/stub したテストケースを一つか二つ書けば、その後ほかの機能を開発する人はそれを真似つつドキュメントを読みつついろいろ試せばいいので、とっかかりやすい。
ふつうのウェブアプリケーション (Rails とか) と同じで、テストと機能開発をバランスよくコミットしていくためには、二つの壁がある。一つ目は、テストスイートを整備し、 CI に載せるところまで。これはわかりやすいタスクで、いろいろノウハウが必要なものの、新雪を踏みならすようなもので比較的楽しい作業だ(で、たいていの場合これをやるとそこで満足してしまい、次の壁を乗り越えられない)
二つ目の壁が、「正しく動く前例を作る」ということ。この壁はある程度開発が進んでからでないと顕在化しない。そもそもテスト対象がそれなりに複雑にならないと込み入ったテストを書く機会もない。この壁が出てくるのは開発の中初期、いわゆるプロトタイピングが終わって具体的な肉付けをし始める段階だ。このフェーズになると開発者は機能を作ることにフォーカスしているので、うまく通らないテストはフラストレーションのもとだし、開発スピードも鈍らせる。だからテストをないがしろにしがちだし、それが正当化もされやすい。
二つ目の壁は思っている以上に高く険しい。これに直面したとき、いかに諦めずテストを書いてグリーンを維持するかが極めて大事だ。ここで諦めてしまうと、あとはもうあっという間に落ちてリカバリは不可能になる。不確定なコードの上で動く不安定なアプリケーションを「直して祈る」憂鬱な日々が待っている(そして数年後まっさらから作り直されて、古いコードは捨てられる)
諦める、というのは開発者個人のマインドという意味でもそうだし、チームのマインドとしても同じことが言える。粘り強く、情熱あふれる個人が不退転の決意で正しい spy のやり方を見つけるというのもいいし、諦めて「ここテストするの難しいので pending でお願いします...」と弱気になったチームメイトを同僚が正しい道に導くというのもいい(具体的には「テスト書いてください。テスト無しではマージできません」と断固たる態度を示したり、かわりにテストを書いてあげたりする)
今回のプロジェクトでは、 Model だろうが View だろうが Controller だろうが、ほとんど常に機能のコードとテストコードはセットで Pull Request されていた。もちろん個々のケースをとってもカバレッジは 100% には届かないし、それらのテストがどの程度効果的なのか?についてはなんともいえない。
ただ、毎日 3~6 個ペースで新しい Pull Request ができ、毎回テストコードが含まれていたということは、このチームにとっては、クライアントサイドであってもテストを書くことがそこまで大きな負担にはならなかった、と言ってもいいはずだ。つまり、月並みな言い方だが、テストを書く気があれば書けないことはないのだ。
Conclusion: 結論
クライアントサイドアプリケーションのテストを書くうえで難しい部分があることは認める。しかし、それは必ずしもクライアントサイド特有の事情ではない。単に新しい概念やツールに習熟するための学習コストがかかっているだけ、という側面もあり、その点ではサーバーサイドも同様だ(RSpec でテストを書き始めたとき苦労しなかった人がいるだろうか?)
どのようなソフトウェアを書くかによっても難易度は大きく変わってくるだろうし、今回たまたまうまくいっただけかもしれない(所詮は個人の体験に基づいた話なので、一般化するほうに無理がある)けれども、テストを書く上で障害となりうる壁、特に開発が乗ってきた頃に直面する二つ目の壁の存在を認識していれば、立ち向かうことができるはずだ。
もしあなたの周りに、テストを書くのが難しいと不平をもらしているひとがいたら、少しの時間でいいから、何が難しいのか話を聞き、実際にコードを読んで難しさの理由を一緒に考えてあげてほしい。たぶんきっと、そのひとは本音ではテストを書きたくないわけじゃないのだ—ただ思うようにいかず嘆いているだけで、誰かがほんのちょっと背中を押してあげればまたいつものように元気よくコードディングに戻れるはずだ。
Photos taken by Nat W and Capt' Gorgeous