寒月記

住みにくいところをどれほどか寛容て

知識ゼロから学ぶソフトウェアテスト【改訂版】 まとめ 前編

 転職が決まり、1月から SREの QA職として働くこととなりました。
テストは未経験であったため、入門書として有名な、高橋寿一さんの「知識ゼロから学ぶソフトウェアテスト【改訂版】」を読みました。
自分の学習のためにも、内容の簡単なまとめ及び所感などを文書化しておこうと思います。
なお、私の経験は本記事執筆時点でテスト経験なし、学生時代は認知心理学教育心理学専攻、ITコンサルとして2年半ほど勤務、開発経験は自主開発で Python少しくらいです。

また、記事中の小見出しは私が勝手につけており、各種表現は超訳・補完しています。
このため、本記事にあるものが必ずしも「知識ゼロから学ぶソフトウェアテスト【改訂版】」に書いてあるわけではありません (この本自体、入門者向けに内容や説明を随分絞ったもののようでした)。
明らかな誤りなどあれば、おそらくそれは本記事を執筆した私に帰属するものです。
もしも誤った理解などあれば、ご指摘・ご教授いただけると嬉しいです。

知識ゼロから学ぶソフトウェアテスト 【改訂版】

知識ゼロから学ぶソフトウェアテスト 【改訂版】

「知識ゼロから学ぶソフトウェアテスト【改訂版】」目次

  1. はじめに
  2. ソフトウェアテストの基本
  3. エンジニアが最もよく使う手法
  4. 探索的テスト
  5. 機能あらざるもののテスト、最難関のテストに挑む
  6. ソフトウェアテストの運用の基本
  7. ソフトウェアテスト品質管理の基本
  8. テストの自動化という悪魔
  9. それでもテストがうまくいかない人へ

1. はじめに

ソフトウェアの「品質が悪い」とは?

 そもそも、ソフトウェアの「品質が悪い」とはどういう状態か。
本書では、「バグによって本来の機能が制限され、そのソフトウェアに期待される機能 (価値) をユーザーに提供できない」 状態と定義しています。

では実際に、重大なバグが見逃されたまま製品が使われると、どうなってしまうのか。
ここでは、簡単なバグが見逃されてしまった結果起きた事故の例として、以下 2例を挙げています。

  • 1996年 ロケット (アリアン5) の爆発事故:浮動小数点数の整数変換時のバッファオーバーフローによるシステムシャットダウンが原因 *1
  • 1999年 NASA火星探索衛星の事故:km - mileの距離単位の混同により、予定より火星に近づきすぎて炎上

 いずれも、ミスとしてはごく単純なものです。
しかし、これらをテストで予め検出し、修正することができていなかったため、高い代償を払うこととなりました (死傷者が出なかったのが不幸中の幸いでした)。
そして、筆者は ソフトウェアテストは、こうした事故を起こす低品質な状態を改善する最もポピュラーな方法 (by Steve McConnel) と続け、ソフトウェアテストの有効性を主張します。

テスト担当者の心得

 では、テスト担当者は実際のテスト実施に当たり、どうすればよいのか。
ここでまず、筆者が考える、テスト担当者の心得 が述べられています。

  • バグを全部見つけるのは不可能 (by Cem Kaner) との前提に立つ
  • バグは偏在する
    • バグの 80%はコードの 20%に存在*2 (Grady, 1992) *3
    • 複雑度の高いプログラムにバグは潜みやすい
  • どの部分にバグが出やすいか、そこにどんなテストを適用すれば十分な品質が得られるか を知ることが重要

 バグはコードに平均的に散らばるのではなく、特定の部分に集中する傾向があるようです。
確かに、実際のコーディングを考えると、シンプルな実装にはバグが入り込む余地が少ないですが、何重にもネストしたり、ループしたり、といった箇所には想定漏れやらが潜みやすそうですね。
ということはつまり、そうした複雑な部分を見つけ、集中的にテストすることで、限られたリソースの中でより効率的にバグの検出ができそうです。

