ソフトウェアの品質を学びまくる

ソフトウェアの品質、ソフトウェアテストなどについて学んだことを記録するブログです。

勝手版 #ソフトウェアバグの7原則 Ver.1.0をリリースしました。

 ちょっと前の話、「ソフトウェアテストの7原則」のオマージュとして、「ソフトウェアバグの7原則」というのを思いつきました。
 キッカケは冗談だったのですが、思いのほかいろんなアイデアをいただきまして、「ならちゃんとやろう!」として、「ソフトウェアバグ*1の7原則 Ver.1.0.0」として、2022年7月4日に公開したのでした。

 急ごしらえで、強引に7つに統合したこともあり、違和感のあるもの、洗練されていない部分もあると思いますが、眺めてみてもらえると幸いです。

ソフトウェアバグの7原則 Ver.1.0.0 (2022/7/4)

ソフトウェアバグの7原則 Ver.1.0.0

前提

  • ここで「原則」は、「いろいろな状況で当てはまるパターン」のこととする。
  • 「テストの7原則」には「欠陥の集合」についての言及がある*2ので、ここでは「個々の欠陥」を扱う。

1. テストしなければバグは見つからない

バグは、テスト*3をして初めて見つかります。知識だけ増やしてもバグは出ないし、怪しい場所を避ければそこにあるバグは見つかりません。テスト、テスト!

 テストで見つからなったバグは、永遠に眠ったままか・・・、本番環境で見つかることになる!
 ので、「テストでしか見つからない」というと語弊はあるのですが、ここで言いたいのは「勉強や検討を一通り済ませたら、早々にホンモノ使ってテストしようぜ!」みたいな勢いを表現したものです。

2. バグが単体で存在するとは限らない

1つのバグを見つけたら、近い箇所に似たバグがきっと見つかります。あるバグが他のバグの発現を隠していて、直したと思ったらまた問題、となることもあります。

 この項目は、いただいた複数のアイデアを強引に1つに押し込んじゃっています。。
 前半は、いわゆる「類似不良」を意図していました。ある間違いに起因するバグがあれば、同じような間違いを他にもしていることが多いというお話です。
 後半は、あるバグを直してその部分が正しく動いたことによって、他のバグにぶつかるといった状況のことを意図しています。修正範囲や影響確認の見誤りによる問題ですね。

3. 再現できないバグはない

バグは、単純・単一の条件で発生するとは限りません。いくら試しても再現しないこともあります。それでもやはりバグには、何らかの発生条件があるはずです。

 これは、『ソフトウェアテスト 293の鉄則』の鉄則077からの引用として、いただいたアイデアです。

 もちろん現実には、時間・工数・能力の制約があったり、真にレアレアな発生条件だったりして、「再現できなかった・・・」となることはあるでしょう。ここでは、「現象には原因があるはずで、その特定をサクッとあきらめてはならん」という教えとして取り上げています。

 ついでに、こんなツイートを思い出しました。

4. バグの重要度は状況に依存する

開発者目線では簡単なバグでも、運用上大きな影響を持つかもしれません。ある時点では軽くても、時間が経って重大なものに変わることもあります。

 チケットに、「重要度」というフィールドを持たせているチームは多いと思います。この重要度を、開発者としての立場だけで判定してしまうと、改修の優先度付けを誤ってしまうかもしれません。「ユーザ影響」ってやつをしっかり考えて、ユーザ目線での「重要度」を決めるのがよいでしょう。
 ちなみに、「テストのブロッカーになるからすぐに直す必要がある」といったものは、「重要度」ではなく「緊急度」「優先度」みたいな別のフィールドにするのもいいと思います。

5. 修正の大きさとバグの大きさは関係ない

「簡単な修正」「軽微なコードフィックス」だからといって、その作業におけるミスのもたらすバグの影響が「簡単」「軽微」とは限りません。

 「バグの大きさって何?」というご指摘をいただきました。確かによくないタイトルです。
 言いたかったことは上の通りで、簡単な修正だと気が緩みかねないので、イマシメ的な項目です。
 いまさらですが、#4と若干重複していますね。

6. 「レア」のはずのバグが本番で牙をむく

