aiken/examples/moonrat/README.md

2.3 KiB

Moonrat

Hedgehog's spineless cousin

Aims

Property based testing for aiken inspired by hedgehog and elm-test.

Aims:

  • Default gen and shrinking auto derived for any types
  • Support custom gen/shrinking
  • Friendly output (progress, sensible feedback such as diffs on large data)
  • Reasonably speedy

Non-aims:

  • e2e testing. This is intended for functions rather than testing full txs against validators. Although it should still be possible, it is not our aim here to make writing and testing txs ergonomic.

Interface

An aiken file

// my_tests.ak

type T0 {
  f0 : Int, 
  ...
}

fn gen_t0(seed : Int, complexity : Int) -> T0 {
  ...
}

fn shrink_t0(x : T0) -> List<T0> {
  // TODO : what should the signature of this be?! 
  ...
}

type T1 {
  f0 : Int, 
  ...
}

test prop_x ( 
  a0 : T0 via (gen_t0(0), shrink_t0), 
  a1 : T0 via (gen_t0(1), shrink_t0), 
  a2 : T0, 
  a2 : T1, 
) {
  todo!
}

Comments on the sample. prop_x is our test - now supporting arguments. There is new syntax via. We have a custom generator and shrinker for T0 which we may or may not use. In the absence of a specified gen/shrink pair, the default, autoderived one is used.

Run 100 times

aiken check -m "my_lib/my_test.{prop_x}"

Run 1000 cases with a specified seed and shrink limit

aiken check --repeat 1000 --seed 123212123 --shrink-limit 5

Reporting:

Testing ...

my_test 
  prop_x PASS [100/100]  
Testing ...

my_test 
  prop_x FAIL (after 16 tests and 5 shrinks):
  a0 = T0 { f0 : 120201, ... }
  a1 = T0 { ... }
  ... 

  RHS = True 
  LHS = False
  
  seed = 123212123

  Rerun with 
    aiken check -m "my_lib/my_test.{prop_x}" --args " [ T0 { }] ... "

Functionality

Aiken compiler finds all tests. Any tests with args are assumed subject to property based testing.

Property config is global, rather than local.

The test is compiled as if it were a parametrized validator. Separate gen and shrink functions are also compiled.

To evaluate the test, the generator(s) are run to generate input for the test. Then the args are applied, and the code evaluated. On success this is repeated until repeat number of successes. On failure, the shrinker is employed to seek a simpler failure case.