From qa-property-based
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.
How this skill is triggered — by the user, by Claude, or both
Slash command
/qa-property-based:quickcheck-testingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Per [qc-hackage][qch]:
Per qc-hackage:
"QuickCheck is a Haskell library for automatic property-based testing. ... 'The programmer provides a specification of the program, in the form of properties which functions should satisfy, and QuickCheck then tests that the properties hold in a large number of randomly generated cases.'" (qc-hackage)
This skill covers both:
-- In .cabal:
build-depends: QuickCheck >= 2.18
-- In stack.yaml: add 'QuickCheck' to extra-deps if not in resolver
Per qc-hackage, version 2.18.0.0 is the latest stable.
// build.sbt
libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.18.0" % Test
// For ScalaTest integration:
libraryDependencies += "org.scalatest" %% "scalatest-propspec" % "3.2.18" % Test
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.
Per qc-hackage, QuickCheck provides:
| Module | Use |
|---|---|
Test.QuickCheck | Main entry: quickCheck, verboseCheck. |
Test.QuickCheck.Arbitrary | Arbitrary typeclass for custom types. |
Test.QuickCheck.Gen | Custom generators. |
Test.QuickCheck.Function | Function generation. |
Test.QuickCheck.Monadic | "for testing stateful/monadic code" (qc-hackage). |
A custom Arbitrary instance:
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 ]
shrink is the function that, given a failing User, returns
candidate simpler Users. QuickCheck tries each until it finds the
smallest still-failing case.
-- Conditional property: discard cases where xs is empty
prop_headOfNonEmpty :: [Int] -> Property
prop_headOfNonEmpty xs = not (null xs) ==> head xs == xs !! 0
-- 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
==> is conditional implication: discard cases where the
precondition fails. forAll quantifies inline. classify /
label track distribution.
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"[email protected]", 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.
For 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
}
}
}
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)
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))
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Skipping shrink in custom Arbitrary | Failures aren't shrunk; counterexample messages are huge. | Always implement shrink (Step 4). |
Heavy ==> (Haskell) / suchThat (Scala) preconditions | Cases 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 types | Compile error / runtime "no instance" - must define instance per type. | Define Arbitrary T instance (Step 4). |
| Random seed in CI | Failures hard to reproduce. | Fixed seed (Step 8). |
quickCheck from Main | Mixes test code with executable. | Use HSpec / Tasty (Haskell) or ScalaTest (Scala) for organization. |
| Properties that always pass trivially | No actual verification; false confidence. | verboseCheck to see distribution; reformulate. |
fc.scheduler,
basic QuickCheck doesn't model concurrent interleavings.shrink implementations need
care to terminate.quickCheck,
Property type, Arbitrary typeclass, shrink, Test.QuickCheck.*
modules, current version 2.18.0.0.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.Provides CDSS development patterns for drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), and alert classification integrated into EMR workflows.
npx claudepluginhub testland/qa --plugin qa-property-based