SwiftCheck Documentation
repository·master·Indexed 23 days ago
https://github.com/typelift/swiftcheckA property-based testing library for Swift inspired by Haskell's QuickCheck. SwiftCheck allows developers to define program properties using the forAll quantifier and the Arbitrary protocol, automatically testing them against randomly generated data and shrinking failing test cases to minimal counterexamples. It includes generator combinators such as suchThat, fromElements, choose, and frequency for creating custom test data.
What's inside SwiftCheck
- When a test fails during fuzz testing, SwiftCheck doesn't just stop. It performs shrinking: it attempts to whittle down the failing input data to the smallest possible counterexample that still triggers the failure. This makes debugging much easier by providing a minimal reproducible case.
Define program properties with `forAll`
masterSwiftCheck uses the
forAllquantifier to define properties. A property is an invariant that must hold true for all generated data of a specific type. To useforAll, the input types must conform to theArbitraryprotocol.Basic usage:
property("Description") <- forAll { (i : Int) in return i == i }Advanced Property Operators
^&&^(Conjunction): Combines multiple properties. The resulting property only holds if all sub-properties hold.<?>(Labeling): Labels a sub-part of a property for clearer output in failure reports.==>(Implication): Defines a precondition. If the precondition returnsfalse, the test case is discarded and not evaluated against the property.
Example of a complex property:
property("The reverse of the reverse of an array is that array") <- forAll { (xs : [Int]) in return (xs.reversed().reversed() == xs) <?> "Left identity" ^&&^ (xs == xs.reversed().reversed()) <?> "Right identity" }property("Integer Equality is Reflexive") <- forAll { (i : Int) in return i == i }Install SwiftCheck
masterYou can integrate SwiftCheck into your project using Swift Package Manager, Carthage, CocoaPods, or as a Framework subproject.
Swift Package Manager
Add the following to your
Package.swiftdependencies:.package(url: "https://github.com/typelift/SwiftCheck.git", from: "0.8.1")Carthage
- Add SwiftCheck to your
Cartfile. - Run
carthage update. - Drag the relevant copy of SwiftCheck into your project.
- Expand the Link Binary With Libraries phase and add
SwiftCheck. - Add a Copy Files build phase, set the directory to
Frameworks, and addSwiftCheck.
CocoaPods
Add the pod to your
Podfileand run:$ pod installFramework (Subproject)
- Drag
SwiftCheck.xcodeprojinto your project tree as a subproject. - Under Target Dependencies, add
SwiftCheck. - Expand Link Binary With Libraries and add
SwiftCheck. - Add a Copy Files build phase, set the directory to
Frameworks, and addSwiftCheck.
- Add SwiftCheck to your
Use Generator combinators to create custom data
masterSwiftCheck provides several combinators to build specialized generators from existing ones:
suchThat: Filters a generator based on a predicate (e.g.,Int.arbitrary.suchThat { $0 % 2 == 0 }).fromElements(of:): Generates values from a specific collection.choose((min, max)): Generates a random value within a range.frequency([(weight, generator)]): Generates values based on weighted probabilities.pure(value): Creates a generator that always returns the same value.
Example:
let onlyEven = Int.arbitrary.suchThat { $0 % 2 == 0 } let vowels = Gen.fromElements(of: [ "A", "E", "I", "O", "U" ]) let randomHexValue = Gen<UInt>.choose((0, 15)) let weightedOptionals = Gen<Int?>.frequency([ (1, Gen<Int?>.pure(nil)), (3, Int.arbitrary.map(Optional.some)) ])let onlyEven = Int.arbitrary.suchThat { $0 % 2 == 0 } let vowels = Gen.fromElements(of: [ "A", "E", "I", "O", "U" ]) let randomHexValue = Gen<UInt>.choose((0, 15)) let uppers = Gen<Character>.fromElements(in: "A"..."Z") let lowers = Gen<Character>.fromElements(in: "a"..."z") let numbers = Gen<Character>.fromElements(in: "0"..."9") /// This generator will generate `.none` 1/4 of the time and an arbitrary /// `.some` 3/4 of the time let weightedOptionals = Gen<Int?>.frequency([ (1, Gen<Int?>.pure(nil)), (3, Int.arbitrary.map(Optional.some)) ])Make custom types testable with `Arbitrary`
masterTo use custom types with
forAll, they must conform to theArbitraryprotocol. This requires implementing a staticarbitraryproperty that returns aGen<T>(Generator).Implementing
ArbitraryviaGen.zipFor types with multiple properties, you can zip existing generators together:
extension ArbitraryFoo : Arbitrary { public static var arbitrary : Gen<ArbitraryFoo> { return Gen<(Int, Int)>.zip(Int.arbitrary, Int.arbitrary).map(ArbitraryFoo.init) } }Implementing
ArbitraryviaGen.composeFor more complex construction (e.g., using setters or procedural logic), use
Gen.compose. This provides a contextcthat allows you to callc.generate()for various types:public static var arbitrary : Gen<MyClass> { return Gen<MyClass>.compose { c in return MyClass( a: c.generate(), b: c.generate(Bool.suchThat { $0 == false }), c: c.generate() ) } }