プログラマーにとって良いテストとは何か、主に私の失敗の歴史を軸に振り返りながら、改めて考えてみました。
個別のテクニックについては詳述しません。
テストは重要。でも好きになれない。
チームに
プログラマーの作業はソースコードを書く事である
と考えている人が一人でもいると、進捗や品質が著しく下がってしまいます。
SE兼プログラマくらいの人の仕事をカテゴライズすると
- 学習など:1割
- 設計など:2割
- コーディング:2割
- テスト設計・テスト・バグ管理等:3割
- ドキュメント、コミュニケーション等:2割
くらいかなと思います。つまり品質管理が占める割合が最も大きい、という実感です。
テストをしないプログラマー、というのはあり得ません。
しかしプログラマーにとって
- 一番楽しい仕事はコーディング
- 一番つまらない仕事はテスト
という矛盾があります。
コーディングという仕事には達成感があり、目に見える成果を生み出しますが、
テストという仕事にはあまり達成感がない上に、見えにくいため誰からも褒められない傾向があるからです。
テスト設計は上級SEの仕事。設計段階からテストケースを策定。
テスト専門のチームがある
などは理想ですが、現実は実装者にテスト設計とテスト実行を任せる場合も多かったです。
それで、
- 質を上げながら
- 容易に
テストすることを真剣に考える必要があります。
良いテスト手法にたどり着くまで
私自身が辿ってきた道のりを振り返ります。
レベル1:テストケースが少なすぎる
プログラマーは何をするとエラーが起きるかをいちばん把握しています。
しかし、ビギナーにテスト任せるとテストケースあまりに少ないことがよくあります。
正常系のテストしかしていないからです。
正しい動きを知っているがために、誤った動きをさせる発想が抜けるという現象です。
また、誰でも自分の製品の不具合などみたくないという気持ちが暗に働いている事も無視できない事実です。
さらに、作業を早く終えたいという気持ちが異常系のテストを避けることに繋がってしまうこともあるでしょう。
テストは異常を洗い出すためのものであるという認識をメンバーが共有していることはとても大事なことです。
レベル2:テストケースが多すぎて困る
異常系のケースを洗い出すことに真剣に向き合うと、無理やり異常な動きを考え出すという謎のフェーズに突入します。
誰がそんな操作するんだというあり得ないケースまで盛り込みコードの量に見合わない大量のテストケースを考えてしまいます。
自分で自分を追い込んでしまうという辛い状態です。
また、本来であれば考慮する必要のないパターンを含めてしまうこともあります。
同値分割、境界値分析などの基本的な考え方を理解した上で、異常系テストケースを考える必要があります。
さらに**直行表**のようなテクニックを理解すると、テストケースを大幅に削減できるようです。
プログラムの目的や性質に見合ったケースを洗い出すのは、熟練者でも難しいことです。
あまりに時間がかかるようなら、他のメンバーから客観的な意見を求めてみましょう。
それでもまだケースが減らないのなら、機能やメソッドの粒度が大き過ぎる可能性があります。時には勇気を出して設計を疑ってみることも重要です。
レベル3:UI系のテストに悩む
例えばクラスのメソッドをテストをする場合、テストケースをまとめることは難しくありません。
極端に言えば想定しうる全てのパラーメータの値の組み合わせをマトリクスにすれば済むからです。
経験上、UI操作系のテストでは、それとは異なるポイントでつまづいてしまいます。
あるボタンを押すというテストを考えるときに
・そのボタンが表示されている画面を開くための手順を書く
・でも、その画面はある特定の条件でしか開かない画面である
・だから、その条件も書く
・しかしその条件を発生しうるのは特定のユーザが操作しているときだけである
etc...
考え出したらキリがありません。
この問題が発生した場合、まず設計が悪いのではないかと疑ってみることは重要です。
UIとロジックが密結合だと、UIのテストと同時にロジックのテストを行なっている、という現象が起きてしまいます。例えば、イベントハンドラの中にロジックを作り込んでしまっている場合などです。
テストの観点からも、MVCに代表されるUIとロジックを分離するデザインパターンを意識する必要があることがわかります。
また、そもそも画面の設計が悪いという要因も考えられます。シンプルな画面構成、シンプルな画面遷移を意識する必要があります。
Windowsアプリケーションは画面の面積が大きいため、やろうと思えば一画面に大量のメニューやボタンを配置できてしまいます。しかしこれはテストの観点からも望ましくありません。
一方、スマホのアプリは画面の面積が小さいため、配置するオブジェクトを極限まで削ってあります。また、UXの追及により、操作も非常に簡単です。結果として、スマホアプリのUIの構成はテストがしやすい傾向があるようです。
レベル4:レビュワーの環境でテストが再現できない
自分の環境でテストを完了し、ケースと結果をExcelにまとめて報告書を作り、レビュワーに渡して一丁上り。
…しかし、きちんとしたレビュー体制が整っている職場であれば間違いなく次のように言われるでしょう。
「本当にテストをしたのか」と。
Excelの報告書だけではエビデンスとしては不足という事です。
他人の環境でもテストをパスすることを確認して初めて、テストを行なったというエビデンスになります。
そのためには、例えばテストに使用したデータを一緒に渡す必要があるでしょう。
また、実装に関連して変更したDBのスキーマやファイル構造などを、レビュワーの環境で簡単に再現できるようなクエリやバッチを用意して、一緒に渡すことも重要です。
自分の環境だけでなく他の環境でも、テストケースを全て再現できるようにする必要があります。
レベル5:デグレードの壁にぶつかる
開発が膨らむにつれ、コードを変更すると、別の機能に影響が出ることは避けられません。
通常、テストケースを作成する際は新機能にかかる分のみを考慮します。
運がよければ他の機能に影響は出ませんが、実際は悪影響がほぼ必ず出ると捉えてください(デグレード)。
デグレードを完璧に回避する方法は残念ながらありませんが、現在一般的に利用されている有用な方法があります。
それは、機能を実装したら、その機能のテストケース自体を実装する、という考え方です。
テストの自動化と呼ばれます。
レベル6:テストの自動化に挑戦する
テストの自動化の詳細については、いくらでも資料がありますので割愛しますが、端的にいうと
・メソッドの実装を行う
・メソッドのテストを行うコードも実装する。
です。
理論上は、全てのロジックに対してテストコードを書いておけば、
ロジックのどこかを変更した影響で別の箇所にデグレードが発生してもすぐに発見することが出来ます。
最近のIDEではテスト自動化を補助する機能が標準でついているので、自動化すること自体は難しくありません。
デグレードを検出したテストケースをヴィジュアルで確認できるようになっていますので、非常に便利です。
しかしすぐ次の壁にぶつかりへこみます。
レベル7:テストコードを書く負荷に耐えられなくなる
テストを自動化するということは、コーディング量が2倍以上になるということです。
全てのテストケースについて、単純にテストメソッドをつくっていくと、膨大な量になります。また、テストメソッド自体にバグが内包される事態も頻発することになります。
この状態を放置すると、テストコードを書く負荷に耐えられず、
心がぽっきりと折れます。
こちらのサイトに、よくあるケースがまとめられています。
テスト自動化の落とし穴とは
テストの自動化を導入するにはこれらの問題を避けるために方針を決めておく必要があります
レベル8:自動化するポイントとしないポイントを分ける
テスト自動化が**「テストを全自動でやってくれる夢のツールではない」**という認識を共有しておくことは重要です。
こういうテストケースは自動化に向いている
こういうテストケースは自動化に向いていない
という傾向を把握しておくと良い。
私は現時点では以下のようにとらえています。
-
自動化に向いているのは
- ロジックの単体テスト。特にマトリクスで網羅性を表現できるテスト。つまり手動でもやりやすいテスト。
-
向いていないのは
- 結合テスト
- UIに関わるテスト
レベル9:単体テストの自動化だけでも大変
- 自動化に向いているのは
- ロジックの単体テスト
と述べましたが、実際にテストコードを書こうとすると、なぜかすっきりしたものが書けない、という現象が起きます。この場合、再び設計を疑ってみることが重要です。
元々テスト自動化を想定しないで作ったロジックに対してテストコードを書くのは困難です。
原因は様々ですが、最も大きな要因は粒度が大きすぎることです。
例えば
- パラメータで指定されたファイルを読み込んで
- ファイルの文字コードを変換して
- 内容を行ごとに文字列とし、配列に保持してループ
- 文字列がURLである場合、そのURLをたたいて
- 帰ってきたレスポンスがXMLであるかチェックして
- XMLであれば構造を分析して
- XMLの最初の要素の2番目の属性値を取得して
- ごにょごにょ…
これが一つのメソッドの中にかかれていると、このメソッドに対して何をテストすべきなのかが曖昧になり、行き詰ってしまいます。
ロジックの設計者はテストの自動化の観点からも
一つのメソッド=一つの処理
という考え方を徹底するべきです。
結論
色々やりましたが、結局テストを好きにはなれません。
ただ、テストの考え方のポイントは理解できるようになってきました。
- テストケースが過不足なく網羅されており
- 他の人の環境で再現性が高く
- デグレードを検出できるよう自動化するが
- 自動化にこだわり過ぎない。
- そもそも良い設計がなければ良いテストはできない。
- コストでいうと、実装>テストに収まるのがが理想
今はこんなところです。