Testland
Browse all skills & agents

quickcheck-testing

Authors property-based tests for Haskell using QuickCheck (the original PBT library) and for Scala via ScalaCheck (the JVM port) - wires `quickCheck` (Haskell) / `forAll` (ScalaCheck) drivers, defines `Arbitrary` instances or generators, uses `shrink` to find minimal counterexamples, and integrates with HSpec / Tasty (Haskell) or specs2 / ScalaTest. Use when the codebase is Haskell or Scala and the team wants the canonical PBT library that the entire family (Hypothesis / fast-check / proptest / jqwik) was inspired by.

Install with skills.sh (any agent)

npx skills add testland/qa --skill quickcheck-testing
View source

quickcheck-testing

Overview

QuickCheck is the original property-based testing library (qc-hackage (opens in new window)). This skill covers both:

  • QuickCheck (Haskell) - the original.
  • ScalaCheck (Scala / JVM) - the canonical port; same API shape, JVM ecosystem.

When to use

  • The codebase is Haskell - QuickCheck is the canonical choice.
  • The codebase is Scala - ScalaCheck is well-integrated with ScalaTest, specs2, and pure Scala test runners.
  • A multi-language project wants PBT consistency; the QuickCheck vocabulary (arbitrary, shrink, property) is the lingua franca.

Step 1 - Install (Haskell)

-- In .cabal:
build-depends: QuickCheck >= 2.18

-- In stack.yaml: add 'QuickCheck' to extra-deps if not in resolver

Step 2 - Install (Scala)

// build.sbt
libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.18.0" % Test

// For ScalaTest integration:
libraryDependencies += "org.scalatest" %% "scalatest-propspec" % "3.2.18" % Test

Pinned versions and update notes: references/quickcheck-reference.md.

Step 3 - Basic Haskell property

import Test.QuickCheck

prop_reverseInvolutive :: [Int] -> Bool
prop_reverseInvolutive xs = reverse (reverse xs) == xs

main :: IO ()
main = quickCheck prop_reverseInvolutive

quickCheck runs the property with 100 random [Int] values by default; on failure, prints the failing case + the shrunk minimal example.

For ghci:

ghci> quickCheck prop_reverseInvolutive
+++ OK, passed 100 tests.

Step 4 - Custom generators

Define an Arbitrary instance with arbitrary (the generator) and shrink (which returns simpler candidates for minimizing a failure):

data User = User { userId :: Int, userEmail :: String, userAge :: Int }
  deriving (Show, Eq)

