Extensive POE Testing PT. 1 - The Rules

xsawyerx on 2009-10-07T10:46:02

If you only use POE::Session for your POE code and find no need for more elaborate tests, feel free to skip these posts. I won't be offended, I promise. :)

After a lot of frustration of trying to set up proper tests for POE (using mainly MooseX::POE while at it), I've decided to write a proper testing framework. However, instead I wrote something simple, yet relatively extensive that helps write fingergrained tests for POE using Moose, specifically aimed towards MooseX::POE.

I've decided to write it in Moose (using Moose::Role) because it makes it much easier for me to do it than other frameworks/modules. I don't intend (at least at this time) to write it in bare bones Perl and I personally don't care about complete optimizations for my testing code, I don't think a lot of people would mind either.

This article will only discuss the general need, the idea and the general rules I've layed out for myself to help me test POE code. It might help you too. The following articles in this series (and I assure you, there will be!) discuss the types of tests that I feel are required and examples of code that does it. I'll also present my new (unnamed) testing framework that's already being used at $work.

Preliminary:
In POE, you have a layer of "events" which are actually just mappings of names of events that can be run to the actual subroutines that they will trigger when run. This seems pretty simple to test. You can check each subroutine separately and that's just fine. However, there are some things it doesn't cover:

  • MooseX::POE: if you're using MooseX::POE your events are mapped for you to anonymous subroutines. Instead of writing a named subroutine and then mapping it through inline_states (or whatever), you simply do event 'something' => sub {}; Problem is, you cannot test it that easily.
  • Sometimes you want to run stuff and see how it works: It is important to test every small component separately, but it's always nice (especially when it comes to complete environments) to check a bigger chunk. It's also easier to comprehend. Sure, I know that check_stuff() was run, but when the program was started, did it get run 3 or 4 times?
  • Some things are harder [and/or take longer] to test. For example, suppose a subroutine can be called several times (loop, events, alarm) with varying inputs. Perhaps there's an alarm that checks each time and only then runs another event. To test every subroutine's possible iteration is possible, but not as much fun.

Theory of tests in POE:
I found that the cleanest and easiest way to test is always accompanied by these following guidelines:

  • Always test a single POE Session at a time. Sometimes the sessions interact. You can mock sessions in order to work through that. Testing POE is difficult enough as it is without testing 10, 20 or 300 Sessions at a time. So do yourself a favor. Each time work on only one session. I separate my tests to folders according to the sessions I have. I have a folder for a Master Session, a Slave Session, a Worker session and so on.
  • Override the session you intend to test. This will allow you to change the session environment, its subroutines, events, alarms, and so on. It will keep the Kernel session-specific and make everything self-contained and a hell of a lot easier.
  • Preferably test events separately. This isn't always desired or necessary but I find it much more comfortable. I keep every event as a separate test file. The reason for this is the same as why I work on only one session at a time. I helps me test it without going insane with the multitude.
  • When testing an event, override and mock everything in its surrounding in order to test it. This is something the True Test Lords have known for a long while now. Creating a controlled environment is the best way to check something. Same goes here. You should mock and override every subroutine, attribute, event or anything else that comes in contact with the event you're testing. Think... Ebola!
  • Separate event tests with subroutine tests. All subroutines should be tested regardless of the POE Running test, in the regular way you test subroutines (or methods). Events will be tested separately in a different manner that requires running POE and seeing what happens.

Next post starts with types of tests!


A slight clarification ...

perigrin on 2009-10-08T04:54:26

MooseX::POE's events are eventually named. They become first class methods in the class as well as events in the ObjectState Session that MooseX::POE creates for the instance. For example the following will "just work" (and print out a warning about POE::Kernel not being run).

package Foo;
use 5.10.0;
use MooseX::POE;
event bar => sub { say 'BAR!' };

package main;
Foo->new->bar(); # prints "BAR!\n"

Also any tests that you happen to want to push back into MooseX::POE will be *greatly* appreciated. The code is available on github.

Re:A slight clarification ...

xsawyerx on 2009-10-08T09:23:48

Thanks for the clarification. I reckon that going through MX::POE code, I could also probably make my framework even better. Right now each event requires adding a line in order for the framework to track it. If I could get a list of the events, I could put in the added line myself perhaps.

I'm also checking into Sub::Caller or stack modules to try and remove the need for parameters to the added line.

Once I decide how to name my framework, I'll put it on Github and CPAN and people will (hopefully) be able to [help me] improve it much more.

Re:A slight clarification ...

perigrin on 2009-10-08T16:38:11

The event are stored in the metaclass.

my @events = $self->meta->get_events;

I've been going through the code myself recent, cleaning up an edge case bug that someone discovered, and modernizing the codebase a bit (the next version will depend on Moose 0.90+ but drops the MooseX::AttributeHelpers dependency).

Re:A slight clarification ...

xsawyerx on 2009-10-08T17:03:45

That's awesome!

Do you know if there's any way to get the arguments a method you've hooking to is getting? For example:

package A;
use Moose;
sub hello {
    my ( $self, $msg ) = @_;
    say $msg;
}

package B;
use Moose;
extends 'A';
before 'hello' => sub {
    # i want to get the $msg parameter hello() got
};

Re:A slight clarification ...

perigrin on 2009-10-09T05:57:47

Is this what you are asking for?

#!/usr/bin/env perl
use strict;
use Test::More tests => 6;

{

    package Base;
    use MooseX::POE;

    sub START {
        ::pass('Base Start');
    }

    event hello => sub {
        ::pass('hello');
        $_[KERNEL]->yield('goodbye');
    };
}
{

    package Extended;
    use MooseX::POE;

    extends 'Base';

    sub START {
        ::pass('Extended after Start');
        $_[KERNEL]->yield( 'hello' => 'world' );
    }

    before 'hello' => sub {
        ::is( $_[ARG0], 'world', 'before saw world' );
    };

    after 'hello' => sub {
        ::is( $_[ARG0], 'world', 'after saw world' );
    };

    event goodbye => sub {
        ::pass('goodbye');
    };

}

my $foo = Extended->new();
POE::Kernel->run();

I've just added this to the test suite.

Re:A slight clarification ...

xsawyerx on 2009-10-09T15:32:31

Actually it is!

This would really help me with the framework. I could automatically hook up to all events. Putting it in will make my code able to work automatically by adding only a single line of "with".

Thanks!

Re:A slight clarification ...

perigrin on 2009-10-11T18:01:02

Sorry, I wasn't clear. That code works *now*. It should work in the CPAN version. Go forth and test away!