Writing Neighbour Functions
Explore how to write neighbor functions that improve property-based testing in Erlang using simulated annealing principles. Understand how to use the ?USERNF macro, incorporate temperature parameters, and balance variation with search efficiency to produce more effective test cases. This lesson guides you through practical examples and tips for refining your test data and improving search outcomes with targeted properties.
We'll cover the following...
Neighbor functions
A core part of simulated annealing is effective neighbor selection. The neighbor is the next arbitrary modification to apply to the data to advance the search. In a graph trying to find the shortest path between all nodes, it might be easiest to pick paths to swap. In a tree, it might be to remove or add a node. In a pathfinding exercise, it may be to add one or five steps.
To submit our own neighbor function, we must use the ?USERNF(Generator, Next) macro. The generator value is the same generator pattern we’d usually give to ?FORALL_TARGETED. The next argument is where we pass in the function used to create the next value. The overall patterns look like this:
prop_example() ->
?FORALL_TARGETED(Var, ?USERNF(list(integer()), next_list()),
some_check(Var).
next_list() ->
fun(PreviousValue, {Depth, CurrentTemperature}) ->
?LET(Val, some_generator(),
modify(Val, PreviousValue))
end.
The tricky stuff is all within the next_list() function. This is a function with no arguments that must return an anonymous function. That anonymous function itself takes two arguments. The previously generated term ((PreviousValue)) that we’re searching on and the temperature parameters. Depth is a positive integer representing how deep the generator is nested, and CurrentTemperature is any number, representing the ...