[erlang-questions] I Hate Unit Testing...

Steve Davis steven.charles.davis@REDACTED
Wed Jan 28 00:37:31 CET 2009


Hi all,

I really hate writing code to test my code. I have noticed that most 
people must also hate writing test code since the unit test code 
included in almost every project I've ever seen is... well, scanty, at 
best.

But it came to me that *there's a way out of this* thanks to Erlang, since:
- all functions in Erlang are deterministic, i.e. for a certain input 
you are guaranteed one and only one output, and so a simple MFA apply 
should always give a predictable result.
- reading in terms to feed to apply/3 is trivial.

So I got to hacking something together today to see how/if this idea 
would work. I'm posting the results of this experiment here to see if 
anyone thinks the idea is interesting and has legs. Hopefully I may get 
a few pointers on improving my naive and diabolical code as I'm 
definitely a "learner".

Familiarity is utmost so I just borrowed/subverted the term structure 
used for .app files, like this...
----- my_app.test ------
%% utest test specification
{utest, my_app,
  [{description, ""},
   {version, "0.1"},
   {flags, [verbose]}, %% only [verbose] or [] supported
   {applications, [kernel, stdlib, crypto]}, %% dependencies
   {tests, [
	% Tests are specified as Module, Function, ArgumentList, ExpectedResult
	% i.e. {M, F, A, R}
    {my_module, hex, [2], "2"},
    {my_module, hex, [255], "ff"},
    {my_module, hex, [55551], "d8ff"}
   ]}
]}.

...after a few hours hacking around I had a simple demo module that 
produces output like this...
---- Sample Console Session ----
3> utest:run("../test/my_module.test").
UNIT TEST
  Application: my_app
  Version: "0.1"
  Description: ""
  Unit Tests: 3
  Depends on: [kernel,stdlib,crypto]
Starting crypto...ok
Running tests...
  [FAIL] my_module:hex(2) -> 50 EXPECTED "2"
  [OK]   my_module:hex(255) -> "ff"
  [OK]   my_module:hex(55551) -> "d8ff"
***FAILURE: There were 1 test failures
{fail,{tests,3},{errors,1}}
4>

...now I'll grit my teeth and post the code in case anyone wishes to try 
it out (or help me by giving me style pointers - which I know I need!)
---- utest.erl ----
%% Simple Unit Testing without writing code!
-module(utest).
-vsn("0.1").
-author('steven.charles.davis@REDACTED').

-record(utest, {package="unknown", version="0.0", description="", 
flags=[], depends=[], tests=[]}).

-export([run/1]).

%% File is a ".test" spec file
run(File) ->
	{ok, [Utest]} = file:consult(File),
	{utest, Package, Options} = Utest,
	R = #utest{package=Package},
	R1 = get_options(Options, R),
	io:format("UNIT TEST~n Application: ~p~n", [R#utest.package]),
	N = length(R1#utest.tests),
	io:format(" Version: ~p~n Description: ~p~n Depends on: ~p~n Unit 
Tests: ~p~n",
		[R1#utest.version, R1#utest.description, R1#utest.depends, N]),
	ensure(R1#utest.depends),
	case R1#utest.flags of
	[verbose] -> V = true;
	_ -> V = false
	end,
	io:format("Running tests...~n", []),			
	{X, E} = do_tests(R1#utest.tests, 0, V),
	{X, {tests, N}, {errors, E}}.

%%
get_options([H|T], R) ->
	case H of
	{version, Value} -> R1 = R#utest{version=Value};
	{description, Value} -> R1 = R#utest{description=Value};
	{flags, Value} -> R1 = R#utest{flags=Value};
	{applications, Value} -> R1 = R#utest{depends=Value};
	{tests, Value} -> R1 = R#utest{tests=Value};
		
	_ -> R1 = R
	end,
	get_options(T, R1);
get_options([], R) ->
	R.

%%
ensure(Apps) ->
     ensure(Apps, application:which_applications()).
%%
ensure([App|Rest], Running) ->
	case lists:keysearch(App, 1, Running) of
		{value, {App, _, _}} ->
			ok;
		false ->
			io:format("Starting ~p...", [App]),
			ok = application:start(App),
			io:format("ok~n", [])			
	end,
	ensure(Rest, Running);
ensure([], _) ->
	application:which_applications().
	
%%
do_tests([H|T], E, V) ->
	{M, F, A, R} = H,
	R1 = apply(M, F, A),
	S = lists:flatten(io_lib:format("~w", [A])),
	S1 = string:sub_string(S, 2, length(S) - 1),
	case R1 of
	R ->
		E1 = E,
		case V of
		true -> io:format(" [OK]   ~p:~p(" ++ S1 ++ ") -> ~p~n", [M, F, R1]);
		false -> ok
		end;
	_ ->
		E1 = E + 1,
		io:format(" [FAIL] ~p:~p(" ++ S1 ++ ") -> ~p EXPECTED ~p~n", [M, F, 
R1, R])
	end,
	do_tests(T, E1, V);
do_tests([], E, V) ->
	case V of
	true when E =:= 0 -> io:format("SUCCESS! All tests completed 
successfully~n", []), {ok, E};
	true when E > 0 -> io:format("***FAILURE: There were ~p test 
failures~n", [E]), {fail, E};
	false when E =:= 0 -> {ok, E};
	false when E > 0 -> {fail, E};
	_ -> {error, E}
	end.

Well, that's the basic idea -- there's a heap of improvements that could 
be made of course, but thanks for reading this far, I look forward to 
any comments you may have!

BR,
/scd



More information about the erlang-questions mailing list