Journal: Property-Based Testing with PropEr, Erlang, and Elixir

To be a more productive reader when rereading a book, it is very convenient to create small rebar3 projects based on books’ samples and ideas. Here’s what I’ve already made.

5 Likes

Corresponding tweet for this thread:

Share link for this tweet.

2 Likes

Exercise 2

I add output and then run it to see it execute.
There is peace of my code with output:

increments([Head | Tail]) -> increments(Head, Tail).

increments(_, []) ->
    io:format("~n"),
    true;
increments(N, [Head | Tail]) when Head == N + 1 ->
    io:format("~p", [Head]),
    if length(Tail) > 0 -> io:format(", ");
       true -> io:format("|")
    end,
    increments(Head, Tail);
increments(_, _) -> false.
3 Likes

Erlang
(page 88)
code/CustomGenerators/erlang/pbt/test/prop_generators.erl

I would like to share inner step calculation while gathering values (by groups of 10). I was confused a litter how is the calculation going.

I have inserted an expression to print the parameters.

prop_collect2() ->
    ?FORALL(Bin, (binary()),
            (collect(to_range(10, byte_size(Bin)),
                     is_binary(Bin)))).

%%%%%%%%%%%%%%%
%%% Helpers %%%
%%%%%%%%%%%%%%%
to_range(M, N) ->
    Base = N div M,
    io:format("~2B = ~2B div ~B | {~2B, ~B} = {~2B * ~B, (~B + 1)*~B~n",
              [Base, N, M, Base * M, (Base + 1) * M, Base, M, Base, M]),
    {Base * M, (Base + 1) * M}.

Command:
rebar3 proper​ -p prop_collect2

