SwiftQC

1.0.0

Modern property-based testing for Swift 6+. Find edge cases you'd never think to test manually through automatic test generation and intelligent shrinking to minimal counterexamples.
Aristide021/SwiftQC

What's New

SwiftQC v1.0.0 - First Stable Release

2025-07-16T15:36:03Z

SwiftQC v1.0.0 Release Notes

๐ŸŽ‰ First Stable Release - July 16, 2025

SwiftQC v1.0.0 marks the initial stable release of a comprehensive property-based testing library for Swift, bringing modern testing capabilities to the Swift ecosystem with full Swift 6 compatibility.

๐Ÿš€ Key Features

Property-Based Testing

  • Automatic test case generation with the forAll function
  • Intelligent shrinking to minimal counterexamples when tests fail
  • Deterministic testing with seed support for reproducible results
  • Comprehensive reporting with Swift Testing integration

Type System Integration

  • Native Swift 6 support with full Sendable compliance
  • Arbitrary protocol linking types to their generators and shrinkers
  • 130+ built-in conformances for Swift standard library types
  • Type-safe generators preventing runtime errors

Advanced Testing Modes

Stateful Testing

  • Test complex state machines and stateful systems
  • Generate and execute command sequences
  • Automatic shrinking of failing command sequences
  • Model-based validation against expected behavior

Parallel Testing

  • Test concurrent systems for race conditions
  • Detect model-system divergence in parallel execution
  • Validate thread safety and consistency
  • Built on Swift's modern concurrency model

Developer Experience

  • Swift Testing integration with seamless issue reporting
  • XCTest compatibility for existing test suites
  • CLI tool for interactive property testing
  • Comprehensive documentation with examples and guides

๐Ÿ“‹ What's Included

Core Components

  • Property runners: forAll with multiple overloads for ergonomic testing
  • Generator system: Composable generators via PointFree's swift-gen
  • Shrinking system: Pluggable shrinkers for all supported types
  • Arbitrary conformances: Complete coverage of Swift standard library
  • State testing: StateModel protocol and stateful() runner
  • Parallel testing: ParallelModel protocol and parallel() runner

Built-in Support For

  • Numeric types: All integer and floating-point types
  • Collections: Arrays, Dictionaries, Sets, and variants
  • Text types: Strings, Characters, Unicode scalars
  • Foundation types: Data, Date, URL, UUID, DateComponents
  • Generic types: Optionals, Results, Ranges
  • Custom types: Easy Arbitrary conformance for your types

Infrastructure

  • Self-hosted CI/CD with GitHub Actions
  • Multi-version testing (Swift 6.0 and 6.1)
  • Code coverage reporting with Codecov
  • Automated releases with comprehensive validation

๐Ÿ”ง Requirements

  • Swift 6.0+ (Xcode 16+)
  • Platforms:
    • macOS 12.0+
    • iOS 13.0+
    • tvOS 13.0+
    • watchOS 6.0+

๐Ÿ“ฆ Installation

Swift Package Manager

dependencies: [
    .package(url: "https://github.com/Aristide021/SwiftQC.git", from: "1.0.0")
]

Quick Start

import SwiftQC
import Testing

@Test func reverseProperty() async {
    await forAll("reverse twice equals identity") { (array: [Int]) in
        #expect(array == array.reversed().reversed())
    }
}

๐Ÿ”ฎ Future Roadmap

  • Enhanced parallel testing with linearizability checking
  • Performance benchmarking integration
  • Additional Foundation types support
  • IDE integrations and tooling improvements

SwiftQC

SwiftQC is a Swift 6+ property-based testing library designed to unify concepts from QuickCheck, Hedgehog, and modern Swift testing into a single, cohesive framework.

