<div dir="ltr"><div><div><div><div><div><div><div><div>Hi,<br><br></div>My solution is to think functionally: decouple computation from effect. You don't have to go total Monadic style, but split the code which computes results from the code which has some kind of side-effect (messaging, file I/O, network I/O, etc). This lets you test your code by injecting the data into the computational chain in a unit test, and run the effectful tests separately from that. In your case, we might run 3 operations:<br><br></div>1. Compute what data we need<br></div>2. Message an external party for that data<br></div>3. Compute a result based on that data<br><br></div>This suggests we need 3 functions, where the middle function has a side effect. But functions 1 and 3 can be unit-tested under the assumption of a result from 2. And 2 can be tested independently of 1 and 3. Thinking a bit more like an Erlang programmer would, this is a finite state machine(!). The function 1 returns a *Command* to a command runner, which then executes 2 and feeds the result back into the state machine. The runner then executes 3. If you decouple the generation of what-to-do from actually doing it, testing is easy.<br><br></div>The aside here is that it is exactly what you would do in a monadic style implementation of the same. You define the program as a Free monad (i.e., as a program which you can interpret in several different ways). Then you run said program in a test-interpreter when you want to test the program. But you run it in a "real system" when you want it to execute the program in the real world.<br><br></div>In practice, I like to build the above model because it enables you to test your program without ever having to run the system as a whole. Once you start having many different data sources and dependent processes, you have to think about isolation of them in test settings. Otherwise it becomes prohibitively hard to work with the system as you add more complexity.<br><br></div>The astute reader will recognize that the above is similar to the Elm Architecture, but that is a subject for another post :)<br><br></div><br><div class="gmail_quote"><div dir="ltr">On Wed, Mar 28, 2018 at 4:10 PM Brujo Benavides <<a href="mailto:elbrujohalcon@gmail.com">elbrujohalcon@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div style="word-wrap:break-word;line-break:after-white-space">Not so long ago I would’ve gone with #3 immediately, but lately my co-worker <a href="https://github.com/marcelog" target="_blank">Marcelo Gornstein</a> is slowly persuading me of using something like #2.<div>I think he’s about to write a blog post exactly about that.<br><div>
<div style="color:rgb(0,0,0);font-family:'Trebuchet MS';font-size:14px;font-style:normal;font-variant-caps:normal;font-weight:normal;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px"><div><span id="m_-5921855790946363024docs-internal-guid-e691a4cc-056a-0210-b8b7-ea8d87d888ad"><span style="font-size:11pt;font-family:Arial;font-weight:700;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;vertical-align:baseline;white-space:pre-wrap"><br class="m_-5921855790946363024Apple-interchange-newline"><hr></span></span></div></div></div></div></div><div style="word-wrap:break-word;line-break:after-white-space"><div><div><div style="color:rgb(0,0,0);font-family:'Trebuchet MS';font-size:14px;font-style:normal;font-variant-caps:normal;font-weight:normal;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px"><div><div><b><i><a href="http://about.me/elbrujohalcon" target="_blank">Brujo Benavides</a></i></b></div></div></div></div></div></div><div style="word-wrap:break-word;line-break:after-white-space"><div>
<div><br><blockquote type="cite"><div>On 28 Mar 2018, at 10:18, Sölvi Páll Ásgeirsson <<a href="mailto:solvip@gmail.com" target="_blank">solvip@gmail.com</a>> wrote:</div><br class="m_-5921855790946363024Apple-interchange-newline"><div><div>Hi all<br><br>I'm wondering about various different approaches to writing testable<br>Erlang and I'd be super interested in hearing about how you approach<br>this.<br><br>As a concrete example, let's assume that we're building software that<br>talks to financial exchanges.<br>When submitting orders, you might need to map symbols to/from order<br>book ids, so you run a named gen_server that:<br><br>- Knows how to map an order book id to symbol (i.e., 1234 -> ERIC)<br>- Knows how to map symbol to id (ERIC -> 1234)<br>- Knows how to refresh it's map periodically from an external service,<br>such as a database or whatever<br><br>Then you want to test your protocol handler, which depends on this id<br>mapping server, but you don't want to make an expensive call to an<br>external service for every test you run, so you want to provide fake<br>data.<br><br>I see a few different possible approaches to orchestrate testing of<br>this, but I'm having a hard time figuring out which of these I should<br>follow in general:<br><br>1. You can start the protocol handler with a parameter, IdMapMod ::<br>module(), and implement a stub_idmap module that knows how to map<br>to/from certain ids to use in your tests.<br>The downside of this is that all calls from the protocol handler to<br>the id map must extract a dynamic module name from the state, and then<br>you lose dialyzer, xref, ...<br>Another downside(or upside?) is that you won't actually perform any<br>actual calls to your id mapping server.<br><br>2. You can start the id mapping server with a parameter,<br>ExternalServiceMod :: module(), and fake out the external service call<br>only.<br>Then your tests of the protocol handler would actually exercise the id<br>mapping server; but it's calls to an external service would be<br>answered with a fake stub.<br>This way you get the benefit of dialyzer + xref as your protocol<br>handler simply calls id_map:symbol_to_id/1 or whatever.<br><br>3. You can use a mocking library such as meck to do something very<br>similar to either of the two above things.<br>In past lives using other languages, I've generally developed a<br>distaste for mocking, as I've felt that it can create brittle tests.<br>Generally, I think I'd prefer to be able to test things in terms of<br>interfaces, but I might be swayed otherwise.<br><br>Is there anything else I'm missing?  How do you write testable Erlang?<br> Is there a general approach you follow?<br><br>Thanks & regards<br>Sölvi Páll Á<br>_______________________________________________<br>erlang-questions mailing list<br><a href="mailto:erlang-questions@erlang.org" target="_blank">erlang-questions@erlang.org</a><br><a href="http://erlang.org/mailman/listinfo/erlang-questions" target="_blank">http://erlang.org/mailman/listinfo/erlang-questions</a><br></div></div></blockquote></div><br></div></div>_______________________________________________<br>
erlang-questions mailing list<br>
<a href="mailto:erlang-questions@erlang.org" target="_blank">erlang-questions@erlang.org</a><br>
<a href="http://erlang.org/mailman/listinfo/erlang-questions" rel="noreferrer" target="_blank">http://erlang.org/mailman/listinfo/erlang-questions</a><br>
</blockquote></div>