Python

Created a Pairwise Test Generation GUI Tool That Runs on Mac

The “pairwise method” for efficiently creating software test cases is a convenient technique that allows for maximum bug detection with fewer tests. This time, I created an app that can easily generate pairwise test cases as a GUI tool built in Python, running on Mac.

Since it’s GUI-based, even those not familiar with programming can operate it intuitively. In addition, it features manual adjustment of the coverage rate, as well as support for CSV and Excel import/export.

1. Code and UI Screenshots

1-1. Python Code (Tkinter + allpairspy + pandas)

This app is structured as follows:

  • GUI Construction: Tkinter

  • Test Case Generation: allpairspy (a pairwise method library)

  • Tabular Data Management: pandas

  • Excel Output: openpyxl

Below is the full source code:

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 - Pairwise Test Generation Tool")
        self.geometry("700x650")

        self.param_entries = []
        self.create_widgets()

    def create_widgets(self):
        lbl = tk.Label(self, text="Enter parameters and values (comma-separated)")
        lbl.pack(pady=10)

        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="Add Parameter", command=self.add_parameter)
        add_btn.pack(side='left', padx=5)

        generate_btn = ttk.Button(button_frame, text="Generate Test Cases", command=self.generate_test_cases)
        generate_btn.pack(side='left', padx=5)

        save_btn = ttk.Button(button_frame, text="Save as CSV", command=self.save_to_csv)
        save_btn.pack(side='left', padx=5)

        save_excel_btn = ttk.Button(button_frame, text="Save as Excel", command=self.save_to_excel)
        save_excel_btn.pack(side='left', padx=5)

        import_btn = ttk.Button(button_frame, text="Import CSV/Excel", command=self.import_values)
        import_btn.pack(side='left', padx=5)

        self.coverage_label = tk.Label(self, text="Coverage (manual input):")
        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"Parameter {len(self.param_entries)+1}")
        label_entry.pack(side="left", padx=5)

        value_entry = ttk.Entry(frame, width=50)
        value_entry.insert(0, "Value1, Value2, Value3")
        value_entry.pack(side="left", padx=5)

        delete_btn = ttk.Button(frame, text="Delete", 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"Values for {name} are empty")
                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("Please enter a coverage between 0 and 100")
                except ValueError:
                    messagebox.showerror("Error", "Please enter a number between 0 and 100 for coverage")
                    return
            else:
                manual_coverage = 100

            all_pairs = list(AllPairs(parameters))
            total_combinations = len(all_pairs)

            if manual_coverage == 100:
                pairs = all_pairs
            else:
                required_cases = int(total_combinations * (manual_coverage / 100))
                pairs = all_pairs[:required_cases]

            df = pd.DataFrame(pairs, columns=headers)
            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("Generated Test Cases (First 10)", preview)

        except Exception as e:
            messagebox.showerror("Error", str(e))

    def save_to_csv(self):
        if self.result is None:
            messagebox.showwarning("Warning", "Please generate the test cases first.")
            return

        file_path = filedialog.asksaveasfilename(defaultextension=".csv",
                                                 filetypes=[("CSV files", "*.csv")])
        if file_path:
            self.result.to_csv(file_path, index=False)
            messagebox.showinfo("Save Complete", f"Saved to {file_path}")

    def save_to_excel(self):
        if self.result is None:
            messagebox.showwarning("Warning", "Please generate the test cases first.")
            return

        file_path = filedialog.asksaveasfilename(defaultextension=".xlsx",
                                                 filetypes=[("Excel files", "*.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("Save Complete", f"Saved to {file_path}")

    def import_values(self):
        file_path = filedialog.askopenfilename(filetypes=[("CSV files", "*.csv"),
                                                          ("Excel files", "*.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("Import Complete", "File import completed.")
        except Exception as e:
            messagebox.showerror("Error", f"Failed to import file: {str(e)}")

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

 

 

1-2. Actual UI Screenshots

The GUI screen is structured as follows:

  • Easy parameter addition/deletion with buttons

  • Each parameter is entered with comma-separated values like “Value1, Value2, …”

  • Click “Generate Test Cases” to automatically show pairwise combinations

  • Enter coverage rate (e.g., 100, 80) to control the number of generated cases

  • Results can be saved in CSV or Excel format

  • Supports importing existing CSV or Excel files

3 parameters, 50% coverage

3 parameters, 100% coverage

 

2. Code Explanation (Decomposed by Function)

In this chapter, the structure of the tool will be explained in detail by each function.

2-1. GUI Construction (Tkinter)

The GUI uses Python’s standard Tkinter. Tkinter works easily on Mac as well, making it suitable for cross-platform use.

  • A scrollable input area is created using Frame, Canvas, and Scrollbar.

  • Each parameter is added as a pair of Entry (text input).

  • A “Delete” button is added to each row to allow flexible operations.

2-2. Pairwise Test Generation (AllPairs)

The pairwise combinations are generated using the allpairspy library.

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

 

The number of generated combinations is adjusted according to the manually entered coverage rate. If 100%, all combinations are shown; if less than that, only a subset is displayed.

2-3. Priority Control of Parameter Order

The generated test cases are reordered according to the user-input order of “Parameter 1, Parameter 2, Parameter 3…”.

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

 

2-4. Data Import / Export (CSV/Excel)

Using pandas, CSV/Excel files can be easily read and saved.

  • Save as CSV: to_csv()

  • Save as Excel: to_excel() + openpyxl

  • Import: Use read_csv() or read_excel() to reconstruct the parameter values

Since everything is GUI-based, the tool is designed to be user-friendly even for non-engineers.

 

3. List of Extended Features (Key Improvements in This Version)

This tool is not just a simple pairwise generation tool—it is designed with attention to the following “ease-of-use” features.

Feature Description
Add/Delete Parameters Easily done via GUI buttons
Coverage Rate Adjustment Allows manual input of desired coverage rate
Order Control Reflects parameter priority based on input order
Import Support Reads values automatically from CSV/Excel
Export Support Saves results in CSV or Excel format
Cross-platform Compatible with both Mac and Windows

 

 

4. How to Run and Required Libraries

Follow the steps below to run the tool.

Required Libraries (Pre-installation)

pip install allpairspy pandas openpyxl

 

Run Command

python3 test.py

 

If any errors occur and the tool does not run, please refer to the following article.

How to Fix the "_tkinter module not found" Error When Creating a Python GUI ToolThis article provides a detailed explanation of the cause and solution when the "_tkinter module not found" error occurs during Python GUI development in a Mac environment....