It provides:

  • Composable Generators (Gen via PointFree's swift-gen)
  • Pluggable Shrinkers (Shrinker, Sendable) for minimal counterexamples
  • An Arbitrary protocol (Sendable) tying types (Value: Sendable) to their default (Gen, Shrinker)
  • A Property Runner (forAll) with automatic shrinking, seed handling, and seamless Swift Testing issue integration
  • Stateful testing via StateModel protocol and stateful() runner with full sequence shrinking support โœ…
  • Parallel testing via ParallelModel protocol and parallel() runner (core functionality complete, advanced linearizability features TBD)

Installation

๐Ÿ“ฆ Library (Swift Package Manager)

Add SwiftQC to your project:

// Package.swift
.package(url: "https://github.com/Aristide021/SwiftQC.git", from: "1.0.0"),

Or in Xcode: File โ†’ Add Packagesโ€ฆ and enter the repository URL.

๐Ÿ”ง CLI Tool (Development Use)

โš ๏ธ Current Limitation: CLI requires full Xcode due to Swift Testing dependencies.

# Clone and run locally (recommended for now)
git clone https://github.com/Aristide021/SwiftQC.git
cd SwiftQC
swift run SwiftQCCLI --help
swift run SwiftQCCLI run --count 100
swift run SwiftQCCLI interactive

Note: We recommend using the library directly in your projects rather than the CLI for production use.

๐Ÿ“‹ See INSTALL.md for detailed installation options and troubleshooting.

Quick Start

Import the library and write a property test in your test suite:

import SwiftQC
import Testing // Or XCTest

// In your test file (e.g., MyLibraryTests.swift)
@Test // Using Swift Testing syntax
func additionIsCommutative() async {
  // Test that integer addition is commutative
  await forAll("Int addition is commutative") { (a: Int, b: Int) in
    #expect(a + b == b + a)
    // Or using XCTest:
    // XCTAssertEqual(a + b, b + a)
  }
}

// --- Or using XCTest ---
// class MyLibraryTests: XCTestCase {
//   func testAdditionIsCommutative() async {
//     await forAll("Int addition is commutative") { (a: Int, b: Int) in
//       XCTAssertEqual(a + b, b + a)
//     }
//   }
// }

Run your tests using the standard command:

swift test

SwiftQC's forAll function will automatically generate random inputs, run your property, shrink failures to minimal examples, and report results (integrating with Swift Testing's issue system if used).

Built-in Types Supporting Arbitrary

SwiftQC provides Arbitrary conformance for many standard Swift types out of the box:

  • Numeric: Int, Int8, Int16, Int32, Int64, UInt, UInt8, UInt16, UInt32, UInt64, Float, Double, CGFloat, Decimal
  • Text: Character, String, Unicode.Scalar
  • Boolean: Bool
  • Data: Data, UUID
  • Collections: Array<T>, Dictionary<K, V> (via ArbitraryDictionary<K, V>), Set<T>, Optional<T>, Result<Success, Failure>
  • Time: Date (with reasonable ranges)

Using forAll

SwiftQC provides several overloads of the forAll function for different testing scenarios.

Single Input

For properties involving a single type conforming to Arbitrary:

import SwiftQC
import Testing

@Test
func stringReversalIdentity() async {
    await forAll("String reversal identity") { (s: String) in
        let reversed = String(s.reversed())
        let backToOriginal = String(reversed.reversed())
        #expect(backToOriginal == s)
    }
}

Multiple Inputs (Tuples)

For properties involving multiple Arbitrary types, provide the types (.self) after the description:

@Test
func stringConcatenationLength() async {
    await forAll(
        "String concatenation preserves length", 
        String.self, String.self
    ) { (s1: String, s2: String) in
        let combined = s1 + s2
        #expect(combined.count == s1.count + s2.count)
    }
}

Dictionary Inputs

A specialized overload handles Dictionary properties. You need to provide the Arbitrary types for the Key and Value. The Key's Value must be Hashable.

@Test
func dictionaryMerging() async {
    await forAll(
        "Dictionary merging combines entries", 
        String.self,   // Key type
        Int.self,      // Value type
        forDictionary: true
    ) { (dict: [String: Int]) in
        let emptyDict: [String: Int] = [:]
        let merged = dict.merging(emptyDict) { (current, _) in current }
        #expect(merged == dict)
    }
}

Custom Arbitrary Types

You can make any type Arbitrary by implementing the protocol:

struct Point: Arbitrary, Sendable {
    let x: Int
    let y: Int
    
    typealias Value = Point
    
    static var gen: Gen<Point> {
        zip(Int.gen, Int.gen).map { Point(x: $0, y: $1) }
    }
    
    static var shrinker: any Shrinker<Point> {
        Shrinkers.map(
            from: Shrinkers.tuple(Int.shrinker, Int.shrinker),
            to: { Point(x: $0.0, y: $0.1) },
            from: { ($0.x, $0.y) }
        )
    }
}

See Arbitrary.md for detailed instructions and examples.

Examples

Explore hands-on examples in the Examples/ directory:

Each example is a complete Swift package you can build and run:

cd Examples/BasicUsage && swift test

Documentation

Explore the Docs/ directory for comprehensive documentation:

License

SwiftQC is released under the Apache License 2.0. See LICENSE for details.

Description

  • Swift Tools 6.0.0
View More Packages from this Author

Dependencies

Last updated: Wed Jul 23 2025 20:41:22 GMT-0900 (Hawaii-Aleutian Daylight Time)