新巻洋一

2015.06.09

システムの品質とUTについて

初めまして、1月に入社した新巻です。

最近はコードを書くよりも、システムの品質管理やテストに時間を割くことが多いので、書籍を参考にしつつ、私自身の開発経験も踏まえた上で、私なりにまとめてみました。

テストは何のためにやるの?

テストの目的

よく勘違いされますが、テストの目的は「バグがないことを証明するため」ではありません。

よほどシンプルなプログラムでない限り、取りうるパターンや状態は無限大といってよいほどあります。ちょっとしたWebサービスでも機能の正常、異常以外に、以下のような外的要因に左右されます。

  • ブラウザの種類 => 特定のブラウザだけうまく動かない
  • ネットワークの状態 => ネットワーク遅延により、タイムアウトが発生
  • 接続先の外部システムの状態 => 明らかに仕様違反の結果を返す
    etc…

上記のような様々な状態、事前条件をすべて網羅するのは現実的には無理でしょう。では 何のためにテストをするのでしょうか。

テスターのメンタル・ライフ

以下の表は、テスターの精神的な成長を5段階で表す「テスターのメンタル・ライフの5つのフェーズ」というものです。

フェーズ名 概要
フェーズ0 ステップ・デバッガでプログラムの動きを確認する活動
フェーズ1 対象システムが期待通り動くことを証明するための活動
フェーズ2 対象システムが特定の条件で動かないことを証明するための活動
フェーズ3 テストは何も証明しないが、システムが受け入られるレベルの価値を発揮できる状態かどうかをモニタリングし、不十分な品質のままリリースしてしまうリスクを減らすための活動
フェーズ4 テストは活動ではない。 そもそもテスト以前にバグを潰すマインド・セットを養成するための精神修行である。

フェーズ4だけは特殊で精神論のようですが、フェーズ3の「リスク管理の一環としてのシステムの品質のモニタリング活動である」というのは、システム開発で重要なものは何なのかを示唆しています。次は、今回挙がった「システムの品質」とはそもそも何なのかを深堀します。

システムの品質とは?

深くて難しい「品質」

そもそもシステムの品質とは何なのでしょうか。人によって思い浮かぶものは様々だと思いますが、IEEEが定義するシステムの品質を要約すると、

「システムがステーク・ホルダの要望、期待を満たしているかどうか」

ということになります。

小規模なWebサービスでも、システムのステーク・ホルダはたくさんいます。

  • プロダクト・オーナー
  • PM
  • 運用
  • 営業
  • クライアント/ユーザー
  • 開発者

etc…

それぞれのステーク・ホルダがシステムに対する要望や期待を持っており、相反する要件を満たさなければならないこともあるでしょう。また、長くなるのでIEEEが定義するソフトウェアの品質モデルの詳細は割愛しますが、前述のステーク・ホルダがシステムに何を期待しているのかを考えれば、システムとしてどんな品質特性を定義、維持管理することが必要か想像がつくと思います。以下はその一例です。

  • 機能性
  • ユーザビリティ
  • 信頼性
  • 性能
  • 保守性
  • セキュリティ性

etc…

例えば、重大なバグやセキュリティの問題で、顧客への損失補填が必要なケースもありえますし、レスポンスが遅いと、ユーザーが使ってくれないかもしれません。システム開発というと、プログラミングやデザイン・パターンなどのツールの話ばかりが注目されがちですが、システム開発の目的は

「ステーク・ホルダの要望、期待を満たすこと」

なので、ツールの話をする前に、そもそも何のために仕事をしているのかをよく考える事が大事です。次はシステムの品質に対するアプローチについて見ていきたいと思います。

Gravin‘s Quality Approaches

以下の表はシステムの品質に関するアプローチをまとめた「Gravin‘s Quality Approaches」というものです。上にいくほど定性的で計測が難しく、下に行くほど定量的で計測が容易です。

アプローチ名 概要
Transcendent approach 客観的に評価できないフィーリング。(なんかよい、かっこいい、使いやすい etc…)
User-based approach ユーザーのニーズを最も良く満足させたものが、最も品質が高い。品質≠満足度なので、品質が低くても問題ないこともある。
Value-based approach 製品のコストと、満たせている要求を比較する。例:信頼性が低くとも、コストが1/5ならトータルのValueは信頼性が高く、コストが5倍の製品に勝る
Product-based approach 製品が持つべき品質特性と、現状の品質との差分。ソフトウェアだと計測が結構難しい(が、できないわけではない)
Manufacturing approach システムの製造工程(=実装)に着目する。例: バグ密度、バグ収束曲線