テスト環境よりも、現実の環境の方が多様で複雑です。「レアケース」だったはずの発生条件が、本番環境であっさり起きることもあります。

 若いころに、「本番環境は魔窟」とか、「レアと思ってても、ユーザが多ければだいたい起きる」とか、さんざん言われました。そういう意味では、バグチケットにありがちな「発生頻度」というフィールドは、「レア=当面直さなくてもいい」とミスリードしてしまうリスクがあります。
 テストでいろいろな条件を尽くしたつもりでも、本番環境におけるシステム構成、タイミング、運用期間、ユーザ操作は、それを軽々と越えてくると考えておくのがよいのでしょう。

 なお、発生頻度については、以下のような記事を書きました。

www.kzsuzuki.com

7. 「運用でカバー」ではカバーしきれない

プログラムを直すのは大変なのでマニュアルに明記して、運用チームやユーザに回避してもらう・・・。多くのチームがそう期待し、結局そのバグを踏みます。

 UXで有名なヤコブ・ニールセンさんの言葉に、こんなものがあります。

コンピュータ上のドキュメントに関するニールセンの第一法則: 誰も読まない

 マニュアルにいくらデカデカと「注意事項」を書いても、それが読まれるとは限りません。読まずに問題が起きてしまった場合、「いやマニュアルに書いてあるじゃないですか!」と言い張れるものか・・・言い張ったとして、それが長期的にいい結果につながるか・・・。
 暫定措置としてどうしても必要な場合はありますが、「恒久的に運用でカバー」は、いい結果をもたらさないでしょう。

?. バグと認めなければバグではない

バグと認めたら負け、なんて・・・ そんなこと言う人はいません・・・よね?

 これは、この原則の話のキッカケとなった、ネタでございます。
 バグじゃない、仕様だ!

おわりに

 あらためて「解説」めいたものをつけてみると、やっぱりまだまだ取捨選択・洗練が必要かなーとも思います。
 Ver.2.0を作りたい方、あるいは自分版を作ってみたい方、ぜひ検討してみてくださいませ!

*1:ISTQB用語集では、「バグ」(bug)という項目はなく、「欠陥」(defect)の同義語として現れる。ただ、「バグ」という言葉は伝わりやすいので、ここではこの言葉を使っている。

*2:原則4「欠陥の偏在」、原則5「殺虫剤のパラドックス」、原則7「バグゼロの落とし穴」

*3:ここでいう「テスト」には、レビュー、ツールによる自動テストなども含む。

JaSST nano vol.15で『殺虫剤のパラドックスの真実』というタイトルで発表しました #jasstnano

 JaSST nanoで発表しました。

jasst-nano.connpass.com

 ソフトウェアテストを学んでいると必ず出会う「殺虫剤のパラドックス」、何がパラドックスなの?と思ってつぶやいていた時に、辰巳さんにコメントをいただいたのがキッカケです。

 そこでWikipediaを調べてみると、ぜーんぜん思いもしなかったような話だったのです。

en.wikipedia.org

 害虫が殺虫剤への耐性を獲得するなんて話は出てこなくって、殺虫剤が害虫の捕食者まで殺してしまうので、結果として害虫は増えてしまう。これをパラドックス・逆説と呼んでいるのでした。それがシンプルな微分方程式で示されています。

 見た目はいかついですが、何のことはない、やっていることはほぼ連立一次方程式です。

 ともあれ、みなさんの今後の仕事の役には立たないと思います。まあ、こういうのもいいんじゃないかっていう提案です。
 よければ、発表資料もご覧ください。
 テクニカル性は超低いのでハードル下げの一助にはなったと思いますが、スライド作る工数は無駄にかかってます。。

speakerdeck.com

ISTQBの「性能テスト担当者」シラバス日本語版を読んでみた - その2

 今回は、1.2で扱われている、性能テストのテストタイプについての記事です。前回の記事はコチラ
 性能テストにはさまざまなテストタイプがあるので、1つのデファクト・スタンダードとしてISTQBが整理してくれているのはありがたいことです。
 またこの部分については、翻訳者でもある湯本さんがブログを公開されています。

