Skip to content

Property-Based Testing

PonyTest includes property-based testing support.

In traditional unit testing, the developer must choose input examples for the unit under test and check whether the output meets expectations.

Property-based testing leaves generation of test input samples to the testing engine, which generates random examples taken from a description of how to do so, called Generators. The developer defines a Generator and describes the condition that should hold for every input sample.

Usage

Writing property-based tests is done by implementing the trait Property. A Property needs to define a type parameter for the type of the input sample, a Generator and a property function. Here is a minimal example:

class _MyFirstProperty is Property[String]
  fun name(): String =>
    "my_first_property"

  fun gen(): Generator[String] =>
    Generators.ascii()

  fun ref property(arg1: String, ph: PropertyHelper) =>
    ph.assert_eq[String](arg1, arg1)

A Property needs a name for identification in test output. We created a Generator by using a factory method defined in the Generators primitive, and we used PropertyHelper to assert on a condition that should hold for all samples.

Below are two classic list reverse properties:

use "pony_test"
use "collections"

class _ListReverseProperty is Property[Array[USize]]
  fun name(): String => "list/reverse"

  fun gen(): Generator[Array[USize]] =>
    Generators.seq_of[USize, Array[USize]](Generators.usize())

  fun ref property(arg1: Array[USize], ph: PropertyHelper) =>
    ph.assert_array_eq[USize](arg1, arg1.reverse().reverse())

class _ListReverseOneProperty is Property[Array[USize]]
  fun name(): String => "list/reverse/one"

  fun gen(): Generator[Array[USize]] =>
    Generators.seq_of[USize, Array[USize]](
      Generators.usize()
      where from = 1, to = 1)

  fun ref property(arg1: Array[USize], ph: PropertyHelper) =>
    ph.assert_array_eq[USize](arg1, arg1.reverse())

Integration with PonyTest

Register a Property with PonyTest by calling test.property in the test list:

use "pony_test"
actor Main is TestList
  new create(env: Env) =>
    PonyTest(env, this)

  fun tag tests(test: PonyTest) =>
    test.property(_MyFirstProperty)

for_all on TestHelper integrates any number of properties directly into one UnitTest:

class _ListReverseProperties is UnitTest
  fun name(): String => "list/properties"

  fun apply(h: TestHelper) ? =>
    let g = Generators

    let gen1 = recover val g.seq_of[USize, Array[USize]](g.usize()) end
    h.for_all[Array[USize]](gen1)(
      {(arg1, ph) =>
        ph.assert_array_eq[USize](arg1, arg1.reverse().reverse())
      })?

    let gen2 = recover val g.seq_of[USize, Array[USize]](g.usize(), 1, 1) end
    h.for_all[Array[USize]](gen2)(
      {(arg1, ph) =>
        ph.assert_array_eq[USize](arg1, arg1.reverse())
      })?

Additional resources

The API documentation covers property-based testing in detail. The ponyc GitHub repository has example tests.

The Pony Patterns book has a testing section covering testing more broadly.