From b11cfd66b185a253fecf10ad06080ae165f32a74 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sun, 19 Apr 2026 16:12:02 -0500 Subject: feat: adopt pairwise-tests (PICT combinatorial) + cross-reference from existing testing skills MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Forked verbatim from omkamal/pypict-claude-skill (MIT). LICENSE preserved. Renamed from `pict-test-designer` to `pairwise-tests` — technique-first naming so users invoking "pairwise" or "combinatorial" find it; PICT remains the tool under the hood. Bundle (skill-runtime only): pairwise-tests/SKILL.md (renamed, description rewritten) pairwise-tests/LICENSE (MIT, preserved) pairwise-tests/references/pict_syntax.md pairwise-tests/references/examples.md pairwise-tests/scripts/pict_helper.py (Python CLI for model gen / output fmt) pairwise-tests/scripts/README.md Upstream's repo-level docs (README, QUICKSTART, CONTRIBUTING, etc.) and `examples/` dir (ATM + gearbox walkthroughs — useful as reading, not as skill-runtime) omitted from the fork. Attribution footer added. Cross-references so /add-tests naturally routes to /pairwise-tests when warranted: - add-tests/SKILL.md Phase 2 step 8: if a function in scope has 3+ parameters each taking multiple values, surface `/pairwise-tests` to the user before proposing normal category coverage. Default continues with /add-tests; user picks pairwise explicitly. - claude-rules/testing.md: new "Combinatorial Coverage" section after the Normal/Boundary/Error categories. Explains when pairwise wins, when to skip (regulated / provably exhaustive contexts, ≤2 parameters, non- parametric testing), and points at /pairwise-tests. - languages/python/claude/rules/python-testing.md: new "Pairwise / Combinatorial for Parameter-Heavy Functions" subsection under the parametrize guidance. Explains the pytest workflow: /pairwise-tests generates the matrix, paste into pytest parametrize block, or use pypict helper directly. Mechanism note: cross-references are judgment-based — Claude reads the nudges in add-tests/testing/python-testing and acts on them when appropriate, not automatic dispatch. Craig can still invoke /pairwise-tests directly when he already knows he wants combinatorial coverage. Makefile SKILLS extended; make install symlinks /pairwise-tests globally. --- Makefile | 2 +- add-tests/SKILL.md | 5 +- claude-rules/testing.md | 20 ++ languages/python/claude/rules/python-testing.md | 16 + pairwise-tests/LICENSE | 35 +++ pairwise-tests/SKILL.md | 373 ++++++++++++++++++++++++ pairwise-tests/references/examples.md | 98 +++++++ pairwise-tests/references/pict_syntax.md | 83 ++++++ pairwise-tests/scripts/README.md | 77 +++++ pairwise-tests/scripts/pict_helper.py | 207 +++++++++++++ 10 files changed, 913 insertions(+), 3 deletions(-) create mode 100644 pairwise-tests/LICENSE create mode 100644 pairwise-tests/SKILL.md create mode 100644 pairwise-tests/references/examples.md create mode 100644 pairwise-tests/references/pict_syntax.md create mode 100644 pairwise-tests/scripts/README.md create mode 100644 pairwise-tests/scripts/pict_helper.py diff --git a/Makefile b/Makefile index fdd120e..37f9d84 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ RULES_DIR := $(HOME)/.claude/rules SKILLS := c4-analyze c4-diagram debug add-tests respond-to-review review-pr fix-issue security-check \ arch-design arch-decide arch-document arch-evaluate \ brainstorm codify root-cause-trace five-whys prompt-engineering \ - playwright-js playwright-py frontend-design + playwright-js playwright-py frontend-design pairwise-tests RULES := $(wildcard claude-rules/*.md) LANGUAGES := $(notdir $(wildcard languages/*)) diff --git a/add-tests/SKILL.md b/add-tests/SKILL.md index 837810e..5803dd8 100644 --- a/add-tests/SKILL.md +++ b/add-tests/SKILL.md @@ -28,8 +28,9 @@ Work through four phases in order. Do not skip phases. 6. Present a test plan as a markdown table with columns: File, Functions, Current Coverage, Proposed Tests, Priority (P0/P1/P2). 7. For each proposed test, specify: target function, category (Normal / Boundary / Error), one-line description, and any mocking required. All three categories are required per function — see `testing.md` for category definitions. -8. For large codebases, group the plan into batches (e.g., "Batch 1: core domain models, Batch 2: API endpoints"). -9. **Stop and ask the user for confirmation.** The user may add, remove, or reprioritize tests before you proceed. Do not write any test files until the user approves. +8. **If a function has 3+ parameters with multiple values each, surface `/pairwise-tests`.** For that function, hand-writing exhaustive or even category-based cases risks either an explosion (N × M exhaustive) or under-coverage (3-5 ad-hoc tests miss pair interactions). Tell the user: "Function `` has N parameters — would you rather generate a pairwise-covering matrix via `/pairwise-tests` for that function, or continue with normal `/add-tests` category coverage?" Default to continuing unless they pick pairwise. When in doubt, suggest pairwise for the combinatorial function and fall back to `/add-tests` for the rest. +9. For large codebases, group the plan into batches (e.g., "Batch 1: core domain models, Batch 2: API endpoints"). +10. **Stop and ask the user for confirmation.** The user may add, remove, or reprioritize tests before you proceed. Do not write any test files until the user approves. ### Phase 3: Write diff --git a/claude-rules/testing.md b/claude-rules/testing.md index f67ace2..b91b76c 100644 --- a/claude-rules/testing.md +++ b/claude-rules/testing.md @@ -58,6 +58,26 @@ Every unit under test requires coverage across three categories: - Resource exhaustion - Malformed data +## Combinatorial Coverage + +For functions with 3+ parameters that each take multiple values (feature-flag +combinations, config matrices, permission/role interactions, multi-field +form validation, API parameter spaces), the exhaustive test count explodes +(M^N) while 3-5 ad-hoc cases miss pair interactions. Use **pairwise / +combinatorial testing** — generate a minimal matrix that hits every 2-way +combination of parameter values. Empirically catches 60-90% of combinatorial +bugs with 80-99% fewer tests. + +Invoke `/pairwise-tests` on the offending function; continue using `/add-tests` +and the Normal/Boundary/Error discipline for the rest. The two approaches +complement: pairwise covers parameter *interactions*; category discipline +covers each parameter's individual edge space. + +Skip pairwise when: the function has 1-2 parameters (just write the cases), +the context requires *provably* exhaustive coverage (regulated systems — document +in an ADR), or the testing target is non-parametric (single happy path, +performance regression, a specific error). + ## Test Organization Typical layout: diff --git a/languages/python/claude/rules/python-testing.md b/languages/python/claude/rules/python-testing.md index 6f04b7f..d2342c1 100644 --- a/languages/python/claude/rules/python-testing.md +++ b/languages/python/claude/rules/python-testing.md @@ -66,6 +66,22 @@ def test_add_item_quantity_validation(cart, quantity, valid): cart.add("SKU-1", quantity=quantity) ``` +### Pairwise / Combinatorial for Parameter-Heavy Functions + +When `@pytest.mark.parametrize` would require listing dozens of combinations +(feature flags × permissions × shipping × payment × etc.), switch to +combinatorial coverage via `/pairwise-tests`. The skill generates a minimal +matrix covering every 2-way parameter interaction — typically 80-99% fewer +cases than exhaustive, catching most combinatorial bugs. + +Workflow: invoke `/pairwise-tests` → get a PICT model + generated test matrix +→ paste the matrix into a pytest parametrize block, or use the helper to +emit directly. The `pypict` package (`pip install pypict`) handles +generation in-process. + +See `testing.md` § Combinatorial Coverage for the general rule and when +to skip. + ## Mocking Guidelines ### Mock these (external boundaries): diff --git a/pairwise-tests/LICENSE b/pairwise-tests/LICENSE new file mode 100644 index 0000000..e40dc44 --- /dev/null +++ b/pairwise-tests/LICENSE @@ -0,0 +1,35 @@ +MIT License + +Copyright (c) 2025 pypict-claude-skill contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--- + +This project uses and acknowledges the following tools: + +PICT (Pairwise Independent Combinatorial Testing) +Copyright (c) Microsoft Corporation +Licensed under the MIT License +https://github.com/microsoft/pict + +pypict - Python binding for PICT +Copyright (c) Kenichi Maehashi +Licensed under the MIT License +https://github.com/kmaehashi/pypict diff --git a/pairwise-tests/SKILL.md b/pairwise-tests/SKILL.md new file mode 100644 index 0000000..e7b45a1 --- /dev/null +++ b/pairwise-tests/SKILL.md @@ -0,0 +1,373 @@ +--- +name: pairwise-tests +description: Generate a minimal test matrix covering all pairwise parameter interactions using PICT (Pairwise Independent Combinatorial Testing). Given a function, feature, or configuration space with many input parameters, identifies parameters + value partitions + inter-parameter constraints, builds a PICT model, and produces a small set of test cases (typically 80-99% fewer than exhaustive) that still hits every 2-way combination of parameter values. Empirically catches 60-90% of combinatorial bugs. Use when a function has 3+ parameters with multiple values each and exhaustive testing would explode (feature flag combinations, permission/role matrices, config matrices, multi-field form validation, API parameter spaces). Do NOT use for functions with 1-2 parameters (just write the cases directly), for regulated contexts where provably exhaustive coverage is required (document as an ADR and write all cases), or for testing non-parametric behavior (happy path, single error case, performance regression — use `/add-tests` for those). Output: PICT model, markdown table of generated test cases, expected result per row. See also `/add-tests` for standard test generation and `testing.md` for the Normal/Boundary/Error category discipline. +--- + +# Pairwise Tests (PICT) + +This skill enables systematic test case design using PICT (Pairwise Independent Combinatorial Testing). Given requirements or code, it analyzes the system to identify test parameters, generates a PICT model with appropriate constraints, executes the model to generate pairwise test cases, and formats the results with expected outputs. + +## When to Use This Skill + +Use this skill when: +- Designing test cases for a feature, function, or system with multiple input parameters +- Creating test suites for configurations with many combinations +- Needing comprehensive coverage with minimal test cases +- Analyzing requirements to identify test scenarios +- Working with code that has multiple conditional paths +- Building test matrices for API endpoints, web forms, or system configurations + +## Workflow + +Follow this process for test design: + +### 1. Analyze Requirements or Code + +From the user's requirements or code, identify: +- **Parameters**: Input variables, configuration options, environmental factors +- **Values**: Possible values for each parameter (using equivalence partitioning) +- **Constraints**: Business rules, technical limitations, dependencies between parameters +- **Expected Outcomes**: What should happen for different combinations + +**Example Analysis:** + +For a login function with requirements: +- Users can login with username/password +- Supports 2FA (on/off) +- Remembers login on trusted devices +- Rate limits after 3 failed attempts + +Identified parameters: +- Credentials: Valid, Invalid +- TwoFactorAuth: Enabled, Disabled +- RememberMe: Checked, Unchecked +- PreviousFailures: 0, 1, 2, 3, 4 + +### 2. Generate PICT Model + +Create a PICT model with: +- Clear parameter names +- Well-defined value sets (using equivalence partitioning and boundary values) +- Constraints for invalid combinations +- Comments explaining business rules + +**Model Structure:** +``` +# Parameter definitions +ParameterName: Value1, Value2, Value3 + +# Constraints (if any) +IF [Parameter1] = "Value" THEN [Parameter2] <> "OtherValue"; +``` + +**Refer to references/pict_syntax.md for:** +- Complete syntax reference +- Constraint grammar and operators +- Advanced features (sub-models, aliasing, negative testing) +- Command-line options +- Detailed constraint patterns + +**Refer to references/examples.md for:** +- Complete real-world examples by domain +- Software function testing examples +- Web application, API, and mobile testing examples +- Database and configuration testing patterns +- Common patterns for authentication, resource access, error handling + +### 3. Execute PICT Model + +Generate the PICT model text and format it for the user. You can use Python code directly to work with the model: + +```python +# Define parameters and constraints +parameters = { + "OS": ["Windows", "Linux", "MacOS"], + "Browser": ["Chrome", "Firefox", "Safari"], + "Memory": ["4GB", "8GB", "16GB"] +} + +constraints = [ + 'IF [OS] = "MacOS" THEN [Browser] IN {Safari, Chrome}', + 'IF [Memory] = "4GB" THEN [OS] <> "MacOS"' +] + +# Generate model text +model_lines = [] +for param_name, values in parameters.items(): + values_str = ", ".join(values) + model_lines.append(f"{param_name}: {values_str}") + +if constraints: + model_lines.append("") + for constraint in constraints: + if not constraint.endswith(';'): + constraint += ';' + model_lines.append(constraint) + +model_text = "\n".join(model_lines) +print(model_text) +``` + +**Using the helper script (optional):** +The `scripts/pict_helper.py` script provides utilities for model generation and output formatting: + +```bash +# Generate model from JSON config +python scripts/pict_helper.py generate config.json + +# Format PICT tool output as markdown table +python scripts/pict_helper.py format output.txt + +# Parse PICT output to JSON +python scripts/pict_helper.py parse output.txt +``` + +**To generate actual test cases**, the user can: +1. Save the PICT model to a file (e.g., `model.txt`) +2. Use online PICT tools like: + - https://pairwise.yuuniworks.com/ + - https://pairwise.teremokgames.com/ +3. Or install PICT locally (see references/pict_syntax.md) + +### 4. Determine Expected Outputs + +For each generated test case, determine the expected outcome based on: +- Business requirements +- Code logic +- Valid/invalid combinations + +Create a list of expected outputs corresponding to each test case. + +### 5. Format Complete Test Suite + +Provide the user with: +1. **PICT Model** - The complete model with parameters and constraints +2. **Markdown Table** - Test cases in table format with test numbers +3. **Expected Outputs** - Expected result for each test case + +## Output Format + +Present results in this structure: + +````markdown +## PICT Model + +``` +# Parameters +Parameter1: Value1, Value2, Value3 +Parameter2: ValueA, ValueB + +# Constraints +IF [Parameter1] = "Value1" THEN [Parameter2] = "ValueA"; +``` + +## Generated Test Cases + +| Test # | Parameter1 | Parameter2 | Expected Output | +| --- | --- | --- | --- | +| 1 | Value1 | ValueA | Success | +| 2 | Value2 | ValueB | Success | +| 3 | Value1 | ValueB | Error: Invalid combination | +... + +## Test Case Summary + +- Total test cases: N +- Coverage: Pairwise (all 2-way combinations) +- Constraints applied: N +```` + +## Best Practices + +### Parameter Identification + +**Good:** +- Use descriptive names: `AuthMethod`, `UserRole`, `PaymentType` +- Apply equivalence partitioning: `FileSize: Small, Medium, Large` instead of `FileSize: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10` +- Include boundary values: `Age: 0, 17, 18, 65, 66` +- Add negative values for error testing: `Amount: ~-1, 0, 100, ~999999` + +**Avoid:** +- Generic names: `Param1`, `Value1`, `V1` +- Too many values without partitioning +- Missing edge cases + +### Constraint Writing + +**Good:** +- Document rationale: `# Safari only available on MacOS` +- Start simple, add incrementally +- Test constraints work as expected + +**Avoid:** +- Over-constraining (eliminates too many valid combinations) +- Under-constraining (generates invalid test cases) +- Complex nested logic without clear documentation + +### Expected Output Definition + +**Be specific:** +- "Login succeeds, user redirected to dashboard" +- "HTTP 400: Invalid credentials error" +- "2FA prompt displayed" + +**Not vague:** +- "Works" +- "Error" +- "Success" + +### Scalability + +For large parameter sets: +- Use sub-models to group related parameters with different orders +- Consider separate test suites for unrelated features +- Start with order 2 (pairwise), increase for critical combinations +- Typical pairwise testing reduces test cases by 80-90% vs exhaustive + +## Common Patterns + +### Web Form Testing + +```python +parameters = { + "Name": ["Valid", "Empty", "TooLong"], + "Email": ["Valid", "Invalid", "Empty"], + "Password": ["Strong", "Weak", "Empty"], + "Terms": ["Accepted", "NotAccepted"] +} + +constraints = [ + 'IF [Terms] = "NotAccepted" THEN [Name] = "Valid"', # Test validation even if terms not accepted +] +``` + +### API Endpoint Testing + +```python +parameters = { + "HTTPMethod": ["GET", "POST", "PUT", "DELETE"], + "Authentication": ["Valid", "Invalid", "Missing"], + "ContentType": ["JSON", "XML", "FormData"], + "PayloadSize": ["Empty", "Small", "Large"] +} + +constraints = [ + 'IF [HTTPMethod] = "GET" THEN [PayloadSize] = "Empty"', + 'IF [Authentication] = "Missing" THEN [HTTPMethod] IN {GET, POST}' +] +``` + +### Configuration Testing + +```python +parameters = { + "Environment": ["Dev", "Staging", "Production"], + "CacheEnabled": ["True", "False"], + "LogLevel": ["Debug", "Info", "Error"], + "Database": ["SQLite", "PostgreSQL", "MySQL"] +} + +constraints = [ + 'IF [Environment] = "Production" THEN [LogLevel] <> "Debug"', + 'IF [Database] = "SQLite" THEN [Environment] = "Dev"' +] +``` + +## Troubleshooting + +### No Test Cases Generated + +- Check constraints aren't over-restrictive +- Verify constraint syntax (must end with `;`) +- Ensure parameter names in constraints match definitions (use `[ParameterName]`) + +### Too Many Test Cases + +- Verify using order 2 (pairwise) not higher order +- Consider breaking into sub-models +- Check if parameters can be separated into independent test suites + +### Invalid Combinations in Output + +- Add missing constraints +- Verify constraint logic is correct +- Check if you need to use `NOT` or `<>` operators + +### Script Errors + +- Ensure pypict is installed: `pip install pypict --break-system-packages` +- Check Python version (3.7+) +- Verify model syntax is valid + +## References + +- **references/pict_syntax.md** - Complete PICT syntax reference with grammar and operators +- **references/examples.md** - Comprehensive real-world examples across different domains +- **scripts/pict_helper.py** - Python utilities for model generation and output formatting +- [PICT GitHub Repository](https://github.com/microsoft/pict) - Official PICT documentation +- [pypict Documentation](https://github.com/kmaehashi/pypict) - Python binding documentation +- [Online PICT Tools](https://pairwise.yuuniworks.com/) - Web-based PICT generator + +## Examples + +### Example 1: Simple Function Testing + +**User Request:** "Design tests for a divide function that takes two numbers and returns the result." + +**Analysis:** +- Parameters: dividend (number), divisor (number) +- Values: Using equivalence partitioning and boundaries + - Numbers: negative, zero, positive, large values +- Constraints: Division by zero is invalid +- Expected outputs: Result or error + +**PICT Model:** +``` +Dividend: -10, 0, 10, 1000 +Divisor: ~0, -5, 1, 5, 100 + +IF [Divisor] = "0" THEN [Dividend] = "10"; +``` + +**Test Cases:** + +| Test # | Dividend | Divisor | Expected Output | +| --- | --- | --- | --- | +| 1 | 10 | 0 | Error: Division by zero | +| 2 | -10 | 1 | -10.0 | +| 3 | 0 | -5 | 0.0 | +| 4 | 1000 | 5 | 200.0 | +| 5 | 10 | 100 | 0.1 | + +### Example 2: E-commerce Checkout + +**User Request:** "Design tests for checkout flow with payment methods, shipping options, and user types." + +**Analysis:** +- Payment: Credit Card, PayPal, Bank Transfer (limited by user type) +- Shipping: Standard, Express, Overnight +- User: Guest, Registered, Premium +- Constraints: Guests can't use Bank Transfer, Premium users get free Express + +**PICT Model:** +``` +PaymentMethod: CreditCard, PayPal, BankTransfer +ShippingMethod: Standard, Express, Overnight +UserType: Guest, Registered, Premium + +IF [UserType] = "Guest" THEN [PaymentMethod] <> "BankTransfer"; +IF [UserType] = "Premium" AND [ShippingMethod] = "Express" THEN [PaymentMethod] IN {CreditCard, PayPal}; +``` + +**Output:** 12-15 test cases covering all valid payment/shipping/user combinations with expected costs and outcomes. + +--- + +## Attribution + +Forked from [omkamal/pypict-claude-skill](https://github.com/omkamal/pypict-claude-skill) — MIT licensed. See `LICENSE` in this directory for the original copyright and terms. + +**Local changes:** +- Skill renamed from `pict-test-designer` to `pairwise-tests`. PICT remains the implementation; the user-facing name leads with the technique (pairwise / combinatorial) rather than the tool brand. +- Description rewritten to lead with the "what" (pairwise coverage, 2-way interaction), include explicit negative triggers (≤2 parameters, regulated contexts requiring exhaustive coverage, non-parametric testing), and cross-reference `/add-tests` and `testing.md`. +- Omitted from the fork: `examples/` directory (ATM + gearbox worked examples — useful as a walkthrough, not needed at skill-runtime) and repo-level metadata (README, QUICKSTART, CONTRIBUTING, releases/, .claude-plugin/). The skill-runtime files — SKILL.md, LICENSE, `references/pict_syntax.md`, `references/examples.md`, `scripts/pict_helper.py` + `scripts/README.md` — are intact. diff --git a/pairwise-tests/references/examples.md b/pairwise-tests/references/examples.md new file mode 100644 index 0000000..258b1ef --- /dev/null +++ b/pairwise-tests/references/examples.md @@ -0,0 +1,98 @@ +# PICT Examples Reference + +> **Note**: This is a placeholder file. Comprehensive examples are coming soon! +> +> For now, check out the [examples directory](../examples/) for complete real-world examples. + +## Available Examples + +### Complete Examples +- **[ATM System Testing](../examples/atm-specification.md)**: Comprehensive banking ATM system with 31 test cases + +### Coming Soon + +#### Software Testing +- Function testing with multiple parameters +- API endpoint testing +- Database query validation +- Algorithm testing + +#### Web Applications +- Form validation +- User authentication +- E-commerce checkout +- Shopping cart operations + +#### Configuration Testing +- System configurations +- Feature flags +- Environment settings +- Browser compatibility + +#### Mobile Testing +- Device and OS combinations +- Screen sizes +- Network conditions +- Permissions + +## Pattern Library (Coming Soon) + +### Common Constraint Patterns + +``` +# Dependency constraints +IF [FeatureA] = "Enabled" THEN [FeatureB] = "Enabled"; + +# Exclusive options +IF [PaymentMethod] = "Cash" THEN [InstallmentPlan] = "None"; + +# Platform limitations +IF [OS] = "iOS" THEN [Browser] IN {Safari, Chrome}; + +# Environment restrictions +IF [Environment] = "Production" THEN [LogLevel] <> "Debug"; +``` + +### Boundary Value Patterns + +``` +# Numeric boundaries +Age: 0, 17, 18, 64, 65, 100 + +# Size categories +FileSize: 0KB, 1KB, 1MB, 100MB, 1GB + +# Time periods +Duration: 0s, 1s, 30s, 60s, 3600s +``` + +### Negative Testing Patterns + +``` +# Invalid inputs (using ~ prefix in some PICT variants) +Email: Valid, Invalid, Empty, TooLong +Password: Strong, Weak, Empty, SpecialChars + +# Error conditions +NetworkStatus: Connected, Slow, Disconnected, Timeout +``` + +## Contributing Examples + +Have an example to share? We'd love to include it! + +1. Create your example following the structure in [examples/README.md](../examples/README.md) +2. Include: + - Original specification + - PICT model + - Test cases with expected outputs + - Learning points +3. Submit a pull request + +See [CONTRIBUTING.md](../CONTRIBUTING.md) for details. + +## External Resources + +- [Pairwise Testing Tutorial](https://www.pairwisetesting.com/) +- [NIST Combinatorial Testing Resources](https://csrc.nist.gov/projects/automated-combinatorial-testing-for-software) +- [Microsoft PICT Examples](https://github.com/microsoft/pict/tree/main/doc) diff --git a/pairwise-tests/references/pict_syntax.md b/pairwise-tests/references/pict_syntax.md new file mode 100644 index 0000000..d25fb57 --- /dev/null +++ b/pairwise-tests/references/pict_syntax.md @@ -0,0 +1,83 @@ +# PICT Syntax Reference + +> **Note**: This is a placeholder file. Complete syntax documentation is coming soon! +> +> For now, please refer to the official PICT documentation: +> - [Microsoft PICT on GitHub](https://github.com/microsoft/pict) +> - [PICT User Guide](https://github.com/microsoft/pict/blob/main/doc/pict.md) + +## Quick Reference + +### Basic Model Structure + +``` +# Parameters +ParameterName: Value1, Value2, Value3 +AnotherParameter: ValueA, ValueB, ValueC + +# Constraints (optional) +IF [ParameterName] = "Value1" THEN [AnotherParameter] <> "ValueA"; +``` + +### Parameter Definition + +``` +ParameterName: Value1, Value2, Value3, ... +``` + +### Constraint Syntax + +``` +IF THEN ; +``` + +### Operators + +- `=` - Equal to +- `<>` - Not equal to +- `>` - Greater than +- `<` - Less than +- `>=` - Greater than or equal to +- `<=` - Less than or equal to +- `IN` - Member of set +- `AND` - Logical AND +- `OR` - Logical OR +- `NOT` - Logical NOT + +### Example Constraints + +``` +# Simple constraint +IF [OS] = "MacOS" THEN [Browser] <> "IE"; + +# Multiple conditions +IF [Environment] = "Production" AND [LogLevel] = "Debug" THEN [Approved] = "False"; + +# Set membership +IF [UserRole] = "Guest" THEN [Permission] IN {Read, None}; +``` + +## Coming Soon + +Detailed documentation will include: +- Complete grammar specification +- Advanced features (sub-models, aliasing, seeding) +- Negative testing patterns +- Weight specifications +- Order specifications +- Examples for each feature + +## Contributing + +If you'd like to help complete this documentation: +1. Fork the repository +2. Add content to this file +3. Submit a pull request + +See [CONTRIBUTING.md](../CONTRIBUTING.md) for guidelines. + +## External Resources + +- [Official PICT Documentation](https://github.com/microsoft/pict/blob/main/doc/pict.md) +- [pypict Documentation](https://github.com/kmaehashi/pypict) +- [Pairwise Testing Explained](https://www.pairwisetesting.com/) diff --git a/pairwise-tests/scripts/README.md b/pairwise-tests/scripts/README.md new file mode 100644 index 0000000..87ea259 --- /dev/null +++ b/pairwise-tests/scripts/README.md @@ -0,0 +1,77 @@ +# Scripts + +This directory contains helper scripts for working with PICT models and test cases. + +## Available Scripts + +### pict_helper.py + +A Python utility for: +- Generating PICT models from JSON configuration +- Formatting PICT output as markdown tables +- Parsing PICT output into JSON + +**Installation:** +```bash +pip install pypict --break-system-packages +``` + +**Usage:** + +1. **Generate PICT model from config:** + ```bash + python pict_helper.py generate config.json > model.txt + ``` + +2. **Format PICT output as markdown:** + ```bash + python pict_helper.py format output.txt + ``` + +3. **Parse PICT output to JSON:** + ```bash + python pict_helper.py parse output.txt + ``` + +**Example config.json:** +```json +{ + "parameters": { + "Browser": ["Chrome", "Firefox", "Safari"], + "OS": ["Windows", "MacOS", "Linux"], + "Memory": ["4GB", "8GB", "16GB"] + }, + "constraints": [ + "IF [OS] = \"MacOS\" THEN [Browser] <> \"IE\"", + "IF [Memory] = \"4GB\" THEN [OS] <> \"MacOS\"" + ] +} +``` + +## Future Scripts + +We welcome contributions for: +- Test automation generators +- Export to test management tools (JIRA, TestRail) +- Integration with CI/CD pipelines +- Coverage analysis tools +- Constraint validation utilities + +## Contributing + +Have a useful script to share? + +1. Add your script to this directory +2. Update this README with usage instructions +3. Add comments and examples in your script +4. Submit a pull request + +See [CONTRIBUTING.md](../CONTRIBUTING.md) for guidelines. + +## Dependencies + +Current scripts use: +- Python 3.7+ +- pypict (optional, for direct PICT integration) + +All dependencies should be clearly documented in each script. diff --git a/pairwise-tests/scripts/pict_helper.py b/pairwise-tests/scripts/pict_helper.py new file mode 100644 index 0000000..3a64f91 --- /dev/null +++ b/pairwise-tests/scripts/pict_helper.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +""" +PICT Helper Script + +This script provides utilities for working with PICT models and test cases. + +Note: This is a placeholder/example script. Full implementation coming soon! + +Requirements: + pip install pypict --break-system-packages + +Usage: + python pict_helper.py generate config.json + python pict_helper.py format output.txt + python pict_helper.py parse output.txt +""" + +import sys +import json +from typing import Dict, List, Any + +def generate_model(config_file: str) -> str: + """ + Generate a PICT model from a JSON configuration file. + + Args: + config_file: Path to JSON config file + + Returns: + PICT model as string + + Example config.json: + { + "parameters": { + "Browser": ["Chrome", "Firefox", "Safari"], + "OS": ["Windows", "MacOS", "Linux"], + "Memory": ["4GB", "8GB", "16GB"] + }, + "constraints": [ + "IF [OS] = \"MacOS\" THEN [Browser] <> \"IE\"", + "IF [Memory] = \"4GB\" THEN [OS] <> \"MacOS\"" + ] + } + """ + try: + with open(config_file, 'r') as f: + config = json.load(f) + + parameters = config.get('parameters', {}) + constraints = config.get('constraints', []) + + # Generate model + model_lines = [] + model_lines.append("# Generated PICT Model") + model_lines.append("") + + # Add parameters + for param_name, values in parameters.items(): + values_str = ", ".join(values) + model_lines.append(f"{param_name}: {values_str}") + + # Add constraints + if constraints: + model_lines.append("") + model_lines.append("# Constraints") + for constraint in constraints: + if not constraint.endswith(';'): + constraint += ';' + model_lines.append(constraint) + + return "\n".join(model_lines) + + except Exception as e: + print(f"Error generating model: {e}", file=sys.stderr) + return "" + +def format_output(output_file: str) -> str: + """ + Format PICT output as a markdown table. + + Args: + output_file: Path to PICT output file + + Returns: + Markdown formatted table + """ + try: + with open(output_file, 'r') as f: + lines = f.readlines() + + if not lines: + return "No output to format" + + # First line is header + header = lines[0].strip().split('\t') + + # Create markdown table + table = [] + table.append("| " + " | ".join(header) + " |") + table.append("|" + "|".join(["-" * (len(h) + 2) for h in header]) + "|") + + # Add data rows + for line in lines[1:]: + if line.strip(): + values = line.strip().split('\t') + table.append("| " + " | ".join(values) + " |") + + return "\n".join(table) + + except Exception as e: + print(f"Error formatting output: {e}", file=sys.stderr) + return "" + +def parse_output(output_file: str) -> List[Dict[str, str]]: + """ + Parse PICT output into a list of dictionaries. + + Args: + output_file: Path to PICT output file + + Returns: + List of test case dictionaries + """ + try: + with open(output_file, 'r') as f: + lines = f.readlines() + + if not lines: + return [] + + # First line is header + header = lines[0].strip().split('\t') + + # Parse data rows + test_cases = [] + for i, line in enumerate(lines[1:], 1): + if line.strip(): + values = line.strip().split('\t') + test_case = {"test_id": i} + for h, v in zip(header, values): + test_case[h] = v + test_cases.append(test_case) + + return test_cases + + except Exception as e: + print(f"Error parsing output: {e}", file=sys.stderr) + return [] + +def main(): + """Main entry point for the script.""" + if len(sys.argv) < 2: + print("Usage:") + print(" python pict_helper.py generate ") + print(" python pict_helper.py format ") + print(" python pict_helper.py parse ") + sys.exit(1) + + command = sys.argv[1] + + if command == "generate" and len(sys.argv) >= 3: + config_file = sys.argv[2] + model = generate_model(config_file) + print(model) + + elif command == "format" and len(sys.argv) >= 3: + output_file = sys.argv[2] + table = format_output(output_file) + print(table) + + elif command == "parse" and len(sys.argv) >= 3: + output_file = sys.argv[2] + test_cases = parse_output(output_file) + print(json.dumps(test_cases, indent=2)) + + else: + print(f"Unknown command: {command}", file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + main() + +# Example usage: +""" +# 1. Create a config.json file: +{ + "parameters": { + "Browser": ["Chrome", "Firefox", "Safari"], + "OS": ["Windows", "MacOS", "Linux"] + }, + "constraints": [ + "IF [OS] = \"MacOS\" THEN [Browser] <> \"IE\"" + ] +} + +# 2. Generate PICT model: +python pict_helper.py generate config.json > model.txt + +# 3. Run PICT (if installed): +pict model.txt > output.txt + +# 4. Format as markdown: +python pict_helper.py format output.txt + +# 5. Parse to JSON: +python pict_helper.py parse output.txt +""" -- cgit v1.2.3