見出し画像

第232回: 「ソフトウェアテストしようぜ」45 CEGTest(15.制約MASK後編)

◀前の記事へ 次の記事へ▶


≡ はじめに

前回は、「制約MASK」の概要がテーマで「制約MASK」を使用する例を書きました。

前回のまとめは以下の通りです。

「制約MASK」には方向があり、ノードaからノードbに向けて制約MASKの線が引かれていたら「aがTになるとbは無くなる(それを「M」と表現する)」です。

※ 「M」は、「MASKがかかった状態」の意味です。T(真)でもF(偽)でもなくM(真と偽のどちらであるか不明な状態)を表現します。

ただし、前回は「制約MASK」を使用した原因結果グラフとそのデシジョンテーブルの提示はしてみたものの、noteが長くなってしまい、それらの図表についてはほとんど説明していません。

そこで、今回は「制約MASK」の前編の続きとして、例題の解説を書こうと思います。



≡ 制約MASKの例題を細かく見ていこう

まずは前回の例題と解答例(ほかの原因結果グラフでも表現できますので“例”です)を再掲します。

《例題》
以下の図は、スケジュール管理アプリにある、【予定】の「タイトル」と「予定時間(開始・終了日時)」を入力する画面です。

スケジュール管理のダイアログ(MASK前)

この図をよく見ると、日時は、2010年8月3日10時から同日の17時となっています。つまり、その日はハイキングの予定ということです。
であれば、一日中ということで、[終日]のチェックボックスをチェックしてみます。ダイアログは、以下に変わりました。

スケジュール管理のダイアログ(MASK後)

「上記ダイアログの原因結果グラフとデシジョンテーブルを作成しなさい」というのが、制約MASKの例題です。

ちなみに、13年後の(現在の)OutlookのGUIは以下のように変わっていました。

Outlookのダイアログ(MASK前)
Outlookのダイアログ(MASK後)

確認したOutlookのバージョンは、「Microsoft 365 / バージョン2304 / ビルド16327.20248」です。

ここで、注目してほしいのは現バージョンでは、[終日]にチェックを入れたときに、手入力した[開始時刻]と[終了時刻]が昔のバージョンのように「無くなる」のではなく、「0:00に強制的に変更されてユーザー変更不可となる」点です。

ダイアログを素直に読むと、開始時刻が「2020/8/3 0:00」、終了時刻が「2020/8/3 0:00」になったように見えます。
でも、よく考えると、この2つは同じ時刻です。終日を丸一日と同じ時間帯を手入力で設定するなら、開始時刻を「2020/8/3 0:00」、終了時刻を「2020/8/4 0:00」に指定しなければなりません。

さて、ここで「クイズ」です。
手動で開始時刻を「2020/8/3 0:00」、終了時刻を「2020/8/4 0:00」に指定したあとに、[終日]ボタンを押すと開始時刻と終了時刻は以下(太字は違う所です)のどちらになるでしょうか?
(8/3? それとも8/4?? 解答はこのnoteの最後に書いておきます)

 1. 開始時刻「2020/8/3 0:00」、終了時刻「2020/8/4 0:00」
 2. 開始時刻「2020/8/3 0:00」、終了時刻「2020/8/3 0:00」

話を例題に戻します。
前回、解答例として、以下の原因結果グラフとデシジョンテーブルを載せました。

スケジュール管理のダイアログの原因結果グラフ

上記の原因結果グラフを「読み解く」ことはそれほど難しいことではありません。でも、ゼロから描いてみると案外描けないものです。もし、自分で描いていない人は今からでも遅くないので、腕試しとして、いったん、上の例題までスクロールして、CEGTestへアクセスして原因結果グラフをつくってみることをおすすめします。

原因結果グラフの知識を頭で理解しても、いわゆる畳水練です。「自分のテストでCEGTestを使うぞー!」となったときに、すぐに使えるものではありません。

