Contents

Aristide021/SwiftQC

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.

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.

Package Metadata

Repository: Aristide021/SwiftQC

Stars: 22

Forks: 0

Open issues: 0

Default branch: main

Primary language: swift

License: Apache-2.0

Topics: ios, macos, property-testing, quickcheck, swift, swift-package, testing

README: README.md