Output (with the intermediate calculations):

 0 =   0 div 10 | { 0, 10} = { 0 * 10, (0 + 1)*10
. 0 =  1 div 10 | { 0, 10} = { 0 * 10, (0 + 1)*10
. 0 =  0 div 10 | { 0, 10} = { 0 * 10, (0 + 1)*10
. 0 =  0 div 10 | { 0, 10} = { 0 * 10, (0 + 1)*10
...
. 3 = 38 div 10 | {30, 40} = { 3 * 10, (3 + 1)*10
. 3 = 33 div 10 | {30, 40} = { 3 * 10, (3 + 1)*10
. 1 = 15 div 10 | {10, 20} = { 1 * 10, (1 + 1)*10
. 2 = 22 div 10 | {20, 30} = { 2 * 10, (2 + 1)*10

OK: Passed 100 test(s).

56.00% {0,10}
28.00% {10,20}
 9.00% {20,30}
 7.00% {30,40}
===>
1/1 properties passed
2 Likes

Erlang
(page 89)
code/CustomGenerators/erlang/pbt/test/prop_generators.erl

While running property based test prop_dupes you will get the same logging info.

prop_dupes() ->
 ?FORALL(KV, list({key(), val()}),
        begin
     M = maps:from_list(KV),
     _ = [maps:get(K, M) || {K, _V} <- KV], % crash if K's not in map
     collect(
       {dupes, to_range(5, length(KV) - length(lists:ukeysort(1,KV)))},
       true
     )
  end).


%%%%%%%%%%%%%%%
%%% Helpers %%%
%%%%%%%%%%%%%%%
key() -> integer().
val() -> term().

to_range(M, N) ->
    Base = N div M,
    io:format("~2B = ~2B div ~B | {~2B, ~2B} = {~2B * ~B, (~B + 1)*~B~n",
              [Base, N, M, Base * M, (Base + 1) * M, Base, M, Base, M]),
    {Base * M, (Base + 1) * M}.

Full code project

maps:from_list/1
maps:get/2
lists:ukeysort/2
length/1

2 Likes

Erlang
(page 91)
code/CustomGenerators/erlang/pbt/test/prop_generators.erl

I have compared results alternative PropEr generators - oneof, union and elements. Here’s what I noticed. The first two generators after 10 launches in the resulting report give 3-4 lines, and the third only 3.

Source code projects:

  • ex05_oneof
    82.00% {dupes,{0,5}}
    11.00% {dupes,{5,10}}
    5.00% {dupes,{10,15}}
    2.00% {dupes,{15,20}}
    ===>
    1/1 properties passed
  • ex06_union
    83.00% {dupes,{0,5}}
    10.00% {dupes,{5,10}}
    5.00% {dupes,{10,15}}
    2.00% {dupes,{15,20}}
    ===>
    1/1 properties passed
  • ex07_elements
    82.00% {dupes,{0,5}}
    13.00% {dupes,{5,10}}
    5.00% {dupes,{10,15}}
    ===>
    1/1 properties passed
2 Likes

Erlang
(page 92)
code/CustomGenerators/erlang/pbt/test/prop_generators.erl

prop_aggregate() ->
    Suits = [club, diamond, heart, spade],
    ?FORALL(Hand, (vector(5, {oneof(Suits), choose(1, 13)})), 
             begin 
			 % io:format("~p~n", [Hand]),
			 aggregate(Hand, true)
			 end). % `true' makes it always pass

If you would like to know what info contains Hand variable, just uncomment logging line.

Full code project

Note that what kind of PropEr API here is. There are generators and wrapper that can be used to collect statistics on the distribution of test data:

2 Likes

Erlang
(page 97)
code/CustomGenerators/erlang/pbt/test/prop_generators.erl

To read more about resize(Size, Generator) function, please go to the documentation page.

Full code project

2 Likes

Erlang
(page 98)
code/CustomGenerators/erlang/pbt/test/prop_generators.erl

Note that in the property prop_profile1proplists module is used.

2 Likes

Erlang
(page 99)
code/CustomGenerators/erlang/pbt/test/prop_generators.erl

Note that macros ?SIZED(VarName, Expression) is contained in the include/proper_common.hrl file.
It is proper_types:sized(fun(VarName) -> Gen end)) function call.

2 Likes

Erlang
(page 100)
code/CustomGenerators/erlang/pbt/test/prop_generators.erl

Note that when you lounch propterties in a command line there should be no spaces between names in the property name list -p prop_profile1,prop_profile2:
$ rebar3 proper -m prop_generators -p prop_profile1,prop_profile2

Otherwise, you will receive an error message with the following content:

===> Property [] does not belong to any module in ["prop_generators"]
2 Likes

Erlang
(page 100)
code/CustomGenerators/erlang/pbt/test/prop_generators.erl

Let’s take a closer look at the expression ?SIZED(Size,resize(min(100,Size)*35,string())).

Note that there is erlang:min function is used. To ensure maximum size without imposing the maximum value you can use in the erlang:max function:

OK: Passed 100 test(s).

27.00% {name,{0,10}}
12.50% {name,{10,20}}
 8.00% {name,{20,30}}
 5.50% {bio,{2400,2700}}
 5.50% {bio,{3000,3300}}
 5.00% {bio,{0,300}}
 5.00% {bio,{300,600}}
 5.00% {bio,{1800,2100}}
 4.50% {bio,{1500,1800}}
 3.50% {bio,{600,900}}
 3.50% {bio,{900,1200}}
 3.50% {bio,{1200,1500}}
 3.50% {bio,{2700,3000}}
 3.00% {bio,{2100,2400}}
 2.50% {bio,{3300,3600}}
 2.50% {name,{30,40}}
===>
1/1 properties passed

Full code project

2 Likes

Erlang
(page 102)
code/CustomGenerators/erlang/pbt/test/prop_generators.erl

Note that binding is the main purpose of this macro ?LET. It is implemented using a function proper_types:bind(RawType,fun(X) -> Gen end,false).
Definition ?LET macro contains in include/proper_common.hrl file.

More information related to queue module you can see in the Erlang documentation.

Macros ?LET helps create new generator queue.

Full projects related to using ?LET macros.

2 Likes

Erlang
(page 103)
code/CustomGenerators/erlang/pbt/test/prop_generators.erl

Pay attention to the peculiarity in the implementation of the macro ?SUCHTHAT. It is a function call

-define(SUCHTHAT(X,RawType,Condition),
        proper_types:add_constraint(RawType,fun(X) -> Condition end,true)).

Note that in your code (test/prop_generators.erl) you can not define non_empty generator because it is already defined in ProEr API. If you try to do it we will get an error message:

===> Compiling test/prop_generators.erl failed
test/prop_generators.erl:19: defining imported function non_empty/1

Let’s see how non_empty/1 generator can be used - rebar3 project.

2 Likes

Erlang
(page 104)
code/CustomGenerators/erlang/pbt/test/prop_generators.erl
Explore Filtering
It was interesting for me to check (reproduce) the error described by the author - the unsuccessful creation of the generator when filtering using ?SUCHTHAT macro.

  • Making mistakes is fun.
  • Messages contain more details to help you with testing. Here is the full text of one of the possible errors:
    $ rebar3 proper -p prop_list_content_integer
Error: Couldn't produce an instance that satisfies all strict constraints from (prop_generators:check_list_content_integer/1) after 50 tries.
===>
0/1 properties passed, 1 failed
===> Failed test cases:
prop_generators:prop_list_content_integer() -> {error,
                                                {cant_generate,
                                                 [{prop_generators,
                                                   check_list_content_integer,
                                                   1}]}}

Code examples to help you reproduce this error.

2 Likes

Erlang
(page 104)
test/prop_generators.erl

In order to better understand the difference between macros ?LET and ?SUCHTHAT, try to implement of your own examples and you will see this difference.
Practice at work is very important, and doing exercises such as this is a wonderful solution.
Here is a project that demonstrates the use of these macros.

2 Likes

Erlang
(page 105)
test/prop_generators.erl

Latin1

When you start running property filtering Latint1 characters

rebar3 proper -p prop_latin1_string

you, likely, received a message from PropEr:

Error: Couldn't produce an instance that satisfies all strict constraints from (prop_generators:latin1_string2/0) after 50 tries.

To increase the number of attempts to 80 (this number of attempts should be enough), use the PropEr option --constraint_tries.

The complete command will look like this:

rebar3 proper -p prop_latin1_string --constraint_tries 80

To simplify the problem being solved - generating encoded Latin1 strings, it would be better to set a numerical segment and generate numbers (codes by symbols) only from printable segment:

prop_latin1_string2() ->
    ?FORALL(String, latin1_string2(),
            is_list(String)
        ).

latin1_string2() ->
  ?SUCHTHAT(S, ?SIZED(Size,vector(Size,integer(33, 126))), io_lib:printable_latin1_list(S)).

Now this property does not need any additional parameters of the testing framework PropEr.

Project

2 Likes

Erlang
(page 105)
test/prop_generators.erl

Unicode strings

For generating Unicode strings, the function proper_unicode:utf8() is quite handy.

To suppress the generation of empty strings, use the non_empty/1 generator.

You will find the implementation of the ideas for generating Unicode strings, which the author described, in the project.

2 Likes

Erlang
(page 106)
test/prop_generators.erl

For implementing predictable tests, the generator frequency is well suited for probability management.

Examples of using this this generator in the property based tests can be found in the rebar3 project.

2 Likes

Erlang
(page 108)
test/prop_generators.erl

It is best to view the result of the methods’ work directly in the property code. I made it.
To make the test more convenient for creating properties, I added non_empty generator.

rebar3 project

2 Likes