note.com

 わたしはこのテストタイプたちを、また違う軸で捉えていたので、ちょっと整理してみました。
 縦軸はテストの目的で、実行条件が想定「以下」「内」なのか、想定「以上」「外」なのか。
 横軸は注目している時間範囲の長さを「短」「中」「長」に分けているます。

 このマトリクスに、シラバスにあるテストタイプを乗せると、こんな感じ。
 ★は、このシラバスには出てこないものです。

目的 時間範囲: 短 時間範囲: 中 時間範囲: 長
想定を超えた条件において、
どのようにふるまうかを確認する
スパイクテスト
コンカレンシーテスト
ストレステスト
キャパシティテスト
拡張性テスト
★加速試験
想定内の条件において、
要件通りにふるまえるかどうかを確認する
同上 負荷テスト 耐久テスト
★ソークテスト

 それぞれのセルを見ていきましょう。

時間「中」×想定「内」

 まずここが基本。
 「想定として定義された負荷条件に対し、どのような性能を示すか」という負荷テストが入ります。

時間「中」×想定「外」

 このセルには、ストレステスト、キャパシティテスト、拡張性テストの3つが入ると考えます。なぜ3つも入るのかというと、目的が異なるからです。目的を無視しているのが、このマトリクスの大きな欠点と言えるでしょう・・・。

 ストレステストでは、負荷が想定以上になった場合のふるまいを検証するものです*1。負荷テストのわかりやすい延長上にあります。
 キャパシティテストと拡張性テストの違いは、わたしにはよくわかりませんでした。ともに、「これからユーザや負荷が増えていったとして、どこまでなら性能要件を守れるか」と言っているように感じます*2

時間「短」

 この列は、想定「外」「内」は分けずに、スパイクテストとコンカレンシーテストを入れています。
 この2つは似ているようにも思えますが、別のものです。

 スパイクテストの特徴は、「突然」。次第に負荷が増えてピークを打ち、次第に下がっていくのではなく、いきなりガンと上がってすぐに収まるという、Twitterでいうバルス的な事象に耐えられるかに注意します。シラバスには「その後、定常状態に戻る能力」とも書かれており、「素に戻る」ことができるかもポイントです。

 一方コンカレンシーテストは、ある短い一定時間の間に「特定のアクションが同時に発生する状況」Tをテストするもの。
 各実行の操作対象が同一のものだったり、実行のために使うリソースが共有されていたりする場合に起きるような、逆にいうと単発の実行では起きない・起きづらい問題が、コンカレンシーテストで見つかることがあります。

 全然現実的なじゃなさそうですが、ある銀行口座に対する入金処理についてテストするとすると、

  • スパイクテスト: 1人が1分間に1000回の入金を行う
  • コンカレンシーテスト: 100人が1分間に1回ずつ入金を行う

といった感じでしょうか。

時間「長」×想定「内」

 ここには、耐久テストが入ります。単発では性能がよくても、時間が経つに連れて急激に悪化していくかもしれません。短時間では発見しづらい問題を、耐久テストで見つけられることがあります。
 「ロングランテスト」という別名に加え、わたしは「ヒートラン」という言葉も(方言的に)使っていたのですが、これは機械・電子機器の文脈で使われる、まったく違う意味の言葉のようですね・・・。

ヒートラン 【heat run】 エージング / バーンイン
ヒートランとは、機械や電子機器などの出荷前に行われる稼動試験、または、使用開始前に行われる「慣らし運転」のこと。
IT用語辞典より

 なお、秋山さんの以下の記事では、 「ソークテスト」 が説明されています。

note.com

ソークテストなら、長時間連続して、実際の利用環境であり得る嫌な負荷をかけ続けるテストということを押さえておきましょう。

 この説明を見る限り、ソークテストも「長」×「内」に入れておくのがよさそうです。

