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

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

Checking vs Testing 論争への強烈な皮肉

Socrates

 Jason Arbonさんによるブログ記事「Checking vs Testing」は、「Checking vs Testing みたいな議論は、時間の浪費に過ぎない。この記事自体も含めて」という皮肉ですね。

medium.com

 なお、ほぼ同じタイトルの記事は、有名なコチラ。

www.developsense.com

 Jasonさんは記事の中で、「テストとチェック」と「手動テストと自動テスト」というよくある二つの比較について言及しています。そもそもこの議論の目的は、別に真実を追求したいわけではなく、ソフトウェアテストの現代化の波の中で、エキスパートとしてのロールを守りたいだけの人が騒いでいるのだろうと一刀両断しています。

testing と checking

 まず test と check については、「Googleで検索すれば、test の定義に check も入っている。以上。別に大した違いはない」としています。
 一部の人が、check に「機械的で単調」というイメージを押し付け、一方で test をより高尚なものと位置づけることで、自分たちが価値ある活動に従事していると言いたがっているというわけです。

 James BackさんやMichel Blotonさんの主張を皮肉っているようにも思えますが、「簡単なチェックを手動で実行するにしたって、頭は使う」「テストケースに基づくテストも探索的テストも、どちらも大事なんだ」という立場をみると、言っていることは同じかなという印象です。

www.kzsuzuki.com

automated と manual

 ここでも、「自動テストはチェック(上で述べた意味での)の繰り返ししかできない」という決めつけによって、「人間のテスターが重要」という主張を正当化しようとしていると言います。Jasonさんはこれを、2つの理由から否定しています。

  1. スピード・コスト・再現性という自動テストのメリットを無視している
  2. 二種類ある自動化のうち、一方しか見ていない。

 「二種類ある自動化」というのは、わたしにとって新鮮な分類でした。
 いわく、「リグレッション」「生成的」の二つです。

 自動テストとしてまずイメージするのは一つ目ですよね。まず人間がテストケースを書き、それに従って自動化する。そのあと、リグレッションテストとして繰り返し実行されます。
 これは「毎回同じ」「単調」なものとされがちですが、賢いテスト自動化エンジニアだったら、実行のたびに環境やパラメタを変えたりしていると。殺虫剤のパラドックスを、自動テストでも回避しようと頭を使っているということですね。「同じことしかできない」という認識はあらためる必要があります。

 二つ目は、人間によるテストケースを前提とせず、仕様などの情報からテストを自動的に生み出すものです。最近も記事に書いたモデルベースドテストが該当しますよね。

www.kzsuzuki.com

 また他の例として、AIの強化学習によってテストを見つけ出すものを挙げています。ここまでくると、もう「単調」とはほど遠いですよね。

ためにする議論はもう・・・

 保身を目的として、あるいは自分を立派に見せたくてこの手の議論をする人たちを、Jasonさんは皮肉をこめていろいろな言葉で呼んでいます。testing philosopher、quirky deep thinkers、evangelists、sophists、・・・。

 「テストとは何か」を議論することが悪いわけではないと思います。
 ダメなのは、「自分は価値のある仕事をしているんだ!」とアピールするためだけに、この手の議論をふっかけて、耳目を集めようとすることです。

「シナリオテスト」とは一体、何なのか - その4

12. Orthoceras

 シナリオテストについてのメモです。
 シナリオテストについては識者がいろいろな議論していますが、それらとの整合性とかは考えず、今わたしが考えている、わたしの知っているドメインでの「シナリオテスト」の話です。

 ただ、下書きを書いた後に、2012年の自分の記事を読んでみると、大きく変わってはいませんでした。。。まるで成長していない・・・ので、「その4」にしています。

www.kzsuzuki.com

シナリオテストの定義

 まず、ISTQBの定義。
 ISTQBでは、シナリオテストはユースケーステストの同義語として扱われています。

ユースケーステスト(use case testing)
ブラックボックステスト技法の一つ。ユースケースの動作を実行するようにテストケースを設計する。
Synonyms: シナリオテスト(scenario testing), ユーザシナリオテスト(user scenario testing)

 次に、ISO/IEC/IEEE 29119 Part3の定義と勝手訳。