ソフトウェアテストは創造的な仕事

 次に、本書中で最も私の印象に残った言葉が現れます。
それは、ソフトウェアテストは、プログラミングに負けず劣らず困難で創造的な仕事である」 というものです。
 少し横道に逸れますが、私は、前職の配属面談時、当時のマネージャーに開発職志望を伝えたところ、「開発をやるにしてもまず品質保証から始めると良い」と言われたような記憶がぼんやりあります (当時のリソースの問題か、面談による適性判断の結果か、なぜかいずれでもなくコンサル配属になったのですが)。
そうした経験があったため、「品質保証は開発者がまず修めるべき、基礎トレーニング的立ち位置にあたるのかな」などと勝手に想像していました (今なら違うと分かります、ごめんなさい)。

 テストは、淡々と開発成果物を操作し続けてチェックのみをするような「作業」ではありません。
開発成果物の品質を改善して、製品の価値を最小のコストで最大限に引き出す ために、テスト担当者には、以下のようなことを追求し続ける能力が求められます。

  • いかに少ない労力 (テストケース) でより多くのエラーを検知できるか
  • どんな部分にどんなエラーが潜み、どんなテストでそれを検出できるか
  • テスト成果の指標としてのメトリクスは、それを適用した文脈において適切か

単純な作業とは縁遠く、一見して困難なものだと分かります。
顧客提供前の最後の砦という重要な立ち位置にあり、場合によっては開発系の知識すらフル動員して、会社ひいては社会へのリスクを最小限に抑える取り組み がテストだと思います。
このあたりが見えてきたとき、テストというミッションの持つ困難さと同時に、その魅力が強く感じられてきました。

2. ソフトウェアテストの基本 ーホワイトボックステスト

 第二章から、具体的なテスト手法の紹介が始まります。
まずは、ホワイトボックステスト です。

ホワイトボックステストとは

ホワイトボックステスト
プログラム・コードの実装を確認・理解し、実装内容に基づいて行うテスト手法

つまり、「テスト担当者もコードの中身を読んで、実装を把握してテストしよう」 です。
ホワイトボックステストでは、例えば実装を見て「条件分岐の全てのケースをテストする」といったことが可能となります (こうしたテストを「制御フローテスト」と呼びます)。
こうした場合、少なくとも 条件分岐のテスト漏れ はなくなります。究極の具体ベースですね。
また、実装内容ベースのテストという事は、実装内容のうちどれだけをテストでカバーしたか という数値の算出が可能であることも意味します。
例えば、「実装中に存在する全ての if文について、TRUE、FALSEそれぞれの場合をどれだけ確認したか」は、比較的容易に算出できそうです。
これは論理構造に基づく具体的なテストだからできることで、こうした「どれだけの実装内容をテストでカバーしたか」を カバレッジ と呼び、一つのテスト指標となります。

ただし、コードを読めないとホワイトボックステストはできないし、コード量が膨大だと全ての論理構造のテストケース作成は現実的ではなくなります。
筆者も基本的に、100%のカバレッジは非現実的かつコストに見合わないと述べています。
また、あくまで実装内容ベースなので、そもそも仕様が誤っているものや、パフォーマンス・セキュリティなどに関わる、非機能要件のバグは検出できません。
(ホワイトボックステストでは) 実装に書いていないことはテストできない ということです。

なお、実装の総量が小さく、コード内容把握が容易だった開発黎明期と比べ、近年は膨大なコードが前提となりがちなため、後述するブラックボックステストが主流となっていました。
しかし、「短いサイクルで開発 -> テストを回そう」という アジャイル や、「初めからテストコードありきで実装しよう」という テスト駆動設計 (TDD: Test Driven Development) が登場したことで、これら手法とマッチするホワイトボックステスト復権してきているそうです。*4
開発者もテストができ、テスト担当者も開発ができることが求められる、という時代になってきているようです。

