Erlang EUnit – continuation 2 – Test Representation


Having passed through basics and some setup / teardown, it’s time to discuss test representation, a very broad subject which invites for misconceptions.

Basics of Representation

As always, the foundation for the discussion is the EUnit manual pages, where it is stated that a test can be defined in many different ways, and to keep the discussion very short, I shall supply some common examples of what is often seen/used.

Simple Test Objects

Each representation form will be shown with examples based on the mylist module defined in the first post. So, each Simple Test Object (STO) can be defined by

Any nullary function (function with zero arguments)

What this implies is that your STO is encapsulated within a functional object and may be executed  by EUnit. More or less you can put yourself in EUnit’s shoes (some very special shoes) and the user gives you a “black box”, which you simply execute. No need to care about other stuff. Just execute it. Examples of STO’s

nullarySTO1() -> ?assertEqual(6,mylist:sum([1,2,3])).            
fun() -> ?assertEqual(6,mylist:sum([1,2,3])) end.
fun nullarySTO1/0

These are fundamental nullary STO’s

A Module Function Tuple

This is just a different way of reaching the nullary function by the module and function name. Here it is implicit that the functionname is a nullary function. This will not work with {mylist_tests, nullarySTO2 } which will be seen below. Why? Because nullarySTO2() RETURNS a nullary functional object.

tupleSTO1() -> { mylist_tests, nullarySTO1 }.

A Linenumber / STO Tuple (generated by _test(Expr) macro)

According do our best friend, (the user’s guide) “LineNumber is a nonnegative integer …. LineNumber should indicate the source line of the test”. Wow, so it’s the linenumber in the sourcecode for where the tests is. For some reason, this can be written by hand, but luckily the _test(Expr) macro does it for us.

tupleSTO2() ->?_test( begin ?assertEqual(6,mylist:sum([1,2,3])) end ).

Test Sets and Test Generator Basics

One last thing on the basics of representation is that EUnit does not make any “type”-difference between a deep list of STO’s and single STO, that is.

TestSet := [ TestSet ] | STO

So, a Test-Set can be a so called deep list of Test-Sets, to any level, or just a single STO. Also, a Test Generator is a function that ends with _test_() and returns a TestSet. End of story. Like this

my_generator1_test_() -> fun nullarySTO1/0.
my_generator2_test_() -> [ fun nullarySTO1/0, tupleSTO2() ]

What we have seen is all valid erlang EUnit. To prove it, let us place it in a grand EUnit file, compile and run. Below follows the example mylists_tests.erl module, with compilation and running as usual.

EUnit Example File

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

nullarySTO1() ->?assertEqual(6,mylist:sum([1,2,3])).
nullarySTO2() ->fun() ->?assertEqual(6,mylist:sum([1,2,3])) end. 

tupleSTO1() ->{ mylist_tests, nullarySTO1 }.

tupleSTO2() ->?_test( begin ?assertEqual(6,mylist:sum([1,2,3])) end ).

generator_test_() ->   
    [
     fun nullarySTO1/0,
     nullarySTO2(),
     tupleSTO1(),
     tupleSTO2()
    ].

my_generator_test_() ->
    fun nullarySTO1/0.

That is the end of the module, next is the compilation and running.

zentop:EunitBasic3 zenon$ erlc -o ebin/ src/*.erl test/*.erl
zentop:EunitBasic3 zenon$ erl -pa ebin/
Erlang R14B03 (erts-5.8.4)  [64-bit] [smp:8:8] [rq:8] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.8.4  (abort with ^G)
1> eunit:test(mylist,[verbose]).       
======================== EUnit ========================
module 'mylist'
  module 'mylist_tests'
    mylist_tests: generator_test_...ok
    mylist_tests: nullarySTO2...ok
    mylist_tests: nullarySTO1...ok
    mylist_tests:10: tupleSTO2...ok
    mylist_tests: my_generator_test_...ok
    [done in 0.014 s]
  [done in 0.014 s]
=======================================================
  All 5 tests passed.
ok

Great! Now, as we know how to write, set up and clean STO’s and TestSets. The next lesson is on Test Control!

Leave a comment

3 Comments

  1. I’m afraid there’s a bug in several of the tests.

    Try changing tupleSTO4/0 so that it should fail. For example:

    tupleSTO4() -> ?_test( fun() -> ?assertEqual(1,2) end ).

    Run the tests and you’ll notice that it still passes!

    ?_test returns its argument wrapped in a fun. So when you give it a fun to start with you get a fun that returns a fun, which is the same thing as a test object that can never fail.

    Now break nullarySTO1 in the same way so that it fails and you’ll see that the other tests that should call it – nullarySTO2, nullarySTO3, tupleSTO1, tupleSTO2, tupleSTO3, tupleSTO6 and tupleSTO7 – all still pass. It is pretty much the same problem here; a test generator can return one or more funs, but you return funs that return funs, and that is one level too deep.

    Reply
    • Per, you are absolutely right!
      This shows not only how “tricky” EUnit can be – but also why it’s so important to always start with a failing test! (Something the author of this post did not – ahem) .

      Reply
  2. Finally got the time in my heart to update this – made it shorter and simpler as well.

    Reply

Leave a reply to gianfrancoalongi Cancel reply