Fl4m3Ph03n1x
Background
I have a process that does nothing in it’s init function and delegates all the work to a handle_continue. I do this because the work being done in the handle_continue is quite heavy, and since this is a Worker process, I don’t want to slog it’s supervisor (and thus the entire application) with the slow initialization of a Worker (of which there can be hundreds or thousands of).
Problem
The problem here comes when testing. When using ExUnit, it will execute the code assertions right after the init function of my Worker, which I remind you, does next to nothing.
So effectively, ExUnit is sometimes running the test assertions before the Worker has even initialized. I say sometimes, because everything is concurrent, so sometimes I am lucky and the assertions run after the Worker’s handle_continue has run, sometimes they don’t.
Questions
Is there a way to make ExUnit run the assertions without forcing the Worker process to send a message in handle_continue signaling it? (think of it as forcing the Worker to broadcast a message once handle_continue is done running).
I ask this because I frown upon this idea. If I change the Worker to broadcast a message once it’s handle_continue is done, then I am just changing my production code for the sake of testing, which is something I abhor completely.
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #ai
- #elixirconf-us
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 31 to 22- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
sorentwo
That’s right. Usually it passes on the second or third attempt locally, but if the system is noisy or it is running in CI there is some breathing room.
This is essentially how assertions work in web testing frameworks like Capybara/Hound.
peerreynders
That’s a good point. I would think nothing of adding a
:stop/:shutdownmessage to the process even if production doesn’t use it. That way the test case could:stopmessage:EXITor:DOWN)swelham
I have been dealing with this same issue recently of wanting to wait for
handle_contiueto complete before I start testing my server.I found a the simplest solution was to just put in a call to the server since the message won’t be processed until after the continue has completed. I didn’t want to wrote code in a
handle_calljust for testing so I used the erlang sys module for this since it provides convince debug functions for working with processes. I found calling:sys.get_state(server_pid)often did the trick. This way I don’t have to use some arbitrary sleep duration to try and guess how long it will take.peerreynders
Maybe I simply prefer Inside-Out testing in order to control test maintenance costs (giving up some defect localization).
Once one becomes diligent with testing it’s important to find ways to balance the value of testing with the burden it imposes - test obsessed can go too far.
Kent Beck’s (2008) opinion just recently surfaced here again.
There are times where “should I even be testing this”, “should I be testing this particular aspect” or “should I be testing it in this manner” are valid questions.
chrismcg
Sure but I’d be fine with that personally in this particular case which I think of as sort of a last resort. It’s a tradeoff between adding something to a public API just for the tests and knowing a small amount about the internal implementation.
I don’t think I’d use this technique a lot, I’d much prefer to structure the code not to need it or wait for something publicly observable like a registry entry. I do remember a couple of times where I’d have been happy to use this instead of changing the code though.
If you did make changes like you said then the fix shouldn’t be too hard to work out. The code to do this could easily be extracted to a “start_and_wait” function if it was used in multiple tests so there would only be one place to change.
If I was reviewing some code and I saw something like this I would definitely raise a flag though, especially if it was tied to application specific implementation details.
LostKobrakai
Take an implementation before
handle_continuebecame a thing usingProcess.send_after. You couldn’t change this to usehandle_continuewithout breaking the test. Or maybe usinggen_statemat some point makes more sense, which has different callbacks, you’d also need to change the test.chrismcg
It’s knows the name of the module, the
:handle_continuefunction, and it’s arity. In my example it knows the return args as well but you don’t need those if you just care about whether the function returned or not. I personally would not rate that as “severely” coupled in this case because:handle_continueis part of OTP not some internal function someone could rename during a refactoring.Why do you rate it severe?
LostKobrakai
I just want to mention one big caveat with using tracing in that context: It’ll quite severely couple the test to the implementation.
chrismcg
I have played with this a bit more and come up with a version that only gets a message when
handle_continueis returned from:chrismcg
Back at the top you said that
The BEAM provides this through it’s built in tracing facilities. The code I prototyped showed how to get a message in your test process when
handle_continueis finished.This line says “I want to trace function calls and their returns in all new processes (and ports) created from now, please send me a message for each one”. The
trace_patterncall in the next line says “Actually I only want a message if it’s a call to or return fromhandle_continuein a specific module” (:localis needed to make the return tracing work).When that is setup the SUT process is started. BEAM will then send messages to the test process for calls and returns that match what’s been asked for. Those messages can be waited for and once the
:return_tomessage has been received you know thathandle_continuehas finished.If you just waited on the
:callmessage there would still be a race condition as yourLogic.establish_connectioncould still be running when the test process started running the asserts.HTH. I haven’t actually used this technique though I can think of times that I might have now!