3. エンジニアがもっともよく使う手法 ーブラックボックステスト

 次に、先ほど名前が出た、ブラックボックステスト の紹介です。

3.1 ブラックボックステストとは

ブラックボックステスト
プログラム・コードの実装を見ずに、要件・仕様に基づいて行うテスト手法

こちらは、ホワイトボックステストの逆です。
実装はあくまでブラックボックスとし、コードの中身は見ないまま、「どのような要件・仕様に基づいているか」という情報を使ってテストケースを作成 します。

開発の知識も必須ではないし、ホワイトボックステストでは膨大になりがちなテストケースも、かなり集約・代表化できるのでコストが小さくなりそうです。
リソースも節約でき、十分な効果も得られるので、膨大なコードがテスト対象である現在においては、非常に重要視されているテスト手法のようです。
実際、「テストと言えば (主に) ブラックボックステスト」という風潮がしばらくあったようです。

ただし、ホワイトボックステストもそうですが、ブラックボックステストは、あくまで 「要件・仕様が正しい」という前提に基づいて行うテスト です。
前提である要件や仕様が誤っていたら、その誤りはもちろん検出できないし、折角のブラックボックステストも意味のないものになります。

とはいえ、「要件・仕様が正しい」という前提に立つと、とても強力な手法であり、長年支持されてきたものであることは間違いありません。
以下では、ブラックボックステストの具体的な個々の手法について紹介していきます。

3.2 ブラックボックステストの個々の手法

 まず前提として、筆者は、「ソフトウェアの仕事は 入力、出力、計算、データ保存の 4種のみ」 である、と述べています。
すなわち、これら 4種の振る舞い (と非機能要求) をテストすればよい、ということになります。
ブラックボックステストでは以下の様に、それぞれに対するテスト手法がいくつか確立されています。

入力・出力に関するテスト

1. 同値分割法:入力領域を「同値クラス」という部分集合に分割し、同一クラスの入力を等価とみなす手法

分かりやすいように、具体例を一つ。
アルバイトに応募するための Webページがあり、そこに年齢を入力するフォームがあるとします。
ある店舗では、16歳から 65歳までを応募可能としており、それ以外の年齢では入力しても無効となるような要件があります (この年齢基準の是非はおいておき)。
テストの際は、どの年齢をテストケースに含めるべきでしょうか。
16歳から 65歳までが有効なら、それらすべてが「有効」と判断されて次のステップに進めることを確認すべきでしょうか。

......こんなことをやっていたら、この単純な例でも、有効値のテストだけで 16 ~ 65の 50の整数値をテストする必要があります (「何ヶ月か」まで入力できる場合は、更にテストケースが増えてしまいます)。
しかし、16 ~ 65が「いずれも有効とみなされるはず」なら、これら 50の値は等価なはずで、すべての数値をテストする必要性は (ほぼ) ないはずです。
ならば、これら 同じ振る舞いが期待される数値の集合を等価とみなして、テストケースを減らそう、というのが同値分割法の発想です。

この場合、このフォームにおいて年齢 xの同値クラスは、以下の 3種の同値クラスに分割できます。

  • x < 16: 無効
  • 16 <= x <= 65: 有効
  • x > 65: 無効

素直に考えると、無限にあったテストケースを 3つまで減らすことができました。

ただし、この例ではあまり意味がないかもしれませんが、筆者は 「0は特殊な値なので入力可能なら必ずテストケースに含めるべき」 と主張しています。
0 devision問題 *5 などあるので、この発想は確かに重要なことだと思います。
また、有効同値の中で、どの値をテスト値として選択すべきか、という疑問もありますが、これについても、「有効同値では中央値付近など、ユーザー利用頻度の高い数値を使う」 と述べています。

以上により、この例では、例えば

年齢 x 期待する結果
ケース1 0 無効
ケース2 15 無効
ケース3 20 有効
ケース4 80 無効

