読者です 読者をやめる 読者になる 読者になる

三流プログラマが脱三流するために書くブログ

PHP, オブジェクト指向プログラミング, デザインパターン, リファクタリング, DDD, 関数プログラミング, etc.

6.「知識ゼロから学ぶソフトウェアテスト 【改訂版】」を読んだ (2) 〜 探索的テストと非機能要求のテスト

読書 テスト

前回の続きです。

前回はホワイトボックステストブラックボックステストについて読みました。今回は探索的テストと非機能要求のテストについてです。

探索的テスト

Wikipedia.orgには、「探索的テスト(Exploratory Testing)はテスト手法の一つで、ソフトウェア機能を学習しながら、テスト設計とテスト実行を同時に行う。(中略)」と説明があります。

Wikipedia の説明を引用しつつ、著者による定義も載せています。

  • すべてのテストを実施するのは時間的制約から望むべくもない。そして行ったとしてもすべてのバグは見つからない。それならいちいちテストケースを書く代わりに、製品を学習したうえでテスト設計して実行してバグ報告を並行してやっちゃえば手っ取り早い。それは新しいテストのスタイルであり、ただ単にテストケースを書かないだけで昔からテストでやっていることと同じなんだから、探索テストで効率良く昔からのテストケースベースのやり方と同等以上の成果を出そう!
  • もちろんそういった作業を一気にやっちゃうのだから、素人には無理なわけで、プロとして成熟した一個人のテスト担当者に責任をもってやらせる!それなら絶対まぬけなバグが後から見つかったりしない

注目すべき点は、探索的テストはテストケースを用いたテストよりも効率的だというデータです。

"Juha Itkonen, Do test cases really matter? An experiment comparing test case based and exploratory testing, 2008" という論文のグラフを引用しています (feature を「フューチャー」と書いてあって著者に対する信頼性がやや下がりました、もったいない…)。

効率的といっても、このグラフは発見できた不具合の数のみみたいなので、一概に言えないところがありますが…

「テスト設計・ケース作成を早い段階で行う」デメリット

早い段階でテストケースを書こうとしても、ターゲットのソフトウェアがないため、手探りでやっているケースが多いことです。(中略) 早過ぎるテストケース作成は著しいソフトウェアテストの工数増大をもたらします。

プロジェクトによってはテスト設計を早い段階で行うものもあるかと思いますし、TDD でやるなら、テストコードはプロダクションコードと並行して書かれます。

探索的テストの優位性は、あらかじめ策定された要求仕様に基づいてテスト計画を立て、テストケースを作成するやり方では、実際に完成した製品と乖離してしまう可能性が高く、計画通りにテストをしたとしても不具合が多く残ってしまうことがあるので、製品が完成してから (とは書いてなくて、どの時点でテストを開始するかは明記されていませんでした) 着手し、機能を学習しながらテストの精度を上げていくことで、不具合が減らせる、ということのようです。

個人的には、テストコードはプロダクションコードと並行してメンテナンスされ、要求仕様が変わればテストコードを変えてからプロダクションコードを変える、というやり方の方が、インクリメンタルな開発には向いてるのかな、と思いますし、このようなある意味職人芸的なアプローチでは、属人的なテストになりがちなので、そこは課題かなと思います。

「同じテストケースをたくさん実行する」デメリット

筆者の経験上、初期に書かれたそれほど練られていないテストケースを繰り返すことにはあまり意味がありません。逆に必要なことは、 - テストを実行しながら、どこかほかの部分に問題がないかを考え、そこをテストすることである - ソフトウェアで弱いところを見つけたら、そこに重点を置き、その部分を十分にテストする です。

