[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