といった、意味のある現実的な数にテストケースを減らすことが可能です。
なお、「負の値はテストしなくてよいのか」「現実的にあり得ない値 (x = 256など) は」「数値だけでなく文字列など入力したらどうなるのか」などの疑問がわく方もいると思います (私も湧きました)。

この疑問については、まず「同値分割法にも強弱があり、どこまで厳密にやるかはコストパフォーマンス次第」という答があります。
厳密にやろうと思えばどこまでも詳しくできるので、リソースと相談し、どこまでやるべきか、そこで省略したテスト内容でバグが出た際、どこまでの問題が起き得るか を総合判断して決定することとなりそうです。

次に、「フォームが受け付けるデータ型のバリデーションなどは、例えば Webフレームワーク等を使った実装の場合は、それを信じて良い」という答もあります。
例えば Java Spring FrameworkRuby on Railsなど、こうしたフレームワークを使った開発の場合は、フレームワーク側である程度のバリデーション機能を備えてくれていることが多いです。
その機能を使えば、それらフレームワークを開発している方たちが既にその機能のテストは通してくれているわけで、その機能まで疑ってわざわざ改めてテストすることは、通常ないと言えます。
フレームワークなどを使うと、開発だけでなくテストにも良い影響があるようですね (ただし、これまた「開発者が正しくフレームワークのバリデーション機能を使っている」という前提が必要となります。結局どこまでやるかは、製品に問題が起きたときの影響度や、リソースの余力により都度変化する、ということとなります)。

2. 境界値分析法:条件文が必要となる箇所 = 境界値付近にはバグが潜みやすいため、境界値を詳しくテストする手法

境界値分析法の中でも、本書では On-Offポイント法 を取り上げています。
これは、「異なる処理が行われる一番近い二点をテストする」 という手法となります。
先ほど取り上げた年齢入力フォームの例では、以下がテスト対象となります。

  • 境界1
    • x = 15: 無効
    • x = 16: 有効
  • 境界2
    • x = 65: 有効
    • x = 66: 無効

さて、「境界値付近にはバグが潜みやすい」とは、どういうことでしょうか。
これも実装を考えてみると、ピンときます。
上の例では、実際のコードは以下のようになっていると想像されます (Pythonで例示、処理は適当)。

if x < 16:
  return '年齢が若すぎます' # 無効
elif x >= 16 and x < 66:
  return '次に進んでください' # 有効
elif x >= 66:
  return '年齢が高すぎます' # 無効

こうした比較演算子の利用時には、"<" と "<="、">" と ">=" の混同などが起き得ます。
これを 閉包関係のバグ と呼ぶのですが、境界値分析法では、この閉包関係のバグを検出することが可能となります。

具体的に同値分析法と境界値分析法をどう使えばよいのか

これまで紹介した同値分析法と境界値分析法ですが、では実際にどう使えばよいのでしょうか。
厳密に利用する場合、「同値分析表」「境界値分析表」というものを作成して実施します。
専門用語のように見えますが、要は「表を作成して、抜け漏れなくテストできるようにしましょう」ということです。

とは言え、毎回毎回表を作るのも却ってコストがかかってしまう、という場合もあります。
そんな時に、筆者は経験則から、「良いデータと悪いデータをテストするのが良い」 と述べています。
「良いデータ」「悪いデータ」とは、以下のようなものです。 - 良いデータ - ユーザーがよく使いそうなデータ - 有効値の上限 - 有効値の下限 - ゼロ - 悪いデータ - 非常に小さなデータ - 非常に大きなデータ - 長いデータ - 無効なデータ

「表を漏れなく作る」は大変そうですが、これならある程度どの場面でもできそうですね。
表作成に時間をかけてしまうくらいなら、はじめはこんな感じで行い、徐々に先達を真似しつつ教わりつつが良いのではないでしょうか。

続いて、次の手法の紹介です。