時間「長」×要件「外」

 シラバスにはここに当たるテストタイプはなさそうですが、「加速試験」(これも方言)が、一応これにあたるかもしれません。

 耐久テストにおいて、たとえば「1ヶ月稼働後の様子を確認したい!」といっても、なかなかできるものではありません。そこで、「想定される負荷の周期」を1/10にすることで、3日間で1ヶ月分を模擬する。雑にいうと、加速テストとはこんな発想のテストです。
 「加速した場合の動作を確認する」ことが目的ではなく、あくまでも「耐久テストを効率的に行う」ことが目的なので、テストタイプとはちょっと違いますね。
 いずれにせよ、それが本当に「模擬」と言えるかどうかは十分な吟味が必要です。

おわり

 以上、性能テストのテストタイプを、目的と時間範囲のマトリクスで分類してみました。
 正直、いくばくかの無理矢理感はありますが、分類してみることで理解が進むこともあるかもしれないので、ヨシとします。

Top speed

*1:シラバスでは、「リソースの可用性が低下した場合のシステムの処理能力を評価するためにも使用できる」とあります。障害時の「片肺運転」「縮退動作」中の性能評価ですね。

*2:湯本さんの記事では、拡張性テストは想定の範囲「内」、キャパシティテストは「外」で、後者とストレステストの仲間と説明されています。

ISTQBの「性能テスト担当者」シラバス日本語版を読んでみた - その1

 JSTQBから、性能テストのシラバスの翻訳が出ました。
 ついつい当たり前に思ってしまいがちですが、テストの知識のデファクト・スタンダードといえるISTQBのシラバスが日本語で読めるというのは、本当にありがたいことですね。訳者のみなさまに感謝。

jstqb.jp

 いわゆる「非機能テスト」というものはそれぞれに独特の難しさがあり、性能テストもその例に漏れません。このシラバスは、性能テストにおいて何を考慮し、何をしなければいけないかについて、全体感を与えてくれます。
 この記事では、シラバスを読みながら自分が考えたこと・理解した(気になっている)ことについて書いていきます。

「性能」という品質特性

 「性能」(または「性能効率性」)という言葉について、あらためてISO 25010を見てみると、以下のように定義されています(翻訳はわたし)。
 なお「性能効率性」が品質特性で、続く3つが副特性。副特性はシラバスではそれぞれ、「時間効率性」「資源効率性」「キャパシティ」に対応します。

性能効率性
明文化された条件のもとで利用されるリソース*1の量にかかわる性能を表す特性。この特性は、以下の副特性からなる。

時間的な振る舞い
プロダクトやシステムがその機能を果たす際に、レスポンス・処理時間・スループットが要件を満たしている度合い

リソースの利用
プロダクトやシステムがその機能を果たす際に、利用するリソースの量や種類が要件を満たしている度合い

キャパシティ
プロダクトやシステムのパラメターの上限が、要件を満たしている度合い

 ファミレスのホールで働く店員さんを例にとってみます。
 「この人、デキる人かな?」と評価するには、どういう点に着目すればいいでしょうか。

 チャイムで呼ばれたらそのテーブルに向かい、オススメやオプションを提示しながら、お客さんの注文を聞き取り、厨房に伝える。
 この作業の正確さは、品質特性でいうと「機能正確性」というものにあたるでしょう。

 しかし、この作業に毎回10分もかかっていたのでは困ります。お客さんの人数や性質に依るけれど、概ねx分くらいで済ませられる。これが「時間効率性」にあたります。

 さて、この店では注文を今も紙とボールペンでメモしている。ベテランの店員さんは、メニューごとに自分なりの符号を編み出していて、一品をカタカナ3文字くらいで表現できるのですが、新人はそれができず、長々と商品名称を書いてしまいます。
 紙とボールペン、めっちゃ使います。これが「資源効率性」です。
 まあ、紙とボールペンを大量に使うくらいなら、「もったいないなあ」で済むかもしれません。ところがある日、ボールペンのインクが切れてしまう。その店員さんは手書きでしか注文を受けられないので、対応できず固まってしまう・・・。これでは困ります。

 さらに、お客さんがたくさんいてひっきりなしに呼ばれたり、1回の注文の内容が大量だったときにも耐えられるか。これが「キャパシティ」ですね。

「キャパシティ」に関する事例

 『ポストモーテム みずほ銀行システム障害 事後検証報告』という本では、以下のような事例が紹介(引用の太字はわたし)されています。

