概要
ソフトウェアテストで使われるデシジョンテーブルという技法がある。
前からプログラムで作成できないのかな?と思っていたのだが、やってみたらPythonで意外と少ないコードで実現できた。
以下で実際にデシジョンテーブルを作成しながら説明していく。
『【この1冊でよくわかる】ソフトウェアテストの教科書―品質を決定づけるテスト工程の基本と実践』という本のp86の「レンタルDVDの料金割引のデシジョンテーブル」を例とさせて頂く。
使ったライブラリ
やり方
「条件」と「アクション」を定義する。
以下のように「条件」と「アクション」をリストに定義する。
conditions = ["旧作", "年齢65歳以上","年齢18歳以下"] actions = ["半額", "20%オフ", "10%オフ", "通常料金"]
※ 「条件」、「アクション」はデシジョンテーブルの用語だが、ここでは説明しない。
各条件の「Y/N」の全通りの組み合わせを求める。
以下の3つの条件について、Yes(=Y)の場合とNo(=N)の場合があるので、その全通りの組み合わせを求める。
- 旧作
- 年齢65歳以上
- 年齢18歳以下
※ 2.と3.は両方Yesがありえない条件だが、ここでは考慮しない。
ここで「直積」という考え方を使うとやりたいことができることを知った。
すなわち、Y/Nの2つの要素を持つ集合が3つあると考えると、直積を求めればY/Nの全通りの組み合わせを求めることができる。
Pythonではitertools.product
という関数を使えば以下のように簡単に書ける。
from itertools import product combinations = list(product(["Y", "N"], repeat=len(conditions)))
この時点で、combinations
の中身は以下の通り、Y/Nの全通りの組み合わせのリストになっている。
[('Y', 'Y', 'Y'), ('Y', 'Y', 'N'), ('Y', 'N', 'Y'), ('Y', 'N', 'N'), ('N', 'Y', 'Y'), ('N', 'Y', 'N'), ('N', 'N', 'Y'), ('N', 'N', 'N')]
※ 「直積」を知ったきっかけは以下の記事だった。
qiita.com
DataFrameでデシジョンテーブルを作成する。
pandasのDataFrameを使って、プログラム上でデシジョンテーブルを作成する。
※ 普段あまりpandasを使わないので、pandasの使い方はぎこちないかもしれない。
まずはDataFrameを作成してY/Nと条件名を紐付ける。
df = pd.DataFrame(combinations, columns=conditions)
この時点でdf
の中身は以下
旧作 年齢65歳以上 年齢18歳以下 0 Y Y Y 1 Y Y N 2 Y N Y 3 Y N N 4 N Y Y 5 N Y N 6 N N Y 7 N N N
次にアクション名のカラムを追加する。(値は空文字)
df = df.reindex(columns=conditions + actions, fill_value="")
この時点でdf
の中身は以下
旧作 年齢65歳以上 年齢18歳以下 半額 20%オフ 10%オフ 通常料金 0 Y Y Y 1 Y Y N 2 Y N Y 3 Y N N 4 N Y Y 5 N Y N 6 N N Y 7 N N N
最後に、縦と横が逆なので転置する。
df = df.T
これで以下のようにプログラム上でデシジョンテーブルの雛形を作成できた。
0 1 2 3 4 5 6 7 旧作 Y Y Y Y N N N N 年齢65歳以上 Y Y N N Y Y N N 年齢18歳以下 Y N Y N Y N Y N 半額 20%オフ 10%オフ 通常料金
Excelに出力する
「プログラムでのデシジョンテーブルの作り方」という意味ではここまでで終わりなのでおまけだが、最後にExcelに出力する。
ただPandasでExcelに出力するだけなら簡単だが、プログラムの中で列幅も調整すると以下のようにちょっとごちゃごちゃしたコードになった。
writer = pd.ExcelWriter("decision_table.xlsx") df.to_excel( writer, sheet_name="cases", header=[str(i + 1) for i in df], # Excel出力時はカラムがNo.1から始まるようにする。 ) # 条件名とアクション名を記載する列のwidthを指定する。 writer.sheets["cases"].set_column( 0, 0, df.index.map(len).max() * 2 # マルチバイト文字の場合は文字数の2倍のwidthにしたら丁度よかった。 ) for column_idx in df: # 各列のwidthを指定する。「Y」か「N」しか入らないことが分かっているので固定長でもいいが、 # 一応可変長に対応した書き方にした。 column_width = max(df[column_idx].astype(str).map(len).max(), len(str(column_idx))) writer.sheets["cases"].set_column(column_idx + 1, column_idx + 1, column_width) writer.save()
この辺は以下の記事を参考にさせて頂いた。