原因結果グラフを覚えたての頃は、スキーが滑れるようになるまでのように、1枚描くごとに上手くなる実感が得られて楽しいものです。楽しいというのは噓のような本当の話です。

最初のうちは嫌々と原因結果グラフを描いていた人も、少しすると「もっと、原因結果グラフの問題はないの?」と聞いてきます。パズルのような面白さがあるのだと思います。
(原因結果グラフの練習問題のネタが尽きたときには、デシジョンテーブルの例題を渡しましょう)

それでは原因結果グラフとデシジョンテーブルを読み解いていきましょう。

■ 原因結果グラフの読み解き

まずは、原因結果グラフの読み解きですが、原因結果グラフは描く必要がありますから、描き方について書きます。

スケジュール管理のダイアログの原因結果グラフ

原因結果グラフの描き方ですが、まず初めに「1番左に原因ノード」、「1番右に結果ノード」を配置します。

上記原因結果グラフの原因ノードの「開始日」、「ユーザー指定開始時刻」、「終了日」、「ユーザー指定終了時刻」、「終日」の5つすべてが入力である点に注意してください。
ほとんどのケースで、「原因ノード = 入力」です。

「ほとんどの」と書いたのは、若干の例外があるからです。「入力」ではなく、「環境条件」や「状態」や「シグナル」(タイマーなどの内部シグナルや、温度上昇などの外部シグナル)などが原因ノードとなることがあります。

※ 「入力」の種類に興味がある方はこの連載の「第185回: 「ソフトウェアテストしようぜ」2 庫内スイッチ」のFRAMについてご確認ください。

次に、右端にある結果ノードの「日時」ですが、「スケジュールに登録するイベントは、 いつから、いつまでなのか?」を「日時」と表現しています。「予定日時」でもよいですし、「開始終了日時」でもよいでしょう。

原因結果グラフは描画スペースをとるので、ノード名はあまり長くしないことをお勧めします。

ところで、今回のダイアログのように、「日」を入力するノードが複数あるときは、「開始日」と「終了日」というように、それぞれのノードに違う名前をつける必要が生じます。(違う名前にしなくても、原因結果グラフやデシジョンテーブルはつくれますが、わかりにくいものになります)

違う名前にするときには、ノード名が「どこの+何」となるため、長くなりがちですが、ノード名を番号にするのはお勧めできません。原因結果グラフをレビューするのが大変になるからです。しっくりくるノード名はロジックのモデル化が上手くいっている証拠です。

さて、最左端の原因ノードと最右端の結果ノードが決まったところで、次はその間に位置する「中間ノード」を見つけていきます。
以前にチラッと書きましたが、中間ノードは結果ノードから原因ノードに向かって作ると上手くいきます。

今回のケースでは、原因ノードは5つあります。5つから複数個を選ぶ組合せは、
   ₅C₂+₅C₃+₅C₄+₅C₅=10+10+5+1=26
で、26通りもあります。
この26通りの組合せのうち、本ダイアログの結果を生み出すロジックに関係する組合せは、「開始日✖ユーザー指定開始時刻」、「終了日✖ユーザー指定終了時刻」、「開始日✖終了日✖終日」の3つですが、この3つの組合せを原因ノードから見つけることは意外と難しいです。これが結果ノードから原因ノードへ遡って中間ノードを見つける理由です。
結果ノードを起点とすれば、ロジックを順番に分解しながら中間ノードを見つけることができるからです。

結果ノードを起点として、順番に分解しながら中間ノードを見つける方法ですが、具体的には、次のようにします。
まず、結果ノードである「日時」について考えます。すると、「日時」すなわち、「スケジュールに登録するイベントは、 いつから、いつまでなのか?」の指定方法には、「時刻まで指定する方法」(便宜的にここでは「時刻指定」と呼ぶ)と「日付のみで指定する方法」(日付指定)の2種類があることがわかります。
  日時 = 時刻指定 ∨ 日付指定