もちろん、どのアプローチが有効なのか、どの程度の品質が必要なのかは、開発するシステムの要件によって様々だと思いますが、どのアプローチも非常に重要で無視できないことがわかります。

次は、各アプローチとシステムのライフサイクルの関連について見ていきます。

システムのライフサイクルと品質に対するアプローチの関連付け

同一システムであったとしても、システムの現在のライフサイクルによって、重視すべきアプローチは変わってきます。Webサービスの立ち上げを例に見てみます。以下の表は品質特性に対するアプローチの考え方をWebサービスの開発に適用した例です。

Webサービスのライフサイクル 重視すべきアプローチ
サービスの立ち上げ時 User-based, Value-based approachが最も大事!そもそも需要があるのか?
サービスがある程度立ち上がった後 Product-based approachに力点をシフト。顧客がつき始めているので、セキュリティ、性能などの品質特性が重要になります。
サービスが立ち上がって安定期に入った後 Manufacturing-based approachも重要に。大量の顧客がいるし、重大なバグ=サービスが即死の可能性があるので、製造業的な品質管理が重要に!

一度立ち上がったサービスは、品質を管理し、バグを出さないことがとても重要であることがわかります。次はバグとコストの関係について見ていきます。

バグが見つかった時のコスト

システムのバグは、見つかるタイミングによって大雑把に以下の2つに分類できます。

  • Internal Failure(内部故障)
    • システムの開発組織内部で見つかったバグ。もちろん、修正にはコストがかかるが、下記の「External Failure(外部故障)」と比較して、相対的に影響が少ない。
  • External Failure(外部故障)
    • システムのリリース後に、開発組織の外部で見つかったバグ。
  • バグの修正、再テストなどのコストに加えて、以下のコストもかかります!
    • 再リリースのコスト
    • 顧客への説明のコスト
    • サポート、ヘルプデスクが問い合わせを捌くコスト
    • ビジネス機会損失のコスト

もちろん、完璧な品質を作りこむのは無理ですし、バグの内容にもよりますが、要するに一度リリースしてしまった後にバグが発覚するコストは非常に高くつくのです。下手をすると、ちょっとしたつまらないバグでも、Webサービスが消し飛びかねません。

UTの勧め

前述の通り、一度リリースしたサービスでは品質を管理することが重要です。もちろん、品質管理の対象、手法ともに様々ですが、コードを書くのが仕事のエンジニアにとっては、ソースコードが主な成果物になるでしょう。そのため、この記事ではソースコードの品質を管理するための実践的な手法である「UT(Unit Test)」を見ていきます。

Unit Testとは何か?

UTの位置づけ

以下の図は有名なソフトウェア開発におけるVモデルと呼ばれるものです。開発プロセス中の要求分析からコーディングまでの活動と、各活動で作成された成果物を確認するテストの対比を表します。

v-model

要するにUT(上記の図における単体テスト)とは、詳細設計の成果物=クラス、関数単位の振る舞いを確認するためのテストになります。

プログラマにとっては一番身近で、結合テストやシステムテスト、受け入れテストと比べて相対的に低コストで実施できます。一般的にはより上流のテストよりも粒度が細かく、UTでの品質を担保できていないとより上流のテストでの品質の確保も難しいでしょう。普通は受け入れテストや結合テストでUT観点のテストは実施しません。

ただ、UTもとにかくやれば良いというものではないので、次は「良いUTの条件」を見ていきます。

良いUTの条件

良いUTは以下の特性を持つ必要があります。

  • 自動化され、繰り返し実行可能性であること
  • 実装が容易であること
  • 一度書いたら、将来にわたって維持管理できること
  • 誰でも簡単に実行できること
  • 高速に実行できること

要するに手動で画面を直接叩いて確認するようなものではなく、専用の自動実行ツールを活用したほうが効率が良いということです。
例えば、有名なテスティング・フレームワークとしてxUnitシリーズ(JUnitやPHPUnit, Nunit)があります。プロトタイプや、作り捨て前提のツールならば、xUnitは必ずしも必要ではありませんが、導入しておくと仕様変更による影響箇所の確認などが非常に楽になります。

UTとカバレッジ

xUnitを実施するときの効果的な指標としてカバレッジがあります。カバレッジはテストの実行によって、テスト対象プログラムをどの程度網羅的に実行できたかどうかを確認するための指標になります。例えば、あるテストの実行の結果、実行行数100行のプログラムのうち、50行しか実行されていなければ網羅率は50%です。(C0の命令網羅の場合)

