[erlang-questions] Unidirectional architectures in Erlang

Fred Hebert mononcqc@REDACTED
Fri Aug 31 01:33:09 CEST 2018


On 08/30, Rodrigo Stevaux wrote:
>Message comes in, logic applies, message goes out.
>The generic servers are very easy to test in a pure fashion: just test the
>return values of handle_call for a given state.
>

That can be done as a regular unit test, yes.

>But:
>- unit testing should test that the unit under test forwards a message to a
>given address (and that is not reflected on the handle_call return value)

Aside from using mocks to know that the proper function calls have taken 
place, you can't easily test the forwarding of messages since that would 
imply side-effects turning your unit tests into integration tests.

>- doing the integration testing of a "pipeline" of process involves testing
>that ultimately a message gets to the last process.
>

Yes and no. You can test that each element of the chain properly 
forwards data to the right destination. You can do that if you make the 
endpoint/address/destination for each forwarded message parametrizable 
or configurable. Forward the data to the test process instead to confirm 
that the right side-effect has taken place.

This lets you test each section of the chain in isolation; then if all 
sections are well-behaved, you can infer that the whole chain should 
behave well. You can confirm that with a more naive end-to-end test, 
knowing that individual components are tested more thoroughly.

>Given the maturity of Erlang, I'd expect to promptly find solutions to this
>case.

On top of the techniques above, you can try using the `sys' module to 
introspect all OTP behaviours and look at their transitions as a neutral 
observer:

    1> sys:get_status(disk_log_server).
    {status,<0.75.0>,
            {module,gen_server},
            [[{<0.76.0>,'$#group_history'},
              {'$initial_call',{disk_log_server,init,1}},
              {'$ancestors',[kernel_safe_sup,kernel_sup,<0.45.0>]}],
             running,<0.66.0>,[],
             [{header,"Status for generic server disk_log_server"},
              {data,[{"Status",running},
                     {"Parent",<0.66.0>},
                     {"Logged events",[]}]},
              {data,[{"State",{state,[]}}]}]]}
    2> P = self(),
    2> sys:install(
    2>   disk_log_server,
    2>   {fun(_Acc, Event, State) -> P ! {trace, Event, State} end,
    2>    nodata}
    2> ).
    ok
    3> disk_log_server ! fake_data.
    fake_data
    4> flush().
    Shell got {trace,{in,fake_data},disk_log_server}
    Shell got {trace,{noreply,{state,[]}},disk_log_server}
    ok

Those won't let you see arbitrary calls being made, but if you have a 
process chain A -> B -> C, then you can, by looking at the traces of all 
three processes, track the history of a call and the consequences they 
have.

The sys module is full of really cool functionality few people know 
about, and that can make live debugging, but also testing much simpler.

>
>I haven't found a nice solution yet, so I suppose I am using OTP in a wrong
>way (maybe it's not a good fit for my project), or I am choosing the wrong
>architecture, or that I am missing key knowledge about how to test things
>in Erlang.

There aren't enough details to know at this point. I frequently just use 
`meck' (https://github.com/eproxus/meck) as a library, which lets you 
replace arbitrary function calls through hot code loadig, get traces and 
counts of all calls and values returned of unmodified code, and so on.

If what you're looking for is a way to track side-effects in a very 
whitebox manner, meck is hard to beat.

>
>Can anyone shed some light on this questions? Thanks in advance!

please provide more details about the nature of the side-effects you are 
trying to observe in a unit test. My default reflex would be to say that 
the moment you're testing side-effects, you are doing integration 
testing.

Regards,
Fred.



More information about the erlang-questions mailing list