TDDを行った時にぶつかった7つの壁

  • 144
    いいね
  • 4
    コメント
この記事は最終更新日から1年以上が経過しています。

はじめに

僕が初めてTDD(テスト駆動開発)に出会ったのは2004か2005年。(どっちか忘れた。)
永和システムマネージメントさんが主催しているオブジェクト倶楽部というイベントで初めて知った。

「こんな方法でプロジェクトを管理することができるんだ!」

とかなり感嘆した記憶がある。

そんなTDDを実際に現場に導入したり、導入している現場を見て感じた事。
結果的に僕がテストコードをほとんど書かなくなったことについての経緯を書いていこうと思う。

TDDを導入すれば品質が上がると盲目的に信じている人や、TDDの導入をしている(しようとしている)現場がTDDについて一歩踏み込んで考えてもらえればと思う。
※全文を読んで頂ければわかると思いますが、僕はTDDを批判しているわけではありません。コストに見合わない事もあると言うことを伝えるために書いてます。

TDD(テスト駆動開発)とは

平たく言うとビジネスプログラムを書く前に、テストコードを書くことです。
これだけでは、テストを先に書いても後に書いても同じ効果を得られそうです、
しかし、テストを先に書くことで以下の恩恵を授かることができるのです。

「テストコードを先に書くことによって、自分が作るべきプログラムが明確化する。」

これはシステムを作らない人が描いた要件定義をシステムを作る人がテストコードと言う形で明文化する事を意味してます。
これが大きなメリットになります。

ある一定レベル以下のプログラマーがビジネスプログラムをいきなり書いてしまうと、ある一定の目的だけが達成できる整理されていないプログラムが出来上がりがちです。
しかし、はじめにテストコードを書くことで何をすべきであるかが頭に入り整理されたプログラムが書きやすくなります。
(これは、テストコードによってインターフェースが決まってくるおかげでもあります。)
これだけでもTDDには大きな価値が見出せます。

しかも、TDDの威力はそれだけではありません。
事前に作成するプログラムイメージが生まれるため仕様齟齬に気づく機会が早めに訪れるのです。
ビジネスプログラムを書いている際に仕様齟齬が見つかると後戻りが大きい場合がありますが、テストコードを書いている途中での後戻りはそこまで大きくはありません。
そして、極め付けはビジネスプログラムが出来た時にはテストコードも一緒に出来上がっているため堅牢なシステムが作り上げられやすいと言う当初の目的も達成できます。

他にもあげればきりがないのですが、僕感じた主なTDDの利点を簡単にまとめます。

  1. テストコードが必ず作成される。
  2. メンバーが仕様を明確に把握した状態で開発に取りかかれる。
  3. 2の延長線で、何を作る必要があり/どのように作れば良いかを事前に得ているため最適なプログラムが出来やすい。
  4. 仕様齟齬に気づくタイミングが早い。
  5. 作るものが明確になっているためタスク整理がしやすい。(別にテストを書いた人が実装を書く必要がないため) etc...

当時の僕の状況

TDDを知った当時に私が働いていた現場はウォーターフォール型の典型的な失敗パターンで進んでいる現場だった。
テストコードはおろか、タスクすら曖昧なまま適当な線が引かれたExcelに沿って仕事をしている状況だった。
いわゆるカウボーイコーディング1と言うやつだった。
そんな状況だったので、このようなプロジェクト管理方法がある事を知った時には感動をしたのを覚えている。

僕はこの優れたプロジェクト管理方法をどこかで実践したいと思い、一人マネジメントに関する勉強をしていた。

時は流れて2009年

2009年。
僕は幾つかのプロジェクトマネジャーを任される立場で仕事をしていた。
現場である程度の実績を上げたため、僕はある程度の権限をもらっていた。
そこで今まで僕が学んでいたアジャイル開発2を試すために、一つのプロジェクトを立ち上げた。
そのアジャイル開発の一部にTDDを含めて行ってみた。

はじめの壁

僕はTDDを導入すれば全てがうまくいくと盲目的に信じていた。
当時立ち上げたプロジェクトではRailsの2系を使っており、テストに関する環境は揃っているかと思ったのですが・・・
通常のユニットテストなら環境を使えばいいのですが、HTTPのリクエスト/レスポンスに関してのテストを行うには少し環境が弱くテストコード用のヘルパーを書かなければなりませんでした。
(当時はRSpecを知らなかった。)
これらの環境を整えるのに少し時間を有しました。
→RSpecほどではないが、文章として成り立つようなテストが書けるように工夫はしていた。

2番目の壁

Webアプリケーションをテストする上で重要なのは、テストコードで担保するものを何にするかの選定かと思います。
それについても悩みました。
ユニットテストにしてしまうと、以下の問題が出てしまいます。

  1. 仕様を深くまで把握してテストを作る必要性が出てきてしまうので、テストコードに大きなコストが発生してしまう。
  2. Railsの機構を使うとモデルにコードを書かず、コントローラーに簡単なコードを書くだけのケースが多くなりユニットテストが不要のケースが多々出てくる。