instance Arbitrary User where
  arbitrary = do
    uid <- arbitrary `suchThat` (> 0)
    name <- listOf1 (elements ['a'..'z'])
    age <- choose (18, 100)
    return $ User uid (name ++ "@example.com") age

  shrink (User i e a) =
    [ User i' e a | i' <- shrink i, i' > 0 ] ++
    [ User i e a' | a' <- shrink a, a' >= 18, a' <= 100 ]

The QuickCheck module map (Test.QuickCheck.Gen, .Arbitrary, .Function, .Monadic): references/quickcheck-reference.md.

Step 5 - Combinators

==> is conditional implication - discard cases where the precondition fails:

-- Conditional property: discard cases where xs is empty
prop_headOfNonEmpty :: [Int] -> Property
prop_headOfNonEmpty xs = not (null xs) ==> head xs == xs !! 0

forAll (inline quantification) and classify / label (distribution tracking): references/quickcheck-reference.md.

Step 6 - ScalaCheck equivalent

import org.scalacheck._
import org.scalacheck.Prop.forAll

object UserSpecification extends Properties("User") {

  implicit val userGen: Arbitrary[User] = Arbitrary {
    for {
      id <- Gen.posNum[Int]
      name <- Gen.alphaLowerStr.suchThat(_.length >= 3)
      age <- Gen.choose(18, 100)
    } yield User(id, s"$name@example.com", age)
  }

  property("json round-trip") = forAll { (u: User) =>
    val json = encode(u)
    decode[User](json) == Right(u)
  }

  property("sorted list stays sorted") = forAll { (xs: List[Int]) =>
    val sorted = xs.sorted
    sorted == sorted.sorted
  }
}

Same shape as Haskell QuickCheck; Prop.forAll is the equivalent of quickCheck. ScalaTest integration (AnyPropSpec + ScalaCheckPropertyChecks): references/quickcheck-reference.md.

Step 7 - Configuration

Haskell:

import Test.QuickCheck

main = quickCheckWith (stdArgs { maxSuccess = 1000, maxSize = 100 }) prop_X
-- or:
quickCheckWith (stdArgs { maxSuccess = 100, maxSize = 50, maxDiscardRatio = 10 }) prop_X

Scala:

import org.scalacheck.Test
import org.scalacheck.Prop

val params = Test.Parameters.default
  .withMinSuccessfulTests(1000)
  .withMaxDiscardRatio(10.0f)

Step 8 - CI integration

Haskell with cabal:

cabal test

Scala with sbt:

sbt test

For deterministic CI, set the seed:

quickCheckWith (stdArgs { replay = Just (mkQCGen 42, 0) }) prop_X
val params = Test.Parameters.default.withInitialSeed(rng.Seed(42L))

References

  • references/quickcheck-reference.md - module map, combinators, ScalaTest integration, anti-patterns, limitations, pinned versions.
  • qch (opens in new window) - QuickCheck Haskell on Hackage: quickCheck, Property type, Arbitrary typeclass, shrink, Test.QuickCheck.* modules.
  • ScalaCheck official site (scalacheck.org) - Scala port; same conceptual model, JVM ecosystem.
  • hypothesis-testing, fast-check-testing, proptest-testing, jqwik-testing - all inspired by QuickCheck; per-language siblings.

QuickCheck / ScalaCheck reference

View source (opens in new window)

QuickCheck / ScalaCheck reference

Detailed lookup material for quickcheck-testing. Sources: QuickCheck on Hackage (opens in new window), ScalaCheck (scalacheck.org).

Pinned versions

  • QuickCheck (Haskell): 2.18.0.0 latest stable at source-fetch (build-depends: QuickCheck >= 2.18).
  • ScalaCheck (Scala): 1.18.0; ScalaTest-plus integration via scalatest-propspec 3.2.18.

Check the sources before bumping.

QuickCheck modules

ModuleUse
Test.QuickCheckMain entry: quickCheck, verboseCheck.
Test.QuickCheck.ArbitraryArbitrary typeclass for custom types.
Test.QuickCheck.GenCustom generators.
Test.QuickCheck.FunctionFunction generation.
Test.QuickCheck.Monadic"for testing stateful/monadic code" (qch (opens in new window)).

Combinators (Haskell)

-- Quantify per-test scope
prop_sortIdempotent :: Property
prop_sortIdempotent = forAll (listOf1 arbitrary :: Gen [Int]) $ \xs ->
  sort (sort xs) == sort xs

-- Classify cases for distribution monitoring
prop_lengthClassified :: [Int] -> Property
prop_lengthClassified xs = classify (null xs) "empty" $
                            classify (length xs > 100) "large" $
                            length (reverse xs) == length xs

forAll quantifies inline; classify / label track distribution.

ScalaTest integration

import org.scalatest.propspec.AnyPropSpec
import org.scalatest.matchers.should.Matchers
import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks

class UserPropSpec extends AnyPropSpec with Matchers with ScalaCheckPropertyChecks {

  property("reverse is involutive") {
    forAll { (xs: List[Int]) =>
      xs.reverse.reverse shouldBe xs
    }
  }
}

Anti-patterns

Anti-patternWhy it failsFix
Skipping shrink in custom ArbitraryFailures aren't shrunk; counterexample messages are huge.Always implement shrink (Step 4).
Heavy ==> (Haskell) / suchThat (Scala) preconditionsCases discarded; "Gave up after N tests" warning.Restructure the generator to produce only valid inputs (Step 4-5).
arbitrary without Arbitrary typeclass instance for custom typesCompile error / runtime "no instance" - must define instance per type.Define Arbitrary T instance (Step 4).
Random seed in CIFailures hard to reproduce.Fixed seed (Step 8).
quickCheck from MainMixes test code with executable.Use HSpec / Tasty (Haskell) or ScalaTest (Scala) for organization.
Properties that always pass triviallyNo actual verification; false confidence.verboseCheck to see distribution; reformulate.

Limitations

  • Haskell-specific syntax (Haskell QuickCheck only). Teams unfamiliar with Haskell will find the syntax off-putting; ScalaCheck is more accessible.
  • Older API quirks. QuickCheck pre-dates many modern PBT conveniences; jqwik / Hypothesis ergonomics are smoother for newcomers.
  • No race-condition detection. Unlike fast-check's fc.scheduler, basic QuickCheck doesn't model concurrent interleavings.
  • Shrinking can be slow. Custom shrink implementations need care to terminate.
  • ScalaCheck integration with ScalaTest can be confusing when multiple property-checking integrations exist (ScalaTest's own generators vs ScalaCheck's).

Related skills

fast-check-testing

Authors property-based tests in JavaScript / TypeScript using fast-check - wires `fc.assert(fc.property(arbitrary, ...))`, picks arbitraries (`fc.integer`, `fc.string`, `fc.array`, `fc.tuple`, `fc.record`), uses `.map()` / `.chain()` / `.filter()` to build domain arbitraries, and integrates with Jest / Vitest / Mocha / Jasmine / AVA / Tape. Use when a JS/TS codebase needs PBT to catch edge cases - fast-check has been used to find bugs in major libraries (`query-string`, etc.) and is trusted by Jest, Jasmine, fp-ts, Ramda.

hypothesis-testing

Authors property-based tests in Python using Hypothesis - wires `@given` with `strategies` (`st.integers`, `st.text`, `st.lists`, `st.from_regex`, `st.composite`), uses `assume()` / `.filter()` for preconditions, configures via `@settings(max_examples=..., deadline=...)`, and exploits Hypothesis's automatic shrinking to find the falsifying example. Integrates with pytest fixtures + parametrize. Use when a Python project needs PBT to catch edge cases the example-based tests miss - bug clusters around input ranges / boundary values / interaction between fields.

jqwik-testing

Authors property-based tests for the JVM (Java + Kotlin) using jqwik - wires `@Property` test methods, `@ForAll` parameter annotations, `Arbitraries.integers/strings/etc` generators, custom `@Provide` arbitraries, and the JUnit 5 platform integration. Use when a JVM project needs PBT - alternative to JUnit-QuickCheck and Vavr's property-checking; tightly integrates with JUnit 5 so property tests run alongside conventional unit tests in the same Maven / Gradle pipeline.

proptest-testing

Authors property-based tests in Rust using proptest - wires the `proptest!` macro, defines strategies (`prop::collection::vec`, type-driven `any` strategies, regex-based string strategies), uses the strategy-per-value model (vs QuickCheck's per-type) for flexible composition, and exploits proptest's automatic shrinking + persistence of failed cases (regression test artifact). Use when a Rust codebase needs PBT - pairs especially well with parsers, serializers, and any function with a structured input domain.