3. ディシジョンテーブルテスト:すべての入力 (条件) の組み合わせ (ルール) 及び、それぞれのルールが満たされたときの動作・出力 (アクション) を明記した表を利用するテスト手法

定義だけ見ると複雑ですが、「複数の 入力が可能な場合、それらの値の組み合わせと期待される結果 を漏れなく表に書き、これを利用するテスト」です (まだ複雑かもしれません)。

具体例は調べれば出てくるので、はじめは上の言い替えた部分程度の理解で良いのではと思います。
筆者は、このテストを「項目が少なく、複雑な動きをするソフトに有効」としています。
仮に 10項目の入力可能な箇所が同時にあると、10項目それぞれ TRUE or FALSEの 2値しか取り得ないとしても、210 = 1024通りも表に書き起こさなければいけなくなりますね......
しかし、項目が少ない場合は確かに漏れなくテストできるので、強力そうです。

4. 状態遷移テスト:状態 (state) および遷移 (transition) をモデル化して、期待される状態の遷移をテストする手法

段々私の気力も尽きてきましたので、ここからはさらに記載内容を要点のみに絞っています。
ただでさえこのあたりのテストの定義は、少々複雑で読んでいてわかりづらかったもので。。
まず、用語の説明をします。 - 状態 (state):システムが 1つ以上のイベントを待っている状況 - 遷移 (transition):イベントによってある状態から別の状態へ変わること

システムは、大抵何らかのイベントを待っている状態にあります。
年齢を入力して「次へ」ボタンを押すというイベントや、確認画面で入力内容を目視確認し、「確認して送信」ボタンを押す、というイベントなどが具体例です。
また、ある場面では受け付けていたイベントや入力が、異なる画面 (状態) に移動すると既に受け付けなくなっており、そこではまた別のイベントや入力を受け付けるようになっている、というように、状態は遷移 (変化) します (必ずしも画面遷移 = 状態遷移ではないことに注意)。
いつでも年齢入力を受け付けているフォームは、普通ないですよね。

こんな風に、システムが「状態」を持ち、それはイベントによって「遷移」する、そしてこの状態遷移が想定通りに正しく動く、ということを確かめる のが状態遷移テストです。
状態遷移図 (作成ツールもあるらしい) や状態遷移表と呼ばれるものを作成して、テストケースを作成するようです。

これまた、状態の数が多い場合は図表の作成が非現実的なので、かえってコストがかかってしまいます。
しかし、限られた状態しか持たないシステムの状態遷移を確認するには、「状態遷移をモデル化したテスト」なのですから間違いなく適した手法でしょう。

また、筆者は、状態遷移テストは特に、GUIソフトウェア、オブジェクト指向ソフトウェア、通信プロトコル のテストに向いていると述べています。
詳細な説明がなかったのですが、GUIソフトウェアは分かります。
見た目にも状態の変化が分かりやすいし適切そうです。
オブジェクト指向ソフトウェアは、クラスごとに役割が分かれていて、その意味で状態の遷移が分かりやすいから、とかでしょうか?
通信プロトコルは......ちょっとわかりませんでした。
ネイティブアプリよりも、例えば Webアプリの様に通信プロトコルを使っているものは、リクエストの送信 -> レスポンスの受信のように状態遷移がはっきりしているため、テストに向いている、ということでしょうか?
研鑽を積んで理解できたら、追記なりしようと思います。。

5. ランダムテスト:計画を考えずに入力や操作を行いテストする

今までで一番分かりやすいですね。
私も前職のドッグフーディングで、勘に頼って操作していくつかのバグを報告したことならあります!(それはテストと呼べるかはさておき)

ある程度バグを発見できるそうですが、筆者曰く、「これをやるくらいならちゃんとテストケースを書いてテストした方が良い」 とのことです。
それはそうですよね。
これでバグを発見できるとしても、ちゃんとしたテストケースの方が網羅性は高いだろうし、「バグは偏在する」ので、ランダムにやるよりは対象を絞った方が効率良さそうです。