一般的にカバレッジは以下の3段階があります。

カバレッジのレベル 確認観点
C0(命令網羅) その行が少なくとも一回以上実行されたかどうかを確認します。
C1(分岐網羅) 分岐の結果がtrueの場合とfalseの場合をそれぞれ、少なくとも一回以上実行されたかどうかを確認します。一般的にはここまで確認することが多いです。
C2(条件網羅) 複数の分岐の分岐結果の組み合わせを確認します。

カバレッジはプロブラムの内部構造に着目したホワイトボックス・テストの手法で、外部仕様からみた観点は含まれません。そのため、カバレッジが100%でも安心はできませんが、テストの網羅性を定量的に把握するための指標として使えます。
(「100%だから安心!」ではなくて、「100%でないと問題」という見方をする)

本来ならば、プログラムの外部仕様に着目したブラックボックス・テストを一通り実施してから、どうしてもカバレッジが通らないところだけホワイトボックス・テストを実施したほうが効果的なのですが、効果的なブラックボックス・テストは難しく、定量化しづらいため、カバレッジを重視することが多いようです。

特にスクリプト言語の場合、わずかな記述ミスでスクリプティング・エラーなどが発生するリスクがあるため、とりあえず命令網羅性を見てるだけでも安心感があります。

UTの例

UTの一例として、以下のシンプルな関数を考えてみます。
(関数の中身は省略)

/**
* 引数の数値に1を足してインクリメントした数値を返します。
* @return number
* @throws Exception 引数が数値型以外の時
*/
function increment($num) {

}

上記の関数をテストする場合、少なくとも以下のケースを考える必要があります。

テストケースの内容 確認観点 備考
$numが1のとき 戻り値が数値の「2」か? 同値分割の有効値の中から、代表値として選択。
$numがNumberの最大値のとき 仕様に記載がないので、どう振る舞うべきか、仕様を決める必要がある。
$numが文字列のとき 例外をスローするか? 同値分割の無効値の中から、代表値として選択。
$numがnullのとき 例外をスローするか? 同値分割の無効値の中から、代表値として選択。

備考欄に記載のある「同値分割」というのはブラックボックス・テストの手法の一つであり、入力値をプログラムが受けつける「有効値」と受けつけない「無効値」に分類して、それぞれから代表値のみを用いてテストします。

上記の例でいうと、取りうる値=有効値はNumber型全てなので、数値ならなんでもよいということになります。null、文字列のときはそれぞれ無効値で、分類上はどちらも無効値ですが、nullだけ特別に扱うことが多いようです。また、テストケースの内容から、以下のことが読み取れます。

  • 引数が一つで振る舞いが単純な関数でさえ、数パターンのテストが必要となる
    • 引数が複数あったり、テスト対象が状態を持つオブジェクトの場合、テストが非常に難しくなります。
    • テスト性を考えると、プログラムは極力シンプルにすべきです。
  • プログラムの振る舞い、仕様を理解しないと、効果的なテストを実施することは難しい
    • 上記の例でいうと、引数がNumber型のならば全ての値が有効値で仕様に記載がないですが、プログラムの振る舞いを考えると最大値のみ特殊なケースであることがわかります。
  • 抜け漏れが全くない仕様や、設計、実装するのは不可能なので、プログラムの利用シナリオ(ユースケース)を考えながらテストするのが効果的です。

まとめ

この記事のまとめは以下のとおりです。

  • システムの品質の計測、維持管理は極めて重要
    • 繰り返しになりますが、「テストとはリスク管理の一環としてのシステムの品質のモニタリング活動」であり、システム開発において極めて重要です。
  • 自動化されたUTはバグやデグレを防ぐのに有効
    • xUnitを導入することで効率的にバグやデグレを防止できます。また、カバレッジを測定することで、品質の管理状況を定量的に見ることもできます。
  • 良いUTを書いて継続的に維持管理していくことが極めて重要
    • xUnitのようなツールを効果的に活用して良いUTを書いて、維持管理していくことが非常に重要です。書き捨てではなく、維持管理してこそ意味があります。

参考文献

  • Pragmatic Software Testing: Becoming an Effective and Efficient Test Professional
    • Pragmaticのタイトルのとおり、トップダウンで効果的な手法を学ぶことができるため、とてもためになります。
  • The Art of Unit Testing: With Examples in C#
    • サンプルコードや実践的な手法が解説されており、記述が非常に簡潔で理解しやすいのでお勧めです。
  • Software Product Quality Control
    • ソフトウェアの品質管理の最新の手法が紹介されており、大変参考になります。