テストコードは、どの程度かけば良いものなの?


2014年 12月 15日

皆様、Spec(コードベースの単体、機能、結合テスト)書いてますか?
私はあんまり書いていません。

「テスト書かないとかありえない!」と怒られそうなので一応弁解しておきますが、C0レベルのテストは、そこそこ書いているつもりです。
プロジェクトでカバレッジはXX%以上!(XXの部分は95だったり100!だったりします)という決まりがあるため、書いてます。消極的な理由ですね。

時としてカバレッジを満たすためのテストを書く、という意味の分からない事をしてしまったことは、反省しております。

しかし、C0は所詮C0であり、このレベルのテストをいくら一生懸命書こうが、バグがでるときはでるものなのです。
だからといってC1、C2、またはそれ以上を満たすようなテストを一生懸命かけばバグが無くなるか?という話でもありませんが、バグ発生率が少しは下がるかと思います。

さて、最近お固い業務システムで、そこそこ致命的なバグをやらかしてしまいました。
今後もC0レベルのSpecを書いてて良いのか? という話題がプロジェクト内から湧き上がったので、いろいろ考えてみることとしました。

なぜ事前にバグを発見できないのか?

なぜ事前にバグを発見できなかったのか。私にもわかりません。

バグを生み出してしまった時は、必ず再発防止策を考えねばいけないのですが、
「今後同じようなバグを発生させないために、どうしたら良い?」と聞かれても、なかなか答えることができません。

単にテストを怠っただけなのでは。手動テストをもっとしっかりしろ、またはテストコードをもっとちゃんと書け、と言われると、反論できません。
「仰るとおりです」としか言えない。

バグのないシステムは無い!と開き直りたいのですが、そうもいかず。
「期間がなかったから、予算がなかったから、、、」という言い訳も思いつきますが、これで相手を納得させるテクニックは、今の私は持ち合わせておりません。

発見が遅れる、厄介なバグ

物凄く単純なバグは、発生することが少ない、と思ってます。

画面みてわかるレベルのやつですね。ボタン押したらInternal Server Errorとか。
C0レベルのテストは、ちゃんと書いてますからね。

めんどくさいのは、複数の条件が組み合わさって、初めて発生するバグです。
しかも目に見えないバグが一番厄介です。
手作業で行う、結合レベルのテストでも発見できない事が多いからです。

目に見えないバグとは何か?

ある条件の時には、DBのこういうテーブルにはAというデータが入って、さらに別のテーブルにはBというデータが。。。みたいなやつで、それらの情報はWeb上の画面には表示されません。

こういうのは、結合レベルのテストでも見逃されがちです。

目に見えない情報なんだから、別に重要じゃ無いんじゃない?と思われがちなのですが、本当にそうでしょうか。

業務システムの場合、目に見えない重要な情報を蓄えている事が多いと感じます。
あとで使うかもしれないから、とりあえずとっておくという類の情報です。

代表的な例は、監査証跡系ですね。
どの業者の、誰が、いつ、どんな操作を、どのデータに対し、どういう条件下で行ったか。みたいなやつのことです。

ログとか別にどうでもいいじゃん。 って思いますか?
業務システムでのログは超重要です。
私の関わっているシステムでは、ログに加え、データに変更が発生するタイミングで、変更前のデータのスナップショットも保存していたりします。
(1テーブルの関連テーブルが何十個もあって、変更が入るタイミングで関連データ全部のスナップショット取らないといけなくて死にそう。。。という愚痴も言ってみる)

まとめると、発見が厄介なバグは

  • 目に見えない(または見えにくい)
  • 複数の条件が組み合わさって初めて生成されるデータ

のバグです。

どうやって、事前に厄介なバグを発見する?

システムのバグを0にする、というのは「無理」ですが、バグを少なくするための方法も、検討せればなりません。

「致命的なバグがでちゃいましたー。すみません。次から気をつけます。」というわけにもいかず、再発防止策を考えねばいけないためです。

予算とか期間とか無視していいのなら、以下の対策を思いつきました。

  • 手作業で行う結合テストを、もっと厚く実施する
  • 結合レベルでも、DBレベルのデータを目で確認するようにする(いままではほとんどしてない)。テスト条件も増やす。なお仕様変更のたびに手作業での大量のテストし直しが発生するため、テスターのモチベーション維持が課題となる。
  • 自動テスト(Spec)を大量に書く
  • 単体レベルだけでなく、機能テスト/結合レベルのテストも書く。C1/C2を意識し、大量にテストを書く。テストが大量にあることにより、テストが走り切るまでの時間が長くなるのが課題。また、仕様変更によりテストが全く動かなった時に直すのが辛い。

手作業で行う結合レベルのテストで、DBのデータを確認しろ!というのは、なかなかきついものがあります。
また、仕様変更が入ったタイミングで、あの大変な手作業結合テストをもう一度実施!となると、コストもかかるしモチベーションも下がります。

手作業でのテストはやめて、プログラムで自動化してテストしましょうよ!という流れになるのは、まあ当然といっては当然です。

ためしに、自動テストを大量に書いてみた

目に見えないデータが、とある条件下で正しく設定されているか。を確認するためにはどうしたらよいか。

いろんな条件で、その機能実行後にデータが設定されているかを確認する、大量のテストコードを書けばよいのでは、
と思い、試しに機能レベルのテストを大量に書いてみました。

# とある業務システムの、機能Aに関するテストコード (Rspec)