scenario testing
class of test design technique in which tests are designed to execute individual scenarios
Note 1 to entry: A scenario can be a user story, use-case, operational concept, or sequence of events the software may encounter etc.
個々のシナリオを実行するためにテストを設計する、テスト設計技法のクラス。
「シナリオ」とは、ユーザストーリー、ユースケース、運用の構想、ソフトウェアが遭遇しうるイベントの連なりなども含む。

 「シナリオテスト」周りにはいろいろな類似概念がありますが、ISTQB、29119ともに、それらをまるっと「シナリオ」「ユースケーステスト」に寄せようとしている印象です。

 で、オレオレ定義。

人間とシステムの相互作用によって、指定されたゴールが達成できることを確認するためのテスト。

 「ゴール」という言葉が逃げなのですが、「ユーザが達成したいこと」という意味です。
 「業務」というと業務系システムに限定してしまう感じなので、この言葉を使っています。

 このオレオレ定義の意図するところには、以下のようなものがあります。MECE性無配慮。

シナリオでは、人が中心であり、システムが絡まない部分がありうる

 シナリオにおいてはコンピュータシステムは要素の一つにすぎず、システムの現れない作業もありえます。
 たとえば「テレビ局の番組のための取材」という業務を考えてみましょう。取材のサブ業務として、以下を仮定します。

  1. 取材に関する情報の登録と、取材に回す人間のアサイン
  2. 取材自体
  3. 取材で得た音声/映像コンテンツの持ち帰り
  4. 当該の取材に関する情報の更新

 1と4はいかにもシステム化していそうな部分ですが、2はシステムからいったん離れています。
 3はもう少しややこしくて、電子的に転送できる環境であればコンテンツシステムに自動登録され、取材と関連付けられます。一方そうでない場合は「媒体を車両で物理的に持ち帰る」ことになります。ここにはシステムが現れません。
 1と4の間で、システムの利用が断絶しているわけです。

 シナリオテストでは、システムの利用が断絶しても、業務自体は断絶しないことを確認しなければなりません。もっと積極的には、「スムーズに回る」ことが理想です。
 これは1、3、4の個々の機能のテストではわからないことです。

シナリオは、機能の組み合わせからなるものではない

 ゴールは機能に先立ちます。まずゴールがあって、システムとその機能は、ゴールを達成するための補助的な存在です。
 よってシナリオテストのためのシナリオは、「ユーザは何を達成したいのか」からトップダウン的に作るのがメインルートであり、「おれたちの作った機能を組み合わせて何ができるだろう」からボトムアップ的に逆算するのはサブルートにするのがよいです。

 といっても、機能をいろいろ組み合わせてシナリオを作り上げることを否定するわけではありません。機械的に組み合わせてみると、トップダウンのアプローチでは思いつかなかったシナリオを発見できるかもしれない。ですので、発想を広げるためにはこのアプローチも有効だと思いますが、まずは「何を達成したいのか」から目をそらさないことが大事だと思います。

シナリオの網羅性は、機械的に説明しがたい

 テスト技法の中には、テストカバレッジの「分母」を数学的に導出できるものがありますよね。コードカバレッジがそうですし、組み合わせテストや状態遷移テストもそうです。

 一方シナリオテストにおいて、「全シナリオ」を機械的に導出できるとは考えづらいですね。
 シナリオの網羅性は、「ユーザが達成したいこと」とその「バリエーション」、そして「それを妨げる事象」をどれだけ洗い出せるかが勝負になります。特定のお客様がいるのであれば、そのお客様が示す明示的/暗黙的な要件。不特定多数のユーザがいるなら、それらのユーザから収集可能なオペレーションログから想定できる「使い方」。などなど、得られる情報から発想を繰り返すことが、シナリオテストにおける「テスト設計」になるのではないかと思います。

ということで

 昔考えていたことから特に進展はありませんでした。
 ただ当時は、シナリオを「ユースケースの接続」のように捉えていたのですが、現在は「ユースケースとユースケースの隙間に、システムの介在しない隙間があるよな」と考え直しております。

 ずーっと積読になっていたこの本を読み始めました。いつか記事書きます。

「探索的テスト3.0」における「探索」の位置づけなどの覚書

Compass Rose

テストと探索的テスト

 James BackとMichael Boltonによる、「彼らの」探索的テストについて、簡単に振り返っておきます。これを読んでも、探索テキストがうまくなるわけではありません。

 『Exploratory Testing 3.0』(探索的テスト3.0)という記事は、「探索的テスト」が生まれた背景や変遷についても言及しており、とても長いです。

www.satisfice.com

 翻訳はこちらにあります。5年以上前の翻訳で粗も目立ちますが、許してください。*1