です。
ここから、「日時」という結果ノードをORで分割できることが分かったので、以下のような中間ノードと、その構造が見つかります。

「日時」を「時刻指定 ∨ 日付指定」に分解したところ

次に「時刻指定」には何が必要かな?と考えます。
すると、「時刻指定」には「開始日時」と「終了日時」の情報が必要ということに気が付きます。
  時刻指定 = 開始日時 ∧ 終了日時

「時刻指定」を「開始日時 ∧ 終了日時」に分解したところ

  これで、全体として結果ノードをここまで分解することができました。

「日時」を分解して中間ノードを見つけたところの全体

今回は、これ以上の分解は不要で、すべて原因ノードにつながりました。

スケジュール管理のダイアログの原因結果グラフ

あとは、原因ノードの同値分割と、制約をつけるだけです。今回は、原因ノードの粒度は十分に詳細化されています。そこで、同値分割はテストデータを作成するときに考えるとして、制約MASKをつけました。

こちらの原因結果グラフにさらに制約EXCLを「時刻指定」と「日付指定」につけたくなるかもしれません。
つけても構いません。しかし、制約は取り得ない組合せがデシジョンテーブルに現れないようにするだけです。したがって、出来上がったデシジョンテーブルをテストできるのなら制約をつける必要はありません。
制約を間違ってつけることによるテスト漏れのリスクが怖いので、私は、必要最小限の制約しかつけないようにしてひとつずつ制約を追加しています。

※ 開発者への情報提供という意味では、原因結果グラフに完璧に制約条件を付けるのもアリだと思います。開発体制に合わせるしかありません。

■ デシジョンテーブルの読み方

原因結果グラフの描き方がわかったところで、次は、CEGTestツールが生成したデシジョンテーブルの読み方(つくるのはCEGTestなので、読めればOK)です。

制約MASKのデシジョンテーブル

デシジョンテーブルの2, 5, 6列では、制約MASKの起点ノードの[終日]がTとなっています。冒頭にも書きましたが、制約MASKは以下のノードに影響を与えます。

「制約MASK」には方向があり、ノードaからノードbに向けて制約MASKの線が引かれていたら「aがTになるとbは無くなる(それを「M」と表現する)」です。

今回で言えば、上記の「a」が[終日]にあたり、「b」は「ユーザー指定開始時刻」と「ユーザー指定終了時刻」の2つです。だから、デシジョンテーブルの2, 5, 6列の「ユーザー指定開始時刻」と「ユーザー指定終了時刻」は「M」となっています。前回のベン図を再掲します。

制約MASKのベン図

AがTのときには、BとCが「M」になっています。「制約MASKのベン図と同等の真理値表」も再掲します。

制約MASKのベン図と同等の真理値表

制約Mの真理値表は組合せを無くするものではありません。上表の「制約結果」を見ると全て「A」なので、組合せは取りえます。制約Mは、制約を受ける先をMに変えてしまうということです。


さて、ここで、デシジョンテーブルの#2列に着目します。
「ユーザー指定開始時刻」と「ユーザー指定終了時刻」が[終日]ノードが「T」となったことを受けて、「M」となっています。また、その結果、「開始日時」と「終了日時」が「I」となっています。

CEGTestのヘルプには、「I」の説明として、「原因側のノードが真偽不明のため真偽が決定できない。」と書いてあります。

この意味ですが、以下の赤く囲った部分に着目してください。

制約MASKが聞いている個所

赤く囲った部分を論理式で書くと、
  開始日時 = 開始日 ∧ ユーザー指定開始時刻
となります。ここで、#2列を見ると「開始日」はTで、「ユーザー指定開始時刻」はMです。この値を上記式に代入します。
  開始日時 = T ∧ M
Mはノード自体がなくなっている(TかFか確認できない)という意味です。「ユーザー開始時刻」はダイアログからも消えていました。(下記参照)