describe '機能Aについて' do
  context '業者AとBが提携関係にあるとき' do
    before do  
      # データ準備
      グループAを用意
      グループBを用意
      業者Aを用意
      業者Bを用意
      業者AをグループAに加入
      業者BをグループBに加入    
      グループAとBで提携関係を結ぶ
      業者AとBの提携関係を結ぶ
    end

    context '業者Aに加入しているユーザAが操作を行う'
      before do
        ユーザAをグループAに加入
        ユーザAを業者Aに加入
      end

      context 'ほにゃららデータがあるとき' do
         context 'さらにほにゃららが' do
           context 'さらに...' do
             it '機能A実行、モデルAのXXXにYYYが入ること' do
             end

             it '機能A実行、モデルAのZZZにAAAが入ること' do
             end

             ... 以下、XXXのデータがYYYみたいに設定されていること、というテストが続く
           end
         end
      end
    end
  end
end

テストを書いていて、気が付きました。

モデル(またはテーブル)AのXXXにYYYというデータが入ること、なんていう細かいレベルのテストを、色んなパターンで実施するテストを書くのは、めちゃくちゃ大変です。
というか、Rspecでこんな細かいレベルのテストコード書いてるの、見たことないぞ。
正直このレベルでテストを書く必要があるのか、甚だ疑問です。

それにテストコード書くのが、めちゃくちゃ苦痛です。
実装書いてる時間より、テスト書いてる時間の方が長い。

テスト書きすぎ問題

テストコードは書きすぎて困ることはない!という主張も、たまに目にします。
本当にそうでしょうか。

テストを書く上で一番大変なのは、テストデータを用意することです。
例えば、とある操作(例えば、物流システムを作っているとして、倉庫から別の倉庫へ商品を移動する操作)を実施するためには、

  • まずグループがあって、その下に業者があって、その下に支店がいて、その下に部署があって、その下に倉庫があって
  • 商品の在庫は倉庫に紐付いていて、倉庫が存在するのはこれこれこういう条件があって
  • 倉庫から商品を移動させるためには、業者AとBの間に提携関係があって、かつ部署間でも提携してないといけなくて
  • 操作できるのは倉庫に紐づくユーザがいないといけなくて
  • 商品を運搬させるためには、運搬業者の情報もないといけなくて、その契約関係もちゃんと無いといけなくて

みたいな、複雑な条件があって、初めて操作が実施できるわけです。
どれか一つがだめだったら、操作できないとか、条件付きで操作ができるとか。

こういう条件を全部満たすために、色んなデータ・セットを用意するのは、相当大変です。
いままではFactoryGirl(というテストデータを作り出すためのライブラリ)で、ちまちまデータを作っていたのですが、これがいろんな条件下のデータを作りだしていると、テスト実行が遅くて遅くて。。。

テストパターンが増えれば増えるほど、テストが目に見えて遅くなっていくのです。
前提となるデータを都度生成しているためですね。
こうなると開発者各々でテストを実行させるのも、億劫になってきます。

仕様変更の際にテストが邪魔になるケースもあります。

最近、プロジェクトで大きな仕様変更がありました。
今私は、1機能を修正するたびに、真っ赤になった何百ものテストケースをちまちま直す作業をしてます。
カバレッジを満たすための、ゴミみたいなテストケースは消してます。でもカバレッジが下がる。。。

なにより、テスト書いてると開発が進まない。
テストが重要なのはわかりますが、実装している時間よりテスト書いてる時間の方が長いっていうのは、なんだかなという思いがあります。
バグがでちゃいけないシステムなんだから、テストもしっかり書くのは当たり前!と言われればそうかもしれませんが、やっぱり納得いかない。

まとめると、テストを書きすぎると、以下の問題が発生します。

  • テストコード実行が遅くなり、開発スピードが落ちる
  • 仕様変更のタイミングで大量のテスト修正が必要になり、開発スピードが落ちる
  • テストばかり書いてると実装が進まず、開発スピードが落ちる

つまり、開発スピードが落ちます。

業務システムも、開発スピードを求められる時代。
プロジェクトに1年も2年もかけてはいられないのです。

じゃあ手作業での結合テストをもっと厚くする?

自動テストを大量に書くのは、開発スピードが落ちてやばそう、という気がします。

では、やはり手動でのテストしか無いのか?
仕様変更がほぼ発生しない状況下を創りだして、手動のテストを厚くするのが良いのか?
けど仕様変更が発生しないシステムなんて、今時ないですよね?(元々無い気もします)

それに、今回問題提起したバグっていうのは、Web画面上に表示されないデータのバグを、どうやって発見するか?ということでした。
手作業での結合テストで、DBに正しい値が入っている、なんていう確認をするテスト、やりたいですか?僕はやりたくありません。

テストコードをどの程度かけばよいのか?

テストコードをどの程度書けばよいのか?
その答えは未だでてはおりません。

結局のところ、手動テストと自動テスト、どっちを取るか?という話ではなくて、両方をうまい具合に混ぜてテストしていくしかないと思っています。
なんでもかんでも自動化すればうまくいくっていうことはありません。

ここはバグ出そうだな、という部分は自動テストも厚くするし、手動テストでももちろん実施する。
手動でのテストでも、場合によってはDBレベルの確認を含める。
あまり細かすぎる自動テストは、のちのち自分の首を締めそうなので、辞めたいところです。

今はその、丁度良い塩梅を見極めている最中です。

最近のシステム開発は、予算も無い、期限もない、でも高い品質を求められるという、もはやどうしたら良いのかわからない状況になりつつあります。

低価格、短納期、高品質、これら3つをすべて満たすことはできないのです。
でも、これらを満たすための努力はしなければなりません。

徹夜で仕事して達成する、とかそういう辛い話ではなくて、テスト自動化を駆使して、なんとか解決するための方法を考えなければいけません。

で、結局のところ、テストコードはどの程度、どのレベルまで書いたほうがいいんでしょうか。
皆様の意見を伺いたいところです。