sites.google.com

 About That Last Bullet Point の部分を読めば、彼らの今の立場がわかります。

 まず大事なのは、「探索的」という言葉についての認識の刷新。

今や、すべてのテスティングが探索的であると定義付けている

 「探索的テスト」が旨としていた「探索」は、実はテスト自体が当然もつべき性質であるとしています。よって「探索的テスト」という言葉は、馬から落馬しているようなものなのです。

 そこで、この新しい認識を踏まえたうえで、Testing 自体に新しい定義を与えています。

テスティングとは、探索と試行を通じてプロダクトについての知識を得ることでそれを評価するプロセスである。これには、質問、学習、モデリング、観察、推論、出力のチェッキングなどが含まれる。

 つまり、よく対比される探索 (exploring) もチェック (checking) も含むプロセスを、テスト (testing) と再定義しているわけですね。
 現在の、探索(search ではなく exploration の方)を行わない自動テスト (automated testing) は、テストの役割のうち特にチェックを担うという位置づけになるでしょうか。

 ただし彼らは、チェックを「価値のないもの」「無意味なもの」と扱ってはいるわけではありません。テストに属する重要な要素の一つです。探索とチェック、それぞれの役割があるという考えですね。

 ダメなのは、人間による unmotivated checking、つまり「やる気なしチェック」です。これはまさに自動化で代替したいところ。逆に、モダンな自動テストであれば、「やる気なしチェック」よりよっぽど上等な学習や推測をしてくれるかもしれません。 

 同じように、スクリプトテストについても否定していません。

必ずしも、誰かに与えられ、従うよう義務付けられた具体的な指示を指すわけではない。あなたの先入観が、手順に影響する。あなたの無知が、手順に影響する。あなたの組織の文化が、手順に影響する。あなたが成し、二度とはやり直せない選択が、手順に影響する。

 一般的に「探索的テスト」といえば、手順が指定されていないことを特徴とされがちですが、「手順があること」は必ずしも「探索」を妨げるものではないからです。
 考えてみれば、手順が明確にされていたとしても、あやしい動きを見たら立ち止まることもありますし、そこから派生するテストケースを書き出したりもしますよね。

 もう一つのポイントが以下ですね。

ET 3.0は、手順化を一つの技法に格下げし、探索的テストをテストそのものであると格上げした

 従来は、「テスト」があって、その下に「手順のない探索的テスト」と「手順のあるスクリプトテスト」のように扱われていました。
 今は、探索行為を含む「テスト」があって、その方法の一つとして「手順のあるスクリプトテスト」を位置づけているということです。

 以上をまとめると、以下のようになります。

  • 探索的なふるまいは、本来「テスト」自体が持つ要素である。
    • 「探索的テスト」という言葉は引退!
  • 「テスト」には、探索やチェックの他、質問・学習・モデリング・観察・推論といった行動も含まれる。
  • テストの手順は探索の否定にはならない。
    • ただし手順化は、テストの技法の一つに過ぎない。

 まあちょっと言葉遊び感も、あるにはあります。
 ただ、「探索的テスト」に限らず、テスト自体が人間の能力と自発性を要求するものだという位置づけは、納得のいくものだなと思います。

 ・・・と書き終えたところで、また『Checking vs Testing』という記事が上がっているとにしさんのツイートを見かけたりして・・・。

medium.com

*1:またこのお詫び自体も、まるで「今ならもっといい翻訳ができる」と言わんばかりの見栄に満ちていますが、合わせて許してほしい。

モデルベースドテストについて学んでみよう - その5

Hanoi - Pagoda de Tran Quoc

 せっかくファンクションネットを学んだので、「ハノイの塔」のモデルを作ってみることにします。

ハノイの塔とは

 ハノイの塔については、Wikiにアニメーションもあるのでそれを見ていただくのが早いと思いますが、「仕様」を引用します。

ja.wikipedia.org

以下のルールに従ってすべての円盤を右端の杭に移動させられれば完成。

  • 3本の杭と、中央に穴の開いた大きさの異なる複数の円盤から構成される。
  • 最初はすべての円盤が左端の杭に小さいものが上になるように順に積み重ねられている。
  • 円盤を一回に一枚ずつどれかの杭に移動させることができるが、小さな円盤の上に大きな円盤を乗せることはできない。

 ブロックゲームと動きが似ているので、ちょっとした改造で作れそうですね。

