Practical Erlang testing techniques

Author: Bob Ippolito (@etrepum)
Date: June 2011
Venue:Erlang Factory London 2011

Introduction

img/yuno.png

Mochi Media's Code

Doing It Wrong

Doing It Better

Cool Tools (for testing)

rebar

rebar src/yourapp.app.src

{application, yourapp,
 [{description, "..."},
  {vsn, "1.2.3"}]}.

rebar.config

{erl_opts, [fail_on_warning, debug_info]}.
{cover_enabled, true}.
{clean_files, ["*.eunit", "ebin/*.beam"]}.
{eunit_opts, [verbose,
   {report, {eunit_surefire, [{dir, "."}]}}]}.

rebarized Makefile

REBAR=`which rebar || ./rebar`
all: deps compile
deps:
    @$(REBAR) get-deps
compile:
    @$(REBAR) compile
test:
    @$(REBAR) skip_deps=true eunit
clean:
    @$(REBAR) clean

rebar alternatives

EUnit

EUnit boilerplate

-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").

%% TEST CODE HERE

-endif.

EUnit test

inc_0_test() ->
  ?assertEqual(
    1,
    increment(0)).

EUnit test generator

inc_test_() ->
  [{"inc by 0",
    fun () ->
        ?assertEqual(1, increment(0))
    end},
   {"inc by 1",
    ?_test(?assertEqual(2, increment(1)))}].

EUnit fixture

inc_setup() -> return_value_from_setup.

inc_cleanup(setup_return_value) -> ok.

inc_fixture_test_() ->
  {foreach,
   fun inc_setup/0,
   fun inc_cleanup/1,
   [{"inc by 0",
     ?_test(?assertEqual(1, increment(0)))}]}.

running EUnit tests

$ make test
==> inc (eunit)
Compiled src/inc.erl
================ EUnit ================
module 'inc'
  inc: inc_0_test...ok
  […]
 [done in 0.012 s]
=======================================
  All 4 tests passed.
Cover analysis: […]/.eunit/index.html

EUnit alternatives

cover

cover html output

meck

meck usage (constants)

-define(WHENEVER, 1303513575954).

statebox_test() ->
  meck:new(statebox_clock),
  meck:expect(statebox_clock,
    timestamp, 0, ?WHENEVER),
  […],
  meck:unload(statebox_clock).

meck usage (funs)

next_minute_test() ->
  meck:new(mochierl_util),
  meck:expect(mochierl_util, now_to_msec,
    fun() -> 55000 + 60000 * 123345 end),
  […],
  meck:unload(mochierl_util).

meck fixture for EUnit

meck_setup() ->
  Modules = [mocked_modules, …],
  meck:new(Modules),
  Modules.

meck_fixture_test_() ->
  {foreach,
   fun meck_setup/0,
   fun meck:unload/1,
   [{"meck test…",
     […]}]}.

meck caveat: OTP modules

meck workaround: OTP modules

-module(statebox_clock).
-export([timestamp/0, now_to_msec/1]).

%% @doc …
-spec timestamp() -> integer().
timestamp() ->
  now_to_msec(os:timestamp()).

meck caveat: side effects

meck workaround: side effects

now_test() ->
  meck:new(statebox_clock),
  meck:sequence(statebox_clock, clock, 0,
    [1, 2, 3, 4, 5]),
  ?assertEqual(1, statebox:clock()),
  ?assertEqual(2, statebox:clock()),
  ok.

meck alternatives

PropEr

PropEr EUnit Skeleton

%% Before eunit.hrl include
-include_lib("proper/include/proper.hrl").

%% EUnit tests
proper_module_test() ->
  ?assertEqual(
    [],
    proper:module(?MODULE, [long_result])).

PropEr Specs Example

-spec int_ceil(float()) -> integer().
int_ceil(X) ->
  T = trunc(X),
  case (X - T) of
    Pos when Pos > 0 -> T + 1;
    _ -> T
  end.

int_ceil_spec_test() ->
  proper:check_spec({?MODULE, int_ceil, 1})

PropEr Property Example

-spec digits(float()) -> string().
digits(F) -> […].

%% In the EUnit test block
prop_digits_exact() ->
  ?FORALL(F, float(),
    begin F =:= list_to_float(digits(F)) end).

PropEr Generator Example

unichar() ->
  union([integer(0, 16#d7ff),
         integer(16#e000, 16#10ffff)]).

utf8_binary() ->
  ?LET(L, list(unichar()),
    unicode:characters_to_binary(L, utf8)).

prop_valid_utf8_bytes_valid() ->
  ?FORALL(B, utf8_binary(),
    begin B =:= valid_utf8_bytes(B) end).

PropEr Caveats

PropEr Alternatives

dialyzer

dialyzer plt building

$ dialyzer --build_plt \
  --output_plt .dialyzer-R14B01.plt \
  --apps kernel stdlib sasl erts ssl \
    tools os_mon runtime_tools crypto \
    inets xmerl webtool snmp public_key \
    mnesia eunit syntax_tools compiler \
    ./deps/*/ebin

dialyzer analysis

$ dialyzer ./ebin --plt .dialyzer-R14B01.plt \
  -Wunmatched_returns \
  -Werror_handling \
  -Wrace_conditions \
  -Wbehaviours \
  -Wunderspecs

dialyzer analysis notes

dialyzer caveats

Jenkins

Jenkins - Setup

Jenkins - Install Plugins

Jenkins - New Job

Jenkins - SCM

Jenkins - Build

Jenkins - Post-build Actions

Jenkins - Watch a build

Jenkins alternatives

Wrap-up

Questions?