e-口座に切り替える定期性預金口座は約259万件。システム上の制限から45万件ずつ6回に分け、2月27日から3月14日までの土日にe-口座への一括切替処理を実施することに決めた。
この一括切替処理のシステムテストに際しては、実機での処理は8万件までしか試さなかった。実は定期性預金口座システムのDBは、前述のインデックスファイルに関する容量制限に起因して、更新件数が64万2969件を超えるとそれ以上は更新できなくなるという問題を抱えていた。しかしテストでの処理件数が不十分だったため、DBの設定にリスクがあることを検出できなかった。

 これを性能テストと呼ぶかはちょっと微妙ですが、上述した品質特性でいうと、キャパシティにあたる問題に見えます。

 本番環境と同等の環境や条件が手に入らないとき、何らかの仮定に基づいて、本番環境のグレードを一部下げた、サブセット的な環境・条件において性能テストを行うことになるでしょう。
 そのリスクについては、「4.2.8 性能テストケース実行のための準備」で以下のように語られています(引用の太字はわたし)。

性能と環境の関係は非線形であることを覚えておくことが重要である。つまり、テスト環境が本番の標準的な環境から離れれば離れるほど、本番の性能を正確に予測することは難しくなる。テストシステムが本番と同等でなくなるほど、予測の信頼性が低くなり、リスクレベルが高まる

 この話については、また考えていきたいと思います。

Top speed

*1:「時間」もリソースの1つという扱いなのだと思います。

コープランド本の例題から、ブラックボックステストとホワイトボックステストを考える

 プレスマンの白本*1・「第19章 ソフトウェアテスト - コンポーネントレベル」の「19.5 ブラックボックステスト」には、以下のような説明があります。

ブラックボックステストテストはホワイトボックステストの代替ではない。むしろ、ホワイトボックステストの手法でみつかるのとはまた別の種類のエラーを見つけることを期待できる、補完的なアプローチと言える。

 これを確認するために、『はじめて学ぶソフトウェアのテスト技法』(リー・Copeland著、日経BP社)の例題を見てみたいと思います*2

Binderの例題

 同書第1章の節「すべてをテストすることはできない」では、その例を以下のようなプログラム*3で示しています。
 関数blech*4は整数iを受け取って1を引き、30,000で割っています。余りなしで「商」を求める*5ものです。

gist.github.com

 ここで、本来は「1を足す」が正しい処理だったとします。その場合、この欠陥はどのように見つけられるでしょうか。
 なおここでは、intの最小が -32,768、最大が 32,767 という前提です。つまり整数だけ考えても、65,535 65,536の入力候補があることになります。

ホワイトボックステスト

 たとえば i = 1 の場合、正しいコードでも間違ったコードでも出力は0となり、間違いを検出できません。
 が、ホワイトボックステストの制御フローテストを考えた場合、i =1 を実行すれば、ステートメントカバレッジは100%となります*6

ブラックボックステスト

 それでは、ブラックボックステストではどうでしょう。
 ブラックボックステストを行う前提として、以下のような仕様が明示されていたとします。そのままプログラムみたいなものなので、現実には考えられないでしょうが・・・。

入力された整数に1を足したあと、30,000で割った商。

 テスト技法として、同値分割と境界値分析を使ってみます。
 まず、入力値の同値分割。
 1つの切り口として、整数iの領域を、正・ゼロ・負の3つのエリアに分割することが考えられます。その境界値は下から、intの下限、-1、0、1、intの上限という5つになります。

 次に出力値を考えてみましょう。
 結果となる商の領域も、正・ゼロ・負の3つのエリアに分割できます。商が-1から0になる境界と、0から1になる境界を考えると、入力値は -2、-1、29998、29999 という4つになります。

誤りを検出できる入力

 両者から重複を除くいた8つのテストケースについて、正しいプログラムと間違ったプログラムでそれぞれどのような出力になるかを整理したのが以下の表です。3つのテスト(i = -1, 0, 29999)で、誤りを検出(★)できていることがわかります。

i 期待する結果 実際の結果 検出 同値分割 (入力) 同値分割 (出力)
-32768 -2 -2
-30001 -1 -2
-30000 -1 -2
-2 -1 -1
-1 0 -1
0 0 -1
1 0 0
29998 0 0
29999 1 0
30000 1 0
32767 1 1