モデリングしてみる

 改造のポイントは以下の通りです。

  1. テーブルに相当する置き場所「杭」が3つになるので、プレースも3つにする。
  2. 持ち上げ動作も、杭ごとにイベントを定義する。
  3. 杭の一番下における円盤は1つだけなので、ontableトークンの数に制約を追加する。
  4. 円盤の大小関係の制約がある。

 CとDについては、イベントのガード条件を以下のように定義。

c ・・・ tokenCount(ontableA,0)
d ・・・ x<y

 その結果が、以下の通りです。

f:id:kz_suzuki:20200429133041p:plain
ハノイの塔のファンクションネットモデル

 汚くてビジーですね。おそらく、MISTA(あるいはファンクションネット)の表現力の理解不足による、冗長なモデルだと思います。

 たとえば改造点aについて、杭の数と同じだけプレースを用意するのではなく、変数化できそうな気がします。
 また改造点bについて、「3つの杭のいずれかに円盤があって、かつアームが円盤をもっていない場合」という論理を表現する方法がわからないため、やむなくイベントも個別に定義したりしています。
 MISTAには条件を表現するさまざまな演算子が用意されていますので、このモデルももっとシンプルにリファクタリングできるのではないかと思います。

 さらにいうと、「持ち上げた元の場所にもう一度下ろすような遷移は冗長なのでガードしたい」ですし、そもそもholdingというプレースを設けずに、moveみたいなイベントで代替した方がさっぱりするでしょう。

 でもまあ・・・とりあえずこれで動くんでね!

シミュレーションしてみる

 初期状態は、杭Aに下から5・4・3・2・1の順番で円盤を積まれています。
 終了状態は、それを同じ形で杭Cに移した状態になっています。

 この条件で Verify Goal State Reachability を実行すると、62手で到達することがわかります。「上げ」「下げ」がセットなので、実質31手。
 ハノイの塔の最小手順は、円盤数nに対し2n-1ということなので、このシミュレーション結果は最小手順になっているようですね。

 ちなみにn=6でチェックすると、

No path is found to reach the following goal under the given search conditions: [G1] clear(1), on(1, 2), on(2, 3), on(3, 4), on(4, 5), on(5, 6), ontableC(6).

 到達できないことになっている・・・。
 実際にハノイの塔をやってみると、再帰的な操作になることがわかります。n=5ができるのに、n=6ができないというのはちょっと不思議ですね。

 いろいろ試してみたところ、n=6の途中出てくるはずの状態

clear(1), on(1, 2), on(2, 3), on(3, 4), on(4, 5), ontableC(5), clear(6), ontableB(6)

 つまり小さい方からn-1枚が重なっていて、一番大きな1枚だけ別の杭にある、という状態には到達できる。またこの途中状態から、定義した終了状態にも到達できる。

 推測として、検証するツリーが大きくなりすぎて、n=6は到達不可能と判断したのかな?と。
 あるいはこのモデルは単に間違っているのかもしれませんが・・・。

おわりに

 ハノイの塔はシンプルなゲームですが、それでもモデルを実装しようとすると暗黙の条件が漏れたりしますし、慣れと試行錯誤が必要です。
 モデルの実装とシミュレーションを通じて仕様を固めていくというのは、設計でもテストでも有効だなと感じます。というか楽しいんですよね、モデリングって

 ということで、ツールでのモデリングから、シラバスに戻ってみようと思います。

モデルベースドテストについて学んでみよう - その4

The model

 第3回では、MBTツール「MISTA」を使って、ブロックゲームのファンクションネットを完成させました。

www.kzsuzuki.com

 第4回では、できあがったモデルでシミュレーションを行ったうえで、テストケースの自動生成をやってみましょう。やっとモデルベースドテストらしくなってきますね。

モデルをシミュレーションしてみる

 では、アノテーションで指定した初期状態と少し変更するとともに、終了状態も追加してみましょう。

INIT ontable(B1), ontable(B2), on(B3, B2), clear(B1), clear(B3)
GOAL clear(B1), on(B1, B2), ontable(B2)

 ブロックを3つにしています。
 初期状態でテーブルの上に直接乗っているのはB1とB2。B2の上にB3が乗っています。
 終了状態は、テーブルの上に直接乗っているのがB2で、そのうえにB1。B1の上には何もありません。B3については不問(任意のプレースを許容)です。

 第2回で行ったシミュレーションを行ってみると、想定した通りに動く(許される遷移が可能で、許されない遷移が不可能である)ことがわかります。

