Python

Macで動作するペアワイズテスト生成GUIツールを作ってみた

ソフトウェアのテストケースを効率的に作成する「ペアワイズ法」は、少ないテスト数で最大限のバグ発見が期待できる便利な手法です。今回は、Mac環境で動作するPython製のGUIツールとして、ペアワイズテストケースを簡単に生成できるアプリを作成してみました。

GUIベースなので、プログラミングに詳しくなくても誰でも直感的に操作できるのがポイント。さらに、手動でカバレッジ率(網羅率)を調整できる機能や、CSV・Excelのインポート/エクスポート機能も備えています。

1. コードと動作画面

1-1. Pythonコード(Tkinter + allpairspy + pandas)

今回のアプリは以下のような構成になっています。

  • GUI構築:Tkinter

  • テストケース生成:allpairspy(ペアワイズ法のライブラリ)

  • 表形式データ管理:pandas

  • Excel出力:openpyxl

以下がすべてのソースコードです。

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import pandas as pd
from allpairspy import AllPairs
import openpyxl

class MiniPictApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("MiniPict - ペアワイズテスト生成ツール")
        self.geometry("700x650")

        self.param_entries = []
        self.create_widgets()

    def create_widgets(self):
        # パラメータ入力欄ラベル
        lbl = tk.Label(self, text="パラメータと値を入力(カンマ区切り)")
        lbl.pack(pady=10)

        # スクロール可能なFrame
        frame = tk.Frame(self)
        frame.pack(expand=True, fill='both', padx=10)

        canvas = tk.Canvas(frame)
        scrollbar = ttk.Scrollbar(frame, orient="vertical", command=canvas.yview)
        scrollable_frame = tk.Frame(canvas)

        scrollable_frame.bind(
            "<Configure>",
            lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
        )

        canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
        canvas.configure(yscrollcommand=scrollbar.set)

        canvas.pack(side="left", fill="both", expand=True)
        scrollbar.pack(side="right", fill="y")

        self.scrollable_frame = scrollable_frame

        # ボタンたち
        button_frame = tk.Frame(self)
        button_frame.pack(pady=10)

        add_btn = ttk.Button(button_frame, text="パラメータを追加", command=self.add_parameter)
        add_btn.pack(side='left', padx=5)

        generate_btn = ttk.Button(button_frame, text="テストケース生成", command=self.generate_test_cases)
        generate_btn.pack(side='left', padx=5)

        save_btn = ttk.Button(button_frame, text="CSVとして保存", command=self.save_to_csv)
        save_btn.pack(side='left', padx=5)

        save_excel_btn = ttk.Button(button_frame, text="Excelとして保存", command=self.save_to_excel)
        save_excel_btn.pack(side='left', padx=5)

        import_btn = ttk.Button(button_frame, text="CSV/Excelのインポート", command=self.import_values)
        import_btn.pack(side='left', padx=5)

        # カバレッジ入力欄
        self.coverage_label = tk.Label(self, text="カバレッジ(手動入力):")
        self.coverage_label.pack(pady=10)

        self.coverage_entry = ttk.Entry(self, width=20)
        self.coverage_entry.pack(pady=5)

        self.result = None

    def add_parameter(self):
        frame = tk.Frame(self.scrollable_frame)
        frame.pack(pady=5, anchor='w')

        label_entry = ttk.Entry(frame, width=20)
        label_entry.insert(0, f"パラメータ{len(self.param_entries)+1}")
        label_entry.pack(side="left", padx=5)

        value_entry = ttk.Entry(frame, width=50)
        value_entry.insert(0, "値1, 値2, 値3")
        value_entry.pack(side="left", padx=5)

        delete_btn = ttk.Button(frame, text="削除", command=lambda: self.delete_parameter(frame))
        delete_btn.pack(side="left", padx=5)

        self.param_entries.append((label_entry, value_entry, frame))

    def delete_parameter(self, frame):
        # パラメータ削除処理
        for entry in self.param_entries:
            if entry == frame:  # 対象のフレームを見つけたら
                self.param_entries.remove(entry)  # リストから削除
                break
        frame.destroy()  # フレームを削除

    def generate_test_cases(self):
        try:
            parameters = []
            headers = []

            for label_entry, value_entry, _ in self.param_entries:
                name = label_entry.get()
                values = [v.strip() for v in value_entry.get().split(",") if v.strip()]
                if not values:
                    raise ValueError(f"{name} の値が空です")
                headers.append(name)
                parameters.append(values)

            # カバレッジ率の入力と検証
            manual_coverage = self.coverage_entry.get().strip()
            if manual_coverage:
                try:
                    manual_coverage = float(manual_coverage)
                    if not (0 <= manual_coverage <= 100):
                        raise ValueError("カバレッジは0から100の範囲で入力してください")
                except ValueError:
                    messagebox.showerror("エラー", "カバレッジは0から100の数字で入力してください")
                    return
            else:
                manual_coverage = 100  # デフォルトで100%カバレッジ

            # ペアワイズ組み合わせの生成
            all_pairs = list(AllPairs(parameters))
            total_combinations = len(all_pairs)

            # 必要なテストケースの数
            if manual_coverage == 100:
                pairs = all_pairs  # 100%カバレッジの場合、全ての組み合わせ
            else:
                required_cases = int(total_combinations * (manual_coverage / 100))
                pairs = all_pairs[:required_cases]

            # DataFrameの作成
            df = pd.DataFrame(pairs, columns=headers)

            # パラメータ1、パラメータ2、パラメータ3の順で並び替え
            df = df.sort_values(by=headers[:len(parameters)], ascending=True).reset_index(drop=True)

            self.result = df

            # 結果表示(ダイアログ)
            preview = "\n".join(df.head(10).astype(str).agg(" | ".join, axis=1).tolist())
            messagebox.showinfo("テストケース生成結果(先頭10件)", preview)

        except Exception as e:
            messagebox.showerror("エラー", str(e))

    def save_to_csv(self):
        if self.result is None:
            messagebox.showwarning("警告", "まずはテストケースを生成してください。")
            return

        file_path = filedialog.asksaveasfilename(defaultextension=".csv",
                                                 filetypes=[("CSVファイル", "*.csv")])
        if file_path:
            self.result.to_csv(file_path, index=False)
            messagebox.showinfo("保存完了", f"{file_path} に保存しました。")

    def save_to_excel(self):
        if self.result is None:
            messagebox.showwarning("警告", "まずはテストケースを生成してください。")
            return

        file_path = filedialog.asksaveasfilename(defaultextension=".xlsx",
                                                 filetypes=[("Excelファイル", "*.xlsx")])
        if file_path:
            with pd.ExcelWriter(file_path, engine='openpyxl') as writer:
                self.result.to_excel(writer, index=False, sheet_name='Test Cases')
            messagebox.showinfo("保存完了", f"{file_path} に保存しました。")

    def import_values(self):
        file_path = filedialog.askopenfilename(filetypes=[("CSVファイル", "*.csv"),
                                                          ("Excelファイル", "*.xlsx")])
        if not file_path:
            return

        try:
            if file_path.endswith(".csv"):
                data = pd.read_csv(file_path)
            else:
                data = pd.read_excel(file_path)

            # インポートした値でパラメータを更新
            for idx, col in enumerate(data.columns):
                if idx >= len(self.param_entries):
                    self.add_parameter()
                self.param_entries[idx].delete(0, tk.END)
                self.param_entries[idx].insert(0, ", ".join(data[col].dropna().astype(str).tolist()))

            messagebox.showinfo("インポート完了", "ファイルのインポートが完了しました。")
        except Exception as e:
            messagebox.showerror("エラー", f"ファイルのインポートに失敗しました: {str(e)}")

