views:

443

answers:

3

Hi.

When writing code in Java, it is very helpful to embrace composition and dependency injection to make it possible and easy to do pure unit testing by mocking collaborating objects.

I find that doing the same in Erlang is less straightforward and makes for dirtier code.

That's likely to be my fault, as I'm quite new to Erlang and quite addicted to JUnit, EasyMock and java interfaces...

Let's say I have this stupid function:

%% module mymod
handle_announce(Announce) ->
    AnnounceDetails = details_db:fetch_details(Announce),
    AnnounceStats = stats_db:fetch_stats(Announce),
    {AnnounceDetails, AnnounceStats}.

When unit testing mymod, I only want to prove that details_db and stats_db are invoked with the right parameters, and that the return values are the used correctly. The ability od details_db and stats_db to generate correct value is tested in other places.

To solve the problem I could refactor my code this way:

%% module mymod
handle_announce(Announce, [DetailsDb, StatsDb]) ->
    AnnounceDetails = DetailsDb:fetch_details(Announce),
    AnnounceStats = StatsDb:fetch_stats(Announce),
    {AnnounceDetails, AnnounceStats}.

And test it this way (basically stubbing the calls directly into the test module):

%% module mymod_test
handle_announce_test() ->
    R = mymod:handle_announce({announce, a_value}, [?MODULE, ?MODULE, ?MODULE]),
    ?assertEqual({details,stats}, R).

fetch_details({announce, a_value}) ->
    details.

fetch_stats({announce, a_value}) ->
    stats.

It works, but the application code becomes dirty and I always have to carry around that ugly list of modules.

I've tried a couple of mock libraries (erlymock and (this other one) but I wasn't satisfied.

How do you unit test your erlang code?

Thanks!

+13  A: 

There are two things to consider here...

You need to separate out all your code into 2 different types of modules:

  • pure functional modules (aka side-effect free modules)
  • modules with side-effects

(You should read up on that and be sure that you understand the difference - the most typical side-effect - and the one that is in your sample code - is writing to the database).

The modules that are pure functional become trivial to test. Each exported function (by definition) always returns the same values when the same values are put in. You can use the EUnit/Assert framework that Richard Carlsson and Mickael Remond wrote. Bish-bash-bosh, job's a good 'un...

The key thing is that about 90% of your code should be in pure functional modules - you dramatically shrink your problem. (You might think this is not 'solving' your problem, merely 'reducing' it - and you would be mostly right...)

Once you have achieved this separation the best way to unit test the modules with side-effects is to use the standard test framework.

The way we do this is not to use mock-objects - but to load the database in the init_per_suite or init_per_test functions and then run the modules themselves...

The best way is to move straight over to system tests as soon as possible for this though as the unit tests are a pain to maintain - so enough unit tests to get you to a system-test round-trip and no more (even better delete the db unit tests as soon as possible).

Gordon Guthrie
Thanks Gordon, very well explained.I'm still trying to switch to the functional paradigm. Anyway, in this project I'm writing (a torrent tracker), all calls originate from the web layer and end up in the database, so most modules do have or depend from side-effects. I'll try the standard test framework.
Matteo Caprari
Good Erlang is to have lots of small functions. Refactor and break out your code into utility modules and you will be surprised how little of it involves writing to the database. 15 - 25 lines is a long function in Erlang.A pure function is one that takes a set of parameters, calculates on them and just returns a value - you should have a lot of them.
Gordon Guthrie
I actually disagree that you should move to integration tests as soon as possible. With parameterized modules and mocking as provided by erlymock, the given function would be trivial to test. You can test it inside the module itself by mocking the two db modules for the duration of the test. You can use this module in other tests by making the db modules be parameters of your module. Anyone looking at mocking in erlang should take a second look at erlymock -- the main problem with it is the lack of documentation, so you really have to read the source to come to terms with it.
Jon Watte
The problem with unit tests on modules with side-effects is the maintenance cost of them. Let me give a concrete example. As we built our system we had to do a lot of performance tuning and restructuring stuff. Each time we did that we had to rejig the database schema and stuff. Having system tests meant we could just fire in and do it and the test suite told us if we failed. If we had a lot of unit tests - they would have been tied to the db representation and would have had to be rewritten.
Gordon Guthrie
+3  A: 

Gordon is correct in that the main target is to test the small side-effect free functions.

But... well, it is possible to test integration as well, so lets show how one can do that.

Injection

Avoid lists to carry the parameterized dependencies. Use a record, the process dictionary, parameterized modules. The code will be less ugly.

Seams

Don't focus on variable modules as dependency seams, let processes be the seams. Hard-coding a registered process name is a lost chance at injecting a dependency.

Tools

You have already found some mocking tools that I too found lacking. So a bit of advertising for my own project: emock. It's for creating a gen-server with much less boiler-plate code. I've already thought of a few simplifications but not implemented them. :-/

Christian
I would not recommend using the process dictionary. It all seems nice and easy but it is SHARED STATE and eventually it will bite you hard with hard to find bugs. In Erlang it is very hard to conceptualise and rationalise the lifecycle of a process (although you might think you can) and a change in process lifecycle is where your clean process dictionary goes badly wrong. I know this from bitter experience :(
Gordon Guthrie
+3  A: 

I second what Guthrie says. You'll be surprised by how much of your logic can be pulled out into pure functions.

One of the things I've been tying lately with the new parameterized modules is to use paramterized modules for the dependency injection. It avoids the problem with parameter lists and process dictionaries. If you can use the recent versions of erlang that might be a good fit as well.

Jeremy Wall