tags:

views:

121

answers:

2

I've got an application where I cast a message to a gen_server to start an operation, then I call the gen_server every second to gather intermediate results until the operation completes. In production, it usually takes a couple of minutes, but it's only limited by the input size and I'd like to test hour long operations, too.

I want to always make sure this operation still works by running a test as needed. Ideally, I'd like to run this test multiple times with different inputs, also.

I use eunit right now, but it doesn't seem to have a purpose-built way of exercising this scenario. Does commmon test provide for this? Is there an elegant way of testing this or should I just hack something up? In general, I'm having trouble wrapping my head around how to systematically test stateful, asynchronous operations in Erlang.

A: 

Testing Stateful Asynchronous operations is hard in any language, Erlang or otherwise.

I would actuall recommend using etap and have the asynchronous tests run a callback that will then run etap:end_tests()

Since etap uses a running test server and it waits for the end_test call you have a little more control for asynchronous tests.

Jeremy Wall
Wicked! Thanks for building this. I'll give it a go.
John Galt
I started it but Nick Gerakines has taken it a long way from it's initial beginnings. He deserves a fair amount of credit as well.
Jeremy Wall
+1  A: 

Yes, common test will do this.

This is a cut down version of the common test suite skeleton that our erlang emacs mode provides (you can use the normal erlang one or erlware one):

-module(junk).

%% Note: This directive should only be used in test suites.
-compile(export_all).

-include("test_server.hrl").

%%
%% set up for the suite...
%%
init_per_suite(Config) ->
    Config.

end_per_suite(_Config) ->
    ok.

%%
%% setup for each case in the suite - can know which test case it is in
init_per_testcase(_TestCase, Config) ->
    Config.

end_per_testcase(_TestCase, _Config) ->
    ok.

%%
%% allows the suite to be programmatically managed
%%
all(doc) ->
    ["Describe the main purpose of this suite"];

all(suite) ->
    [].

%% Test cases starts here.
%%--------------------------------------------------------------------
test_case(doc) ->
    ["Describe the main purpose of test case"];

test_case(suite) ->
    [];

test_case(Config) when is_list(Config) ->
    ok.

There are 2 basic ways you could do it.

First up start the gen_server in init_per_suite/1 and then have a large number of atomic tests that act on that long running server and then tear the gen_server down in end_per_suite/1. This is the preferred way - your gen_server should be long-running and persistent over many transactions, blah-blah...

The other way is to make a singleton test and start the gen_server with init_per_testcase/2 and tear it down in end_per_testcase/2

Gordon Guthrie
I guess the biggest issue I have is that I need an arbitrary number of test cases. It might take 5, 10, 23 or any number of calls to the gen_server to retrieve all results. Is that possible?
John Galt
I guess instead of treating each request for results from the client to the gen_server as a separate test case, I can just recursively call the same test case function. If this is allowed, it should be perfect.
John Galt
Actually, an even better solution is to use the repeat-until-successful strategy, and repeat the test (with a 1 second delay) until it gets the final results. Combined with a timetrap, this should be ideal.
John Galt
I feel my participation in this thread is somewhat superfluous :) Keep up the good work!What I would recommend you do is put a loop in the test that repeats until successful, but maybe do a 1 minute, 10 minute and 1 hour test in the suite..
Gordon Guthrie