Awesome
Overview of Property-Based Testing Functionality
Property-based testing (PBT) frameworks come with a number of different features, but which library supports which features? For a PBT newcomer, it can be hard to tell. Strictly speaking you don't even need a PBT framework. Property-based tests can be written from scratch on a case-by-case basis using a random number generator. One can even test stateful code without a state-machine framework, e.g., as outlined here. However a framework provides reusable parts and infrastructure, thus paving the way for bigger developments, such as property-based testing automotive software against the AUTOSAR specification.
- To test an imperative API a framework with state machine support would be nice.
- Integrated shrinking can be a nice feature for bigger developments where writing custom shrinkers may be out of the question.
- ...
This overview is to help myself keep track. It has been compiled over a number of years teaching PBT. As features are gradually added to a framework the table's entries may unfortunately become outdated. YMMV.
I'll be happy to accept PRs for updating entries and adding new frameworks.
Framework functionality
Framework | Language | Gen. EDSL | Shrinking | Int. shr. | State machine | Par. st. mach. | Cov. guidance |
---|---|---|---|---|---|---|---|
QuickCheck | Erlang | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | |
PropEr | Erlang | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | |
QuickCheck | Haskell | :heavy_check_mark: | :heavy_check_mark: | ( :heavy_check_mark: ) | ( :heavy_check_mark: ) | ||
SmallCheck | Haskell | :heavy_check_mark: | <sup>1</sup> | <sup>1</sup> | |||
Hedgehog | Haskell | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | |
Scala-Hedgehog | Scala / JVM | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | |
R-Hedgehog | R | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | ||
FSharp-Hedgehog | F# / .Net | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | |||
Hypothesis | Python | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | ( :heavy_check_mark: )<sup>2</sup> | ( :heavy_check_mark: ), ( :heavy_check_mark: ) |
TSTL | Python | <sup>3</sup> | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | |
ScalaCheck | Scala / JVM | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | ||
gopter | Go | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | |||
Rapid | Go | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | ||
propCheck | Kotlin | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | |
FsCheck | F# / .Net | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | |||
CsCheck | C# / .Net | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | |
fast-check | JS / TS | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | ( :heavy_check_mark: )<sup>2</sup> | |
QCheck | OCaml | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark:<sup>4</sup> | :heavy_check_mark: | :heavy_check_mark: | |
Crowbar | OCaml | :heavy_check_mark: | ( :heavy_check_mark: )<sup>5</sup> | :heavy_check_mark: | |||
Monolith | OCaml | :heavy_check_mark: | ( :heavy_check_mark: )<sup>5</sup> | :heavy_check_mark: | |||
Base_quickcheck | OCaml | :heavy_check_mark: | :heavy_check_mark: | ||||
Popper | OCaml | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | |||
junit-quickcheck | Java | :heavy_check_mark: | :heavy_check_mark: | ( :heavy_check_mark: )<sup>6</sup> | |||
QuickTheories | Java | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | ( :heavy_check_mark: ) | ( :heavy_check_mark: ) | :heavy_check_mark: |
jqwik | Java | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | ||
quickcheck | Rust | :heavy_check_mark: | :heavy_check_mark: | ||||
propcheck | Rust | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | |||
Fox | Obj.C / Swift | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | ( :heavy_check_mark: ) | |
SwiftCheck | Swift | :heavy_check_mark: | :heavy_check_mark: | ||||
RapidCheck | C++ | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | ( :heavy_check_mark: ) | ||
test.check | Clojure | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | ? | ? | |
Lua-QuickCheck | Lua | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | ||
theft | C | ( :heavy_check_mark: ) | :heavy_check_mark: | ( :heavy_check_mark: ) | ( :heavy_check_mark: ) | ||
DeepState | C / C++ | ( :heavy_check_mark: ) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | ||
Echidna | Solidity / EVM | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | ||
... |
Legend:
- Language denotes the frameworks's language or platform
- Generator EDSL denotes whether the framework has an expressive, embedded domain-specific language for programming custom generators (
int
,list
,map
, ...) - Shrinking denotes whether the framework has built-in support for reducing counterexamples.
- Integrated shrinking denotes whether a shrinker automatically preserves any invariants of an EDSL-based custom generator (sorted lists, non-empty array, valid JSON, ...)
- State machine denotes whether the framework has a state-machine library for model-based testing.
- Parallel state machine denotes whether the framework supports parallel state-machine testing for race conditons, etc.
- Coverage guidance denotes whether the framework's generators (and shrinkers) can be guided by code coverage information obtained via instrumentation.
Footnotes
- <sup>1</sup> Contrary to QuickCheck and its descendants, SmallCheck's generators enumerate values starting from the smallest ones (up to some bound). SmallCheck will therefore encounter a minimal counterexample first and hence doesn't require shrinking.
- <sup>2</sup> Hypothesis and fast-check support asynchronous state machine testing, which can find race conditions (although it is strictly speaking not using parallel testing).
- <sup>3</sup> TSTL instead uses an external DSL.
- <sup>4</sup> QCheck supports integrated shrinking in the generators from the QCheck2 module, whereas the original QCheck module does not.
- <sup>5</sup> Crowbar and Monolith both use AFL which trims each test input as part of its core genetic algorithm. In addition they support test case reduction via
afl-tmin
which is unaware of (and hence may break) OCaml typing. - <sup>6</sup> JQF and the underlying Zest supports multiple forms feedback guidance beyond coverage.
Background:
The term property-based testing seems to originate from 'Property-Based Testing; A New Approach to Testing for Assurance' by Fink and Bishop (SE Notes 1997). The approach was popularized as an embedded domain-specific language in 'QuickCheck: A Lightweight Tool for Random Testing of Haskell Programs' by Claessen and Hughes (ICFP 2000) which inspired ports to many other languages.
Integrated shrinking is explained in more detail in
- an early design thread on the Haskell mailing list
- a Reddit thread announcing Hedgehog
- a talk by Jacob Stanley on Hedgehog's design
- a blog post by Edsko de Vries with pro/cons
- McIver's Hypothesis article advocating the approach
- 'Test-Case Reduction via Test-Case Generation: Insights From the Hypothesis Reducer' by MacIver and Donaldson (ECOOP 2020)
- Everything You Ever Wanted to Know About Test-Case Reduction, But Didn’t Know to Ask - describing DeepState's reducer/shrinker
State machines to test protocols and systems with state are described in
- 'Testing reactive systems with GAST' by Koopman and Plasmeijer (TFP 2003, revised 2005) - which describes a model-based framework for Clean's Gast library
- 'Testing Telecoms Software with Quviq QuickCheck' Arts, Hughes, and Johansson (Erlang 2006) - which describes Erlang's first
eqc_commands
module - 'QuickCheck Testing for Fun and Profit' by Hughes (PADL 2007) describes a later revision
- the API documentation of Quviq's latest (commercial) version
- 'A Note on State-Machine Frameworks for Property-Based Testing' - a tutorial which reconstructs
qcstm
for OCaml
Parallel state-machine tests for race conditions were later introduced in
- 'Finding Race Conditions in Erlang with QuickCheck and PULSE' by Claessen et al. (ICFP 2009) -- along with a scheduler and a process visualizer
- 'Testing a Database for Race Conditions with QuickCheck' by Hughes and Bolinder (Erlang 2011)