問題は、「初期に書かれたそれほど練られていないテストケース」を放置してしまうことなのではないかと思っていて、要求仕様を満たすテストであれば何度実行しても問題はないかと思いますが、たしかに、UI (ウェブアプリケーションなら JS 周りで、この辺は疎かにしがちです…) の複雑な画面のテストでは、ある条件でしか発生しない不具合も多いので、自動テストでテストケースが漏れてしまうことを考えて、人力でのテストは不具合の多い箇所を重点的にやるのはいいと思います (それを自動テストに反映できればなおいいのですが…)。

探索的テストの判定基準

探索的テストを行うにあたっては、判定基準を設ける必要があって、Windows アプリケーションの例が載っています (一部記述を省略しています)。

機能性

Pass基準: 各々の主機能がテストされ、ユーザーが望む操作ができ、かつそのアウトプットが正しいものである

Fail基準: 一つ以上の主機能の実装の不備があり、ユーザーがその目的を達成できない

安定性

Pass基準: OSを不安定にさせない、ハングアップやクラッシュやデータの損失をしない

Fail基準: OSが機能不全に陥ることがある、ハングアップやクラッシュやデータの損失が起こる

まぁ、読むと当たり前のことなので、これをわざわざ「判定基準」として明文化することの意義がいまいち分かりません。この辺はもうちょっと深掘りしたいところです。

探索的テストのタスク実行

実行フェーズは以下の5つに分かれます。

  1. ターゲットソフトウェアを決める
  2. 機能をリストアップする
  3. 弱いエリアを見つける
  4. 各機能のテスト及びバグの記録
  5. 継続的なテストの実行

ポイントは、「一つ一つのテストケースは書きません。(中略) テストを行ううえでテストケースを書くことに時間がかかり、テストの実行自体の時間が削られてしまうということは避けなければなりません」ということでしょうか。

不具合自体は記録するけれども、テストケースは残さない、というのは一見場当たり的のようにも感じますが、時間優先ということみたいです。

非機能要求のテスト

非機能要求のテストも、時間やコストの兼ね合いで疎かになりがちなので、いちどしっかり基礎知識を刷り込んでおきたいです。

一般論としての非機能要求は多岐に渡るので省略し、本書では以下の3つに絞って解説しています。

  • パフォーマンス (Efficient)
  • 機密性 (Security)
  • 信頼性 (Reliability)

(筆者注: Efficient と記載がありますが Efficiency の間違いと思われます)

参照: 非機能要求とISO9126 | オブジェクトの広場

パフォーマンステスト

パフォーマンステストの注意点

パフォーマンステストの注意点を引用します。

  • パフォーマンスの定義は明確に
  • 要求定義通りのテストケースを書かない
  • パフォーマンステストは後まわしにしない

パフォーマンスの定義は明確に

明確な数値で定義すること。たとえば、20MB のデータを 10秒以内に処理すること、など。この場合、境界値だけでなく、1MB や 21MB でもテストすること (前者で 10秒近くかかったり、後者で 30秒以上かかったりすれば、いずれも不具合として扱う)。

要求定義通りのテストケースを書かない

テストはバグを見つける作業なので、要求定義を逸脱するようなテストケースを設計すること。

パフォーマンステストは後まわしにしない

パフォーマンステストで発見された不具合は、設計のやり直しが必要になるようなケースもあるので、プログラムが動くようになった時点で行うこと。

パフォーマンステストのステップ

Step1: アーキテクチャバリデーション Step2: パフォーマンスベンチマーク Step3: パフォーマンス回帰テスト Step4: パフォーマンスチューニング、アクセプタンステスト Step5: 24×7 パフォーマンスモニター

Step1: アーキテクチャバリデーション

ソフトウェアがアーキテクチャ的に十分なパフォーマンスを発揮できているかをチェックします。 (中略) 机上でチェックします。もしくは非常に小さいプロトタイプソフトウェアを作って (中略) 測ります。

Step2: パフォーマンスベンチマーク

実際に開発されたソフトウェアのテストを行います。パフォーマンステストができる状態のソフトウェアが準備できたらすぐやるのが基本です。

Step3: パフォーマンス回帰テスト

プログラムが開発途上で常に変更されている状態で行います。変更したことによってパフォーマンス低下を招かないように定期的にチェックします。

Step4: パフォーマンスチューニング、アクセプタンステスト

最終製品が要求定義に定められたパフォーマンスを出しているかをチェックします。

Step5: 24×7 パフォーマンスモニター

顧客のデータを完全に利用できないことがあります。その場合は顧客の使用するデータに近いダミーデータでテストします。

セキュリティテスト

70年代からセキュリティテストは研究されてきたのですが、これぞという決定打のような手法はいまだにありません。(中略) 1日8時間労働のサラリーマン開発者がいくらセキュリティのしっかりしたコードを書いているといっても、1日24時間無給で楽しくハッキングしている人とでは勝負は見えています。(中略) 残念ながら、本書執筆時点では「セキュリティテスト」という分野は存在しません。そして現場の人間は困っています。

ええーっ Σ(゚Д゚)

まぁ、ソフトウェアテスト自体、不具合をゼロにはできないものなので、それはセキュリティテストも同じ、と言っているに過ぎないですよね、いかに致命的な不具合を出さないか、ということなのかなと思います。

それでも、著者はセキュリティ問題の特徴を二つ挙げています。

  • プログラム構造の不備を突き、そのプログラムの制御を奪ったり、不能にしたりする
  • プログラムのデータの不備を突き、そのデータを改ざんする

それを踏まえた上で、以下の手順で行う「モジュール指向のセキュリティテスト」を紹介しています。

  1. アプリケーションを基本モジュールに分解する
  2. モジュールに対する入力を定義する
  3. その入力によってモジュールの脆弱性が露見するかどうかを判断する
  4. 脆弱性があると考えられるモジュールにさまざまなデータを入力する

入力の中には環境変数も含めなければいけません。

ウェブアプリケーションフレームワークを使っていれば、入力に対してはエスケープ処理が施されているので、SQLインジェクションXSS (クロスサイトスクリプティング) などは回避しやすいようになっていますが、エスケープ処理をしないで入力を受け取る方法もあるので、開発者が意図せずに脆弱性を埋め込んでしまう可能性はあります。

ともするとフレームワークの仕組みに頼ってチェックを疎かにしがちなので、外部から入力される値のチェックはコードレビューなどでチェックしていきたいと思います (私が普段使っている PHP だと、$_POST の値を直接取得せず、フレームワークが提供しているインタフェースを使って取得するのが当たり前になってきてはいますが、たまにエスケープせずに使ってるのを見かけるので、気を緩めることのないようにしないといけないですね。

信頼性のテスト

ソフトウェアの信頼性を測るためには信頼度成長曲線なるものを書かねばなりません。

ウェブアプリケーションの場合は、ウェブサーバーやデータベースサーバなど、他のソフトウェアと連動して動くことがほとんどなので、それらも含めて、どのくらいの頻度で故障 (ハングアップやクラッシュ) するか、というのを測るわけですが、枯れたソフトウェアであればあまり神経質になる必要はないかと思います。ミッション・クリティカルなソフトウェアであれば、サーバーは二重化するでしょうし。

ということで、ざっと読んだ上で、本項の引用は上の一行だけにしておきます。

AWSなどのクラウドの登場で、システムの多重化が格段に楽になりました。いい時代になりましたね。

まとめ

  • 探索的テストはテストケースベーステストより効率が「多少」いい
  • 探索的テストでは、不具合の多い箇所を重点的にテストするのがいい
  • 非機能要求のテストで最も重要なのは、パフォーマンス、機密性、信頼性
  • パフォーマンステストは早めに実施すること
  • セキュリティテストでは入力データに注意すること
  • 信頼性テストは (一般的なウェブアプリケーションにおいては) それほど重要でなくなっている気がする

次回テストについて書くときは、ヘッドレスブラウザを使った機能テストについて書けたらいいなと思っております。