Erlang EUnit – introduction


Why do I want to have a battery of EUnit tests?

  1. Show that each part of the module is working as expected (Unit testing)
  2. Be able to see if/how new code breaks existing code (Regression testing)

As part of TDD (Test Driven Development) one should ideally write the tests first, and later write the code that makes the tests go through. This will automatically give you a set of Unit + Regression tests, so that each step in your development cycle is “fastened” in a secure place (a “green light”).

How do I write my first EUnit test?

Now, assuming you have not followed TDD and have an untested list-processing module (module is for demonstration purposes only and serves no other reason)

-module(mylist).
-export([sum/1,product/1,odds/1]).

sum([X|R]) -> X + sum(R);
sum([]) -> 0.

product([X|R]) -> X * product(R);
product([]) -> 1.

odds(List) -> odds(List,1).
odds([X|R],N) when N rem 2 == 1 -> [X | odds(R,N+1)];
odds([_|R],N) -> odds(R,N+1);
odds([],_) -> [].

Thus this module could be tested with the separate test module called mylist_tests
(This is good practice since you do not wish to clutter the logic – your real module, with test cases, thus your tests also become portable and regression testing with different versions of code becomes easier).

-module(mylist_tests).
-include_lib("eunit/include/eunit.hrl").

sum_test() ->
    ?assertEqual(0, mylist:sum([])),
    ?assertEqual(0, mylist:sum([0])),
    ?assertEqual(6, mylist:sum([1,2,3,4,-4])).

product_test() ->
    ?assertEqual(1, mylist:product([])),
    ?assertEqual(2, mylist:product([2])),
    ?assertEqual(36, mylist:product([2,3,2,3])).

odds_test() ->
    ?assertEqual([], mylist:odds([])),
    ?assertEqual([1], mylist:odds([1])),
    ?assertEqual([1,3,5], mylist:odds([1,2,3,4,5])).

That was the whole test module, now there are some interesting points with the test module.

  1. The test module name ends with _tests, this is to allow the eunit test function to find the tests for your module by simply referencing the source module (mylist.erl)
  2. The test module does not export any functions explicitly
  3. The test module includes the eunit header file -include_lib(“eunit/include/eunit.hrl”), this is a most important part since this will automatically export all the tests in the module and cause them to be executed once we start.
  4. Test function names end with _test(), this is a requirement for eunit to identify tests

How do I run my eunit tests?

Save the mylists.erl and mylists_tests.erl in the same directory (for this basic guide).

zen:ErlangBlog zenon$ cd EUnitBasic/
zen:EUnitBasic zenon$ erlc *.erl
zen:EUnitBasic zenon$ ls *.beam
mylist.beam mylist_tests.beam

Next, start erl, and run the tests with the test() function, as follows

zen:EUnitBasic zenon$ erl
Erlang R13B04 (erts-5.7.5) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.7.5 (abort with ^G)
1> eunit:test(mylist).
All 3 tests passed.
ok

All right! Seems to work. However, we had the test module in the same directory as the source module, which is not good practice.

Testing like a gentleman and a good lady

Make a clear division between test code and source code (well, clearly all of it is source anyway), also we wish our beam files to be stored in a separate directory

zen:EUnitBasic zenon$ rm -iv *.beam
zen:EUnitBasic zenon$ mkdir src
zen:EUnitBasic zenon$ mkdir test
zen:EUnitBasic zenon$ mkdir ebin
zen:EUnitBasic zenon$ mv mylist.erl src/
zen:EUnitBasic zenon$ mv mylist_tests.erl test/

So, what did I do? I removed our previous mess (the beams), created the source directory, the test directory and the ebin directory. Each one of them should contain source modules, test modules and beam files.

Next off, lets compile properly and run the tests, this time standing at the top level

zen:EUnitBasic zenon$ tree .
.
├── ebin
├── src
│ └── mylist.erl
└── test
  └── mylist_tests.erl

3 directories, 2 files

This proves that I am in the top directory of my little “project”.

Next let me compile and output all beams to the ebin/ also I have to tell erlc where to find the source module and the test module.

zen:EUnitBasic zenon$ erlc -o ebin/ src/*.erl test/*.erl

Next, let’s check that all beam files where generated into the ebin/ directory as promised.

zen:EUnitBasic zenon$ tree .
.
├── ebin
│ ├── mylist.beam
│ └── mylist_tests.beam
├── src
│ └── mylist.erl
└── test
  └── mylist_tests.erl

Seems good, let’s run the tests then. From the top level, but telling erl how to find the beams. Also, let’s do it verbally.

zen:EUnitBasic zenon$ erl -pa ebin/
Erlang R13B04 (erts-5.7.5) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.7.5 (abort with ^G)
1> eunit:test(mylist,[verbose]).

======================== EUnit ========================
module 'mylist'
module 'mylist_tests'
mylist_tests: sum_test...ok
mylist_tests: product_test...ok
mylist_tests: odds_test...ok
[done in 0.009 s]
[done in 0.009 s]
=======================================================
All 3 tests passed.

ok

Wonderful! This was what we wanted. Now we have the basics set, and next post on this topic will teach us how to do proper test environment setup and cleanup.

All the basis can be found in the EUnit users guide.

Leave a comment

5 Comments

  1. Cody Rioux

     /  November 12, 2011

    Thanks for the quick intro! I’m just getting started with Erlang and I think baking in some TDD skills from the start might really help me out.

    Reply
  2. This is what I need, thank you very much!

    Reply
  3. This is great! I plan to work through your tutorials in the coming weeks. Thanks for the share!

    Reply
  1. TDD: Links, News And Resources (5) « Angel ”Java” Lopez on Blog
  2. Erlang: Links, News And Resources (3) | Angel "Java" Lopez on Blog

Leave a comment