初めてのTDDということもあり、この辺りの判断は大いに悩みました。
悩んだ結果、初期段階ではインテグレーションテストのみ手厚くレビューをする方針で行くことにしました。
(他のテストコードを書かないわけではなく、全体的なコードレビューからの対象からユニットテスト等を外したのです。)

この理由としては、TDDのメリットである「仕様をメンバーが把握する」という利点を活かしたいと思ったためです。

3番目の壁

これでTDDをする準備が整いました。
そこで実際にメンバーにもテストコードを書いてもらい、使いやすさの確認やテストコードがドキュメントとして成立するかを確認して実践に投入しました。
当初は大まかな画面遷移と簡単な処理だけだったのでテストコードは誰が書いても同じようなものが出来てました。

この時の問題は、テストコードを書くほどの事も無い処理に対してもテストを書かなければならないと言う問題でした。
CRUD系のユーザー管理、カテゴリー管理などは似たようなテストコードが量産されてしまった。
結果コピペされたテストコードが増え、同じようなことをさせるためメンバーのモチベーションを下げてしまうという状況に陥った。

4番目の壁

テストコードをテストしないという問題が発生した。
これは、コミット前にテストコードを流してからコミットしてもらうつもりだったができなかった。

理由として一番多いのは「忘れていたから」だ。他にもテストの実施に時間がかかると言うのもあった。
そのためテストを通していないコードがSVN(当時はまだGitを知らなかった。)にどんどんコミットされるようになってきた。

Rubyを使う以上テストの実施は必須(と、当時は思っていた。)だが、メンバーみんなにテストしてもらうのは現実的ではなかった。
ただでさえモチベーションが下がりつつある中でテストコードを個人個人にやってもらうわけには行かず、、、
結局、夜間バッチでテストコードを流し結果をメールで通知する方法をとった。

この時点になる頃にはTDDの良し悪しがメンバーにも浸透し始めた。
はじめにインテグレーションテストをすることで、リクエストとレスポンスについて要求されるものが何かを各メンバーが把握して進めれるというのは高い効果を発揮した。
一方で似たような業務ロジックや共通ロジックを一部使っているところでもテストコードを書く必要があり、テストコードを書くのが面倒臭いと言う空気を醸し出してしまった一面も出てきた。

5番目の壁

コードレビューをしている人ならわかると思うが、テストコードも同じでメンバーによって粒度にばらつきがでた。
具体的には「ここまで考えなくても良い」というくらい細かいテストを書く人と「やたらと抜けが多い」という大雑把なテストを書く人が出てくるのだ。
これは当初からあった問題で、その場その場で調整しながら粒度を合わせていたのだが最後まで全体的な粒度を揃えることはできなかった。
(レビュー後に揃えると言う事は行っていたが、メンバーから出てくる粒度は毎回ずれていた。)

考えすぎている部分や考慮漏れのイレギュラーケース等を実際にビジネスプログラムを書く前に把握できたのは大きなメリットだった。
一方で、テストコードのレビューに大変時間がかかるという最後まで解決できない問題が残ってしまった。

6番目の壁

ステータスなど状況によって処理結果が変わるテストコードを書くのに時間がかかった。
僕が未熟だったからかもしれないがビジネスプログラムでは簡単にかけるけれど、テストを書くとなるとパターンが多すぎて大量にコードを投入しなければならない状況が発生した。
また、UIに関わる部分やサードパーティと連携して結果を返す部分に関してはそもそもテストが書けなかったりモックを使ってテストを書いたりでかなり非効率的な状況が発生した。

最終的にはUI部分はseleniumで解決したが、正直かなり荒かった。
また、モックを使うテストもあらゆるパターンのモックを準備することが現実的に難しく荒い形になってしまった。

7番目の壁

最後の壁だ。
この壁を崩せなかったため最終的にTDDをやめた。

それはビジネスの変化にシステムが追いつかない状況が発生したということだ。
大げさに言うと「昨日言っていた仕様が今日変わる3」というレベルの物を許容しなけれならなくなった時にTDDをする時間がなくなったのだ。

それ以外の問題

初めてのTDDということもあり、パフォーマンステストが一切抜けていたというのも結構痛かった。
ここで作っていた製品は(Bto)BtoCの製品だったため、お客さんによっては処理速度が足りず土壇場に速度改善をしなければならないという問題が発生した。
TDDでテストコードができていたため、ある程度は品質を落とさずに対応できたのだが、、、メモリキャッシュやKVSを使って回避したところが出てきてしまった。
そうなると、事前のリクエストで作られたキャッシュを次のリクエストで使いまわしたりしたため結果的にテストにパスしないコートが出てきてしまった。

もう一つ大きく痛かったのがRails2からRails3へのバージョンアップだ。
Rails2系と3系は大きくシステムが変わっている箇所があり、ほとんどのテストコードが動かなくなり結局テストコードを捨てた。
せっかく作った資産だっただけにこの時は落ち込んだ。(これはTDDの問題ではないけれど、、、)

やってよかったけれども、もうやらない