f:id:kz_suzuki:20200429102021p:plain
モデルのシミュレーションでランダムプレイを行う

 Start Random Simulation というモードでは、遷移先の選択肢をMISTAが選んで、次々に先に進めてくれます。
 上の図だと、ブロックが上からB1・B2・B3と重なっている状態になっています。

 また、Verify Goal State Reachability(終了状態到達可能性の検証)という機能を使えば、指定した初期状態から終了状態に到達することができるかをチェックすることができます。
 今回のモデルと初期/終了条件で実行すると、以下のような結果とシーケンスが導出されます。

Goal state: clear(B1), on(B1, B2), ontable(B2) is reachable.
Initial state: clear(B1), clear(B3), on(B3, B2), ontable(B1), ontable(B2)
Resultant state: clear(B1), clear(B3), on(B1, B2), ontable(B2), ontable(B3)
Firing sequence:
1. unstack(B3, B2)
2. putdown(B3)
3. pickup(B1)
4. stack(B1, B2)

 ブロックB2に乗ったB3を持ち上げ、テーブルの上に下ろす。テーブルの上のB1を持ち上げ、B2の上におく。という手順であることがわかります。*1

 このように、シミュレーションや経路探索の機能を用いることで、モデルの妥当性を検証することができます。

テストを導出する

 モデルが確認できたので、いよいよテストケースの生成をやってみましょう。

 MISTAでは、いくつかのカバレッジ基準に基づいて、「テストツリー」を自動生成することができます。
 たとえば、Transition Coverage(トランジション網羅)というカバレッジ基準では、すべてのトランジション*2が最低一度は現れるようにテストケースが生成されます。上のモデルだと、以下の通り。

f:id:kz_suzuki:20200429101355p:plain

 トランジションがツリーで表現されています。テストケースとしては以下の3つになります。

  • B1を持ち上げる→B1をテーブルの上に下ろす (pickup(x)putdown(x)をカバー)
  • B1を持ち上げる→B3の上に下ろす(pickup(x)stack(x, y)をカバー)
  • B3をB2の上から持ち上げる(unstack(x, y)をカバー)

 これですべてのトランジションが網羅できています。

 State Coverage(状態網羅)は、状態遷移図における状態網羅とは少しイメージが違います。

 状態遷移図で状態網羅というと、状態ノードを一度は通ることを指しています。
 一方第2回で述べたように、ファンクションネットでいう「状態」とは、トークンがプレースに配置されるパターンのことを指しています。よって「状態網羅」も、プレースの網羅ではなく、トークン配置のパターンの網羅を意味しています。

 (トークンではなく)ブロックの配置を考えてみると、3つのブロックについて以下のパターンがあります。

  1. すべてのブロックがテーブルの上にある。・・・1パターン
  2. 2つのブロックがテーブルの上にあり、残り1つはアームにつかまれている。・・・3パターン
  3. 2つのブロックが重なっており、残り1つはテーブルの上にある。・・・6パターン
  4. 2つのブロックが重なっており、残り1つはアームにつかまれている。・・・6パターン
  5. 3つのブロックが重なっている。・・・3パターン

 たとえば5.について上からブロックA・B・Cという配置であれば、

clear(A), on(A, B), on(B, C), ontable(C)

という状態になります。A、B、Cの順列を考えると、6パターンあることがわかりますね。
 このように、プレースは4つ、ブロックは3つですが、状態、つまりトークン配置は19パターンもあるというわけです。

 状態網羅を指定してテストケースを導出すると、以下のようになります。

f:id:kz_suzuki:20200429101631p:plain

 これを丁寧に追っていけば、上の19パターンがテストケース群のシーケンスのどこかでカバーされていることがかります。

テストケースから自動テストスクリプトへ・・・

 さて、モデルからテストケースが導出できるのはわかったのですが、モデルは実際のプログラム自体ではありません。
 MISTAでは、model-implementation mapping (MIM) という情報を通して、モデルとプログラム実装と関連付けることで、テストケースだけでなくテストコードまで生成し、さらに実行することができます。が、そこまでやる体力はない・・・。

 どなたかMISTAをお持ちの方は、ぜひ試してみてください!
 その5はコチラ。

www.kzsuzuki.com

*1:これは唯一のシーケンスではありませんが、なぜこのシーケンスが選択されたかはわかりません。。

*2:「イベント」と言った方がわかりやすいかもしれない。すべてのイベントを通ることで、すべてのトランジションも網羅される。