if __name__ == "__main__":
    app = MiniPictApp()
    app.mainloop()

 

 

1-2. 実際の動作画面

GUIの画面は以下のような構成です。

  • パラメータの追加・削除がボタンで簡単

  • 各パラメータには「値1, 値2, …」のようにカンマ区切りで入力

  • 「テストケース生成」で自動的にペアワイズの組み合わせが表示

  • カバレッジ率(例:100、80など)を入力して、生成するケース数を制御

  • 結果はCSVやExcel形式で保存可能

  • 既存のCSVやExcelファイルの読み込み(インポート)にも対応

パラメータ3つ、カバレッジ50

パラメータ3つ、カバレッジ100

 

2. コード解説(機能別に分解)

この章では、ツールの構成を機能ごとに詳しく解説していきます。

2-1. GUI構築(Tkinter)

GUIにはPython標準のTkinterを使用しています。TkinterはMacでも簡単に動作するため、クロスプラットフォームでの利用に向いています。

  • Frame, Canvas, Scrollbar でスクロール可能な入力エリアを作成

  • 各パラメータごとに Entry(テキスト入力)をペアで追加

  • 各行には「削除」ボタンを追加して柔軟に操作できるようにしています

2-2. ペアワイズテストの生成(AllPairs)

ペアワイズの組み合わせは allpairspy ライブラリを利用しています。

from allpairspy import AllPairs
...
all_pairs = list(AllPairs(parameters))

 

カバレッジ率(手動で入力)に応じて生成される組み合わせ数を調整し、100%なら全件、それ以下なら一部のみ表示されます。

2-3. 並び順の優先度を制御

生成されたテストケースは、ユーザーが入力した「パラメータ1、パラメータ2、パラメータ3…」の順番を尊重して並び替えます。

df = df.sort_values(by=headers[:len(parameters)], ascending=True).reset_index(drop=True)

 

2-4. データのインポート / エクスポート(CSV・Excel)

pandasを使って簡単にCSV/Excelの読み込み・保存を行えるようにしています。

  • CSV保存:to_csv()

  • Excel保存:to_excel() + openpyxl

  • インポート:read_csv() または read_excel() を用いてパラメータ値を再構成

GUIベースで完結するため、非エンジニアの方にも優しい設計になっています。

 

3. 拡張した機能一覧(今回の工夫ポイント)

本ツールは単なるペアワイズ生成ツールではなく、以下のような「使い勝手の良さ」にこだわって設計しています。

機能 説明
パラメータの追加・削除 GUIボタンで簡単に行える
カバレッジ率の調整 任意の網羅率を手動入力で設定可能
並び順の制御 入力順通りにパラメータ優先度を反映
インポート対応 CSV / Excel を読み込んで値を自動入力
エクスポート対応 結果をCSVまたはExcel形式で保存可能
クロスプラットフォーム Mac / Windows 両対応

 

 

4. 実行方法と必要なライブラリ

以下の手順で動かせます。

必要なライブラリ(事前インストール)

pip install allpairspy pandas openpyxl

 

実行コマンド

python3 test.py

 

もしエラーが発生して動作できない場合には下記の記事を参考にしてみてください。

PythonのGUIツール作成で「_tkinterモジュールが見つからない」エラーが発生した場合の対処方法Mac環境でPythonのGUI開発時に「_tkinterモジュールが見つからない」エラーが出た際の原因と対処方法を詳しく解説します。...
ABOUT ME
りん
このブログでは、Web開発やプログラミングに関する情報を中心に、私が日々感じたことや学んだことをシェアしています。技術と生活の両方を楽しめるブログを目指して、日常で触れた出来事や本、ゲームの話題も取り入れています。気軽に覗いて、少しでも役立つ情報や楽しいひとときを見つけてもらえたら嬉しいです。