少なくとも、テスト担当者として選択すべき方法ではなく、これをメインに据えてやるときは、ある意味「テストケースが作れなかった」という敗北宣言のようなものなのではないでしょうか。

ちなみに、ランダムテストは「アドホックテスト」「モンキーテスト」など様々な別名があるようですが、やはりランダムテストが分かりやすいですね。

第三章まとめ

 第三章は特にボリュームが多いのですが、それだけブラックボックステストが重要視されていることの表れと考えられます。
筆者も、ブラックボックステストは最も重要で、最も時間を費やす、最も簡単なテスト」 と述べています。
しかし、これだけ色々な手法があると、本書の想定読者であるテスト初心者には、「結局どうすればいいのか」が分かりづらいです。
そこで、筆者は以下のような推奨テスト手順を示してくれています。

  • 筆者の推奨手順
    1. 入力ダイアログがあれば 境界値テスト
    2. 複数の入力ダイアログがあれば ディシジョンテーブルテスト
    3. ダイアログボックスの遷移があれば 状態遷移テスト

どのテスト手法をどう組み合わせていいかわからない初心者としては、こうして一つのテンプレートを示してくれるのは助かりますね。
そして、上記手順を踏んでもなおバグが出る場合は、以下の可能性があるようです。

前編まとめ

 当初の想定以上に長くなったので、記事をこのあたりで分割しようと思います。
ここまでの説明で、具体的テスト手法として、ホワイトボックステストブラックボックステストとが説明されていました。
テスト未経験者である私のここまでの所感としては、「ブラックボックステストが重要視されているんだな」という感覚です。
ただし、名前を見るだけだと紛らわしいかもしれませんが、説明を見ると分かる通り、これらは決して対立する手法でなく、両方を目的に応じて、適切に使い分ける・併用することが重要です。
それぞれに目的が異なるので、現実的なテストを行う際には、「どちらかのみを使っていればよい」というものでもありません (理想的にはホワイトボックステストですべてを網羅できるとよいのかもしれませんが、現実的に不可能ですよね)。

しかし、何より、そうした個々の手法の説明よりも、ソフトウェアテストは開発に負けず劣らず創造的で困難な仕事である」 という言葉が印象に残りました。
テストは、単なる作業とは縁遠い、論理的思考・批判的思考をフルに活用して取り組むもののようで、刺激的・挑戦的な分野のようです。
正直、今回の転職の前後でテストというものをちゃんと調べたことで、テストへの印象が変わりました (先達の方々済みません)。

では、前編はここまでにして、後日後編に続けようと思います。
なお、後編はさらに要約する予定なので、ここまでのボリュームにはならないはず......
お付き合いいただきありがとうございました、繰り返しとなりますが誤りなども多分にある可能性があるので、ご指摘いただけると学びになり大変嬉しいです。

なお、本記事執筆にあたっては、Web上の情報や、たまたま古本屋で出会った「はじめて学ぶ ソフトウェアのテスト技法」等も参考にしています。
個人的には「初めて学ぶ~」が肌に合っていて、後編では、本書とこの本の比較も少し触れられればと思っています (まだ時間がなくて読めてませんが)。

はじめて学ぶソフトウェアのテスト技法

はじめて学ぶソフトウェアのテスト技法

*1:最近のプログラミング言語だけ触ってるとイメージし辛いですが、型変換が走った際に、変数に割り当てたメモリ空間から値が溢れてエラーを起こしてしまった

*2:パレートの法則みたいですね

*3:Grady, R. B. (1992). Practical Software Metrics for Project management and Process Improvement. Hewlett-Packard Professional Books.

*4:アジャイルテスト駆動開発については、本書には詳しい説明はありませんでした。筆者も、別途専門のものの参照を勧めています

*5:大抵のシステムでは 0で割ると割り算の答えが無限大になるため、ちゃんとエラー処理していないとバグります