Skip to main content
Z3 can automatically generate test inputs that satisfy specific constraints, find edge cases, and create comprehensive test suites. This approach, often called constraint-based testing or symbolic testing, helps uncover bugs that traditional testing might miss.

When to Use Automated Testing

Use Z3 for test generation when you need to:
  • Generate test inputs: Create inputs satisfying complex preconditions
  • Find edge cases: Discover boundary conditions and corner cases
  • Achieve coverage: Generate inputs covering different execution paths
  • Fuzzing: Create structured inputs for security testing
  • Property-based testing: Verify properties hold for all valid inputs
  • Regression testing: Generate minimal reproducing examples

Core Concepts

Constraint-Based Test Generation

  1. Encode preconditions: Express input requirements as constraints
  2. Encode path conditions: Capture execution path constraints
  3. Solve for inputs: Find concrete values satisfying constraints
  4. Execute and verify: Run test with generated inputs

Coverage Strategies

  • Branch coverage: Generate inputs for each branch
  • Path coverage: Cover distinct execution paths
  • Dataflow coverage: Exercise def-use pairs
  • Boundary coverage: Test boundary conditions

Example: Input Generation

Generate test inputs for a function with complex preconditions:

Example: Finding Edge Cases

Discover boundary conditions and corner cases:

Example: Property-Based Testing

Generate inputs to test properties:

Example: Path Coverage

Generate inputs covering different execution paths:

Example: Structured Fuzzing

Generate complex structured inputs for fuzzing:

Example: Minimal Reproducing Examples

Find minimal inputs that trigger a bug:

Integration with Test Frameworks

pytest Integration

Best Practices

  1. Start with simple constraints: Build up complexity gradually
  2. Use appropriate theories: Choose the right solver for your domain
  3. Generate diverse inputs: Block previous solutions to get variety
  4. Minimize examples: Use Optimize() to find minimal test cases
  5. Combine with fuzzing: Use Z3 to generate seed inputs for fuzzers
  6. Cache solutions: Store generated tests for regression testing

Theory Solvers for Testing

  • Bit-vectors: Testing low-level code and overflow conditions
  • Arrays: Testing data structures and memory operations
  • Strings: Generating string inputs and testing parsers
  • Arithmetic: Numeric test generation
  • Optimize: Finding minimal/maximal test cases
  • Models: Extracting concrete values from solutions
  • Tactics: Solver strategies for test generation

Further Examples

  • examples/python/example.py: Basic constraint solving
  • examples/java/JavaExample.java: Array and bit-vector examples