スケジュール管理のダイアログ([終日]チェック後)

さて、
  開始日時 = T ∧ M
をどう考えるかですが、「Mは無いのですが、∧演算子は残っている」と考えます。

演算の結果は以下のルールで決まります。

・ MがTのときは、T ∧ Tなので開始日時はT
・ MがFのときは、T ∧ Fなので開始日時はF

今、MはTとFのどちらであるかわからないのですから、開始日時についてTかFかはわからないという状況になります。これが「I」という意味です。CEGTestのヘルプを再掲します。

「I」は、「原因側のノードが真偽不明のため真偽が決定できない。」

さて、それではデシジョンテーブルの#5列にはなぜ「I」があらわれないのでしょう。それは、開始日にFが設定されているからです。
開始日時 = F ∧ M
ですから、MがTでもFでも開始日時はFになることがわかります。

#5列の終了日時と#6列目の開始日時が「I」になっていないのは、、、おそらく、CEGTestのバグです。でも、表示だけの問題なので、テストとしては大丈夫です。
大丈夫なことは次回以降のカバレッジ表の見方が分かれば納得できます。


▪️ XCEGの結果

昔つくったツール(XCEG)の結果も載せておきますね。

XCEGツールの原因結果グラフ


XCEGツールのデシジョンテーブル

「I」の表示は期待通りでした。
さらに違いが見つかりました。
「I ∧ I」の結果がCEGTestでは「F」XCEGでは「i」(オレンジ色のセル)になっていたのです。(「I」は「真偽が決定できない」意味ですから、演算した結果も真偽が決定できないということで、小文字の「i」にしたのでしょう。「F」よりも妥当とは思いますが、[終日]が「T」の時には、日付指定が「T」で日時指定が「F」でしょうから「F」でもいいのかもしれません。
ちょっと曖昧な書き方をしているのは、制約同士が矛盾した場合どちらを優先すべきかというちょっと難しい問題……この連載ではこれ以上触れない予定です……があるからです。)

原因結果グラフツールが生成した条件の組み合わせ結果に疑問が生じたときには「そこのロジックについて仕様が曖昧」のことが多いです。
したがって、その列についてはどういう結果が正しいのかよく考えて、テストをします。開発も難しい箇所になりますので、テスト対象のバグを見つけるチャンスです!

このことを一言で言えば「制約MASKのトリガーになるノードをTとしたテストを徹底的におこなう」です。
例題で言えば「[終日]にチェックを入れたときにバグが出ることが多い」ということです。
上の方に「クイズ」を出しましたが、なぜクイズになるかと言えば間違いやすいからです。

ところで、例題では∧(AND)の話しか出てきませんでした。

∨(OR)の場合は、「F ∨ M」が「I」になります。
なぜなら「F ∨ T」はT、「F ∨ F」はFとなるからです。



≡  おわりに

今回は、「制約MASK」の例題の説明でした。

最後にCEGTestのバグが見つかって、ヒヤッとしました。
制約MASKはあまり使いませんが、CEGTestツールのバグ(#5, #6列の「I」表示)は直してほしいなと思いました。ソースが提供されているのだから「自分で直せ」って話なのですが……。

そして、「制約MASKのトリガーになるノードをTとしたテストを徹底的におこなう」という話も書きました。こちらは原因結果グラフを描かなくても仕様書や要件定義書を読んで、何かの機能を隠すものを見つけたら、そこを注意深くテストすれば良いです。テスト担当者なら無意識でおこなっていることとは思うのですが、「境界」や「空欄」と同じように、意識してテストをつくることをお勧めします。

さて、次回以降ですが、数回にわたって、カバレッジ表について書く予定です。

途中のクイズの答は2でした。
Outlook、ふるまいを統一していて偉い!

◀前の記事へ 次の記事へ▶


いいなと思ったら応援しよう!