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()
orread_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.