TDDを行うことで事前に何を考えれば良いかということがメンバーに浸透したことは、チーム全体を見れば大きな成果だった。
(実際に開発効率は、他部署の6倍と言う数値を出したこともある。※人月計算で試算したコストから計算)
また、テストコードを意識してビジネスプログラムを書くことは疎結合な綺麗なコードができやすい。これを学んだ事も大きな資産である。
なので、やったことによって得られたものは非常に大きいと思っている。

しかし僕が今回担当したプロジェクトはそこそこの規模で現場の調整が難しいかった、それに加えてTDDでできたテストコードのチェックまでするのはもうこりごりだと思った。
このプロジェクトはマネジメントをしている僕が一番スキルが高かったため、コードレビューで大きく僕の時間が割かれるのは厳しかった。

それに代わるものは?

TDDを行ったことで、プロジェクトを推進する上で重要なのはメンバーが何をすべきかを知っていると言うことだと僕は感じた。
なので仕様を説明して担当を決めた後に、メンバーへ何をするかを問いただせばTDDの一部を代替できると思い実践した。
具体的には朝会で「今日やること」を話させて、それの方針の是非を確認すれば良いことだった。
重要なのは自分がやることを、自分の言葉で話させることのなのだ。

そして、もう一つ。
それは作ったものを他の開発メンバーが手動テストをするフェーズを作ることだ。
僕はペアテストという形で、二人一組でテストをしてもらった。
これに実施することでイレギュラー処理や仕様の考慮漏れを見つけることができる上に、テストした人達が仕様を把握できるというメリットがあった。
しかも、短時間で実施できるというのが最大のメリットだった。

そこで発生したバグのみをテストコードに書いていくという方法を使うことで、パフォーマンスは大きく改善した。
(品質は若干落ちたが、、、)

テストコードのレビューについて

最近、機会があってテストファースト(TDDではない)を実施している現場に入った。
その現場はテストコードを誰もレビューをしないでコミットしていくと言うものだった。

自動テストの恩恵はある程度あるものの、正直抜け漏れが多くその現場では多くのバグが発生していた。
(自分で通るテストを書いてコミットして、誰もチェックしないのだから当然と言えば当然かと。)

僕は上記に書いているように「メンバーが仕様を把握しているか?」と言う、作る前にイメージすることで得られるメリットを大切にしてきた。
それを無くしてしまったらテストは先に書いても後に書いてもあまり効果が変わらないと思える現場だった。
(ある程度のデグレは防げるが、費用対効果で考えるとかなり悪いのではないかと・・・)

なので、TDDにしろテストファーストにしろテストコードを書くならそれに対してもレビューをすべきだと思っている。

個人で受けている仕事について

僕はTonwnSoftと言う屋号で仕事をしている。
そこで請ける仕事ではRailsを使って、サービスを提供しているが、テストコードは一切書いていない。
しかしテストコードを書かないことによっての品質低下はなく、むしろバグがないと言われることの方が多い。

ある程度の規模になると難しいところがあるが、逆に言うとある程度の規模まではテストコードがなくてもある程度の技術力があれば十分担保できると思っている。
(代わりに、僕はメソッド内のコード量を極力減らしコメントを手厚くしている。それは品質向上とともに、度々発生する改修依頼を受ける時に大きな手助けをしてくれている。)

結果、僕はTDDをしたことでプログラミングスキルが上がりテストコードがあまり要らなくなった。(少し自意識過剰かも・・・)

まとめ

結局のところ、僕はTDDの導入に失敗した。
僕のスキルが足りなかったのかもしれないが、他にもいろいろ問題があった。

例えば、先輩よりも出来る後輩がこのプロジェクトには存在していて

「綺麗なテストコードを書く後輩」と「ずさんなコードを書く先輩」と言う謎な状況が生まれてどっちの粒度に合わせたルールを作るべきか?

と言う難しい局面で決めたルールが後々まで尾を引いたりした。

なので、結局は現場とメンバーに合わせて使うツールの一つがTDDなんだろうと言うのが僕の結論。
で、もう少し踏み込んで言うとメンバーの仕事が明確化されてコードレビューをしていればTDDを採用しない方がパフォーマンスが出るのではないかと思う。
そして、そのスピード感を今のビジネスが求めているのではないかと思ってます。
(まぁ、そんなに単純な話ではないんですけどね・・・)

TDDは死んだ

DHHの発言の影響か、このキーワードを元にした記事が増えているので僕はどうだったかな?と思って書いた記事です。

宣伝

TonwnSoftでは仕事も請けておりますので、マネジメントを頼みたい方などはご連絡ください。


  1. 本編とは全然関係ないのだが、「カウボーイコーディング」で検索すると僕が昔書いた記事を引用している人のブログがはじめにヒットする・・・。僕の記事より引用記事の方が上位にくるなんて・・・(涙 

  2. 機会があればアジャイルに関して、僕が実践した結果を書いていきたいと思うがここでは話が反れるためキーワードだけを出しておく。 

  3. この言葉が独り歩きをしているみたいなので補足。これはイメージを実態化したものを見せるとイメージが違う場合が往々に発生する。そのフィードバックを吸収するためにプロトタイプを作ってすぐにフィードバックを得て修正する事でビジネスを加速している事を大げさに書いています。これがTDDのモデルと合わないと思って書いた言葉です。