FreeMindを使ってテスト設計技法クラシフィケーションツリー法のツールを作る

ソフトウェアテストの小ネタ Advent Calendar 2017 - Qiitaの7日目の記事です。

テスト業務でFreeMindを使っている現場をちらほら見ます。このFreeMindについてですが、中身はテキストベースのXMLフォーマットなので、容易に読み出しや変更を行えます。XMLパーサでスクリプトを組めば、自動で他の成果物と連携させたり、ツリーの整合性を維持したりできるようになります。
今回は実践例として、このFreeMindを使ってテスト設計技法であるクラシフィケーションツリー法の簡易的なツールを作ります(クラシフィケーションツリー法の詳細は「クラシフィケーション・ツリー法入門」を参照ください。題材の完成形は「https://github.com/hiro-iseri/fm_ctm」)。

1. FreeMindクラシフィケーションツリーを表現する。

まずFreeMindクラシフィケーションツリーを描きます。
クラシフィケーションツリーの表現では、最低限クラスとクラシフィケーションの区別が必要です。その区別は一般的には枠線の形で行いますが、今回は区別に操作容易なアイコンを用います。具体的には、テストの入力に指定する末端のクラシフィケーションに、フォルダアイコンを付与する表記ルールを取ります。
例題として、ラーメン二郎のツリーを描いてみました。


2. Freemindのツリーを読み込む

次にFreeMindから、テスト条件となるクラシフィケーションとクラスの抽出を行います。
今回はPythonXMLパーサを用います。処理としては、マインドマップのノードを再帰的に巡回し、フォルダーアイコンが付与されたノード(属性BUILTINに「"folder"」格納)と、その子ノードのテキストを抽出していきます。
コードは例えば以下のようになります。ファイルパスinput_fileのファイルを解析し、クラシフィケーション名をキー、クラス名のリストを値とするディクショナリを_clsf_dictに格納しています。

import xml.etree.ElementTree as ET

_clsf_dict = {}

def _get_testcon_from_node(parent):
    """FreeMindのノードを再帰的に巡回。クラシフィケーションとクラスの組を_clsf_dictへ格納"""

    if [x for x in parent if x.attrib == {'BUILTIN': 'folder'}]:
        class_list = []
        for node in list(parent):
            if 'TEXT' in node.attrib:
                class_list.append(node.attrib['TEXT'].encode(sys.stdout.encoding))
        cf_text = parent.attrib['TEXT'].encode(sys.stdout.encoding)
        _clsf_dict[cf_text] = class_list
    else:
        for node in list(parent):
            _get_testcon_from_node(node)
    return _clsf_dict

...
cls_tree = ET.parse(input_file)
_get_testcon_from_node(cls_tree.getroot())
# _clsf_dictに結果格納
...

3. ツリー結果をPICTに入力し、テスト条件リストを生成する。

最後にテスト条件一覧を出力します。
今回はFreeMindから抽出したクラシフィケーションとクラスの組を、組み合わせテストツールPICTに入力して、2ワイズカバレッジ100%のテスト条件組み合わせを出力させます。
コードは例えば以下のようになります。
前述の「2.」のディクショナリclsf_dictを入力にして、結果を標準出力に出力しています。

def _print_testcondition(clsf_dict):
    """クラシフィケーションとクラスの組をインプットにpictを実行。その結果を標準出力に出力"""
    # クラスとクラシフィケーションの組をpict形式ファイルtemp.csvとして保存
    with open("temp.csv", "w") as pict_input_file:
        for key, classlist in clsf_dict.iteritems():
            line = key + ':' + ",".join(classlist) + '\n'
            pict_input_file.write(line)
    subprocess.Popen("pict temp.csv", shell=True)

表示例は以下の通りです。

豚        アブラ  野菜    ニンニク        辛め    麺      サイズ
豚W     なし    増し    増し    なし    普通    小
普通    増し    あり    なし    あり    硬め    大
豚入り  あり    増し    あり    増し    硬め    大
豚入り  あり    なし    なし    あり    普通    小
普通    なし    なし    増し    増し    普通    大
豚W     なし    あり    なし    増し    硬め    小
豚W     あり    なし    あり    なし    硬め    大
豚入り  あり    あり    増し    あり    硬め    小
豚入り  増し    あり    あり    なし    普通    小
豚W     増し    増し    あり    あり    普通    大
普通    なし    増し    あり    あり    硬め    小
豚入り  なし    増し    なし    なし    硬め    小
豚W     増し    なし    増し    増し    硬め    小
普通    あり    なし    あり    なし    普通    大

コード完成形

題材を実行可能にしたコードを以下に置いています。

https://github.com/hiro-iseri/fm_ctm

python fmctm.py マインドマップファイル」で実行します。