結論

  • ホワイトボックスでコードカバレッジ100%を達成したというのは、そのパスを通す一例が実行できたということに過ぎず、十分なテストをしたことを意味するわけではない。
  • 仕様に基づくブラックボックステストは、ホワイトボックステストでは満たせない部分をカバーすることができる。

気になること

 Copelandさんのこの節で、よく理解できない点が2つあります。

  1. 入力の例として、1、42*7以外に、intの範囲を超えた40,000、-640,000を挙げている。「期待される結果」「実際の結果」も合っていない。

  2. 例題において、欠陥を検出できる入力は4件としているが、上の表にも示すように、6つの入力値で出力の差が出る。

 わたしの方が間違っているのかもしれませんが、誰かこの謎を解いてください・・・。

Cat eyes on black body"Cat eyes on black body" by Stefan Tell is licensed under CC BY 2.0

2022/3/30追記

 「気になること」について、辰巳さんにが原著や引用元をあたってくださり、概ね以下のことが判明しました。ありがとうございます!

事実1 引用・改版・翻訳で情報が変化

 Copeland本ではこの例を、Binderの本からの引用としていますが、これはさらにFriedman*8の本からの引用であるとのこと。
 またCopeland本の翻訳の過程で数字が(おそらくは意図せず)変わってしまったり、原著の方ではErrattaが出ているなど、間違いが何箇所かで紛れ込んでいるようです。

事実2 入力例に誤植あり

 入力値-640,000というのは、本書が前提としているintの下限以下であり、例としてはおかしいと考えていました。この点、原著では-64,000だったとのこと。
 ただ、-64,000も下限以下なんだよな。。

事実3 負の数の除算の扱い

 -7/3 = -2.333... の小数部を切り上げて-2にするか、切り下げて-3にするかについて、Pythonの //演算子では切り下げになっており、また割り算の定義もこれに合致するため、この記事では切り下げを採用しています。
 が、Copeland本では切り上げを採用しているようです。

 切り下げと切り上げでは、欠陥を検出できる入力値も個数も変わってきます。

  • 切り上げ(ExcelのROUNDDOWN()イメージ): -30000 -29999 29999 30000 の4つ
  • 切り下げ(ExcelのINT()イメージ): -30001 -30000 -1 0 29999 30000 の6つ

 Copeland本でいう「4つ」というのは、この結果だと思われます。

事実4 Binder本では「40個」

 Copeland本の引用元となるBinder本では、欠陥を検出できる入力値は40個であるとしているとのこと。これは、割る数が30,000ではなく3,000となっているからのようです。
 割る数を3,000とし、また負の数の除算を切り上げで考えると、確かに40の入力値で差分が出ることが確認できました。

 一部のマニアしか興味をもたないと思いますが、わたしは好きなので残しておきます。将来Copeland本をマジメに読んで「!?」となる人のためにはなるかもしれません。

参考

 不定期に盛り上がる同値分割、境界値分析に関するTogetterを参考にしました。
 読み直して、今持ってなお自分が、基本にして最難関のこの技法を十分に理解できていないことを感じました。

*1:『実践ソフトウェア・エンジニアリング 第9版』のことです。

*2:正直、この記事の内容には自分自身でも疑いを抱いています・・・。

*3:Robert Binderの『Testing Object-Oriented Systems』からの引用とのこと。

*4:英辞郎によるとblechは

間投 《嫌悪》ゲーッ、オエッ、ゲッ、くそっ、ウヘッ、ヒェッ

*5:負の整数の割り算について、 -7÷3 = -2...-1 ではなく、-7÷3 = -3...2 という計算をするようです。 定義上、0≦余り<割る数 と考えるため。

*6:ブランチカバレッジも100%です。そもそも分岐がないので。

*7:42は、生命、宇宙、そして万物についての究極の疑問の答えを表すため、代表値として選択する価値がある。ja.wikipedia.org

*8:以下、教えていただきました。
M. A. Friedman and J. M. Voas. Software assessment: reliability, safety, testability. 1995.