Discussion:
Anyone want Test::Class::Moose?
Ovid
2012-12-12 17:51:27 UTC
Permalink
Hi all,

People keep asking me how to properly integrate Moose with Test::Class. I know about Test::Able and some alternatives, but I *generally* like Test::Class's interface (or maybe I'm just in a comfort zone). So I wrote my own Test::Class::Moose (it does not use Test::Class) and it uses subtests all the way down.

Every test class is one test. Every test method is a subtest in the test class. Every test in a test method is, well, one test in a test method. Here's a simple test class which, in turn, inherits from another test class (you get all of the functions from Test::Most along with Moose helper functions, if desired):

    package TestsFor::Basic::Subclass;                                                                                                                  
    use Test::Class::Moose parent => 'TestsFor::Basic';

    sub test_startup {
        my $test = shift;
        $test->next::method;
        # more startup here, but tests are not allowed
    }

    sub test_me {
        my $test  = shift;
        my $class = $test->this_class;
        ok 1, "I overrode my parent! ($class)";
    }

    before 'test_this_baby' => sub {
        my $test  = shift;
        my $class = $test->this_class;
        pass "This should run before my parent method ($class)";
    };

    sub this_should_not_run {
        fail "We should never see this test";
    }

    sub test_this_should_be_run {
        for ( 1 .. 5 ) {
            pass "This is test number $_ in this method";
        }
    }

    1;

Note that attributes are not required on the test methods. Methods which start with "test_" are considered test methods (you can override this behavior, of course). That includes the test control methods of test_startup(), test_setup(), and so on.

You should be able to consume roles or use the rest of Moose any way you think you should.

Here's how we load and run tests:

    use Test::Class::Moose::Load qw(t/lib);

    Test::Class::Moose->new({
        # timing on a class and method level
        show_timing => 0,
        # how many classes, methods and tests
        statistics  => 1,
    })->runtests; 

My main concern is that the nested subtests will annoy people:

    # 
    # Executing tests for TestsFor::Basic::Subclass
    #
        # TestsFor::Basic::Subclass->test_me()
            ok 1 - I overrode my parent! (TestsFor::Basic::Subclass)
            1..1
        ok 1 - test_me
        # TestsFor::Basic::Subclass->test_this_baby()
            ok 1 - This should run before my parent method (TestsFor::Basic::Subclass)
            ok 2 - whee! (TestsFor::Basic::Subclass)
            1..2
        ok 2 - test_this_baby
        # TestsFor::Basic::Subclass->test_this_should_be_run()
            ok 1 - This is test number 1 in this method
            ok 2 - This is test number 2 in this method
            ok 3 - This is test number 3 in this method
            ok 4 - This is test number 4 in this method
            ok 5 - This is test number 5 in this method
            1..5
        ok 3 - test_this_should_be_run
        1..3
    ok 1 - TestsFor::Basic::Subclass
    # 
    # Executing tests for TestsFor::Basic
    # 
        # TestsFor::Basic->test_me()
            ok 1 - test_me() ran (TestsFor::Basic)
            ok 2 - this is another test (TestsFor::Basic)
            1..2
        ok 1 - test_me
        # TestsFor::Basic->test_this_baby()
            ok 1 - whee! (TestsFor::Basic)
            1..1
        ok 2 - test_this_baby
        1..2
    ok 2 - TestsFor::Basic
    1..2
    # Test classes:    2
    # Test methods:    5
    # Total tests run: 11
    ok
    All tests successful.
    Files=1, Tests=2,  2 wallclock secs
    Result: PASS

So, does this look useful for folks? Is there anything you would change? (It's trivial to assert plans for classes and the entire test suite rather than rely on done_testing(), but I haven't done that yet).

Cheers,
Ovid
--
Twitter - http://twitter.com/OvidPerl/
Buy my book - http://bit.ly/beginning_perl
Buy my other book - http://www.oreilly.com/catalog/perlhks/
Live and work overseas - http://www.overseas-exile.com/
chromatic
2012-12-12 17:58:01 UTC
Permalink
Post by Ovid
So, does this look useful for folks?
Yes, please. I would use it last month if I could.
Post by Ovid
Is there anything you would change?
I don't *love* maintaining individual driver files (t/subsystem/feature.t),
but I do like being able to run test classes individually with:

$ prove -l t/subsystem/feature.t

Is that easy to support?

-- c
Ovid
2012-12-12 18:07:35 UTC
Permalink
________________________________
Post by Ovid
Is there anything you would change?
I don't *love* maintaining individual driver files (t/subsystem/feature.t),
    $ prove -l t/subsystem/feature.t
Is that easy to support?
Shouldn't be too hard. Maybe have a DEMOLISH which checks to see if the tests have been run and run them with a default (empty) constructor if they haven't.

Cheers,
Ovid
--
Twitter - http://twitter.com/OvidPerl/
Buy my book - http://bit.ly/beginning_perl
Buy my other book - http://www.oreilly.com/catalog/perlhks/
Live and work overseas - http://www.overseas-exile.com/
Mark Stosberg
2012-12-12 18:15:10 UTC
Permalink
Post by Ovid
So, does this look useful for folks? Is there anything you would change? (It's trivial to assert plans for classes and the entire test suite rather than rely on done_testing(), but I haven't done that yet).
I would welcome it as an option.

We use Test::Class now, but I have the sense that there's a better
alternative. We have started to load Moose in most cases, so there's no
additional load time penalty for Moose since it's already there.

Some things I like from some alternatives (from reviewing them, not
using them):

I like the declarative style and simple nesting of Test::Spec:

https://metacpan.org/module/Test::Spec

describe "A User object" => sub {
my $user;
before sub {
$user = User->new;
};
describe "from a web form" => sub {
before sub {
$user->init_from_tree({ username => "bbill", ... });
};
it "should read its attributes from the form";
describe "when saving" => sub {
it "should require a unique username";
it "should require a password";
};
};
};

Test::Ika has a similar spirit, but with an option for very readable output:

describe 'MessageFilter' => sub {
my $filter;

before_each {
$filter = MessageFilter->new();
};

it 'should detect message with NG word' => sub {
my $filter = MessageFilter->new('foo');
expect($filter->detect('hello foo'))->ok;
};
it 'should detect message with NG word' => sub {
my $filter = MessageFilter->new('foo');
expect($filter->detect('hello foo'))->ok;
};
};

Check out the TAP-alternative output here:

https://metacpan.org/module/Test::Ika

This is a good idea that I expect to spread: Generating TAP for test
harnesses, but more a readable format when the target is a human reader.

However, I think if I just wanted to mash-up Test::Class and Moose, I
would do something more like this:

package Test::Class::Moose;
use Moose;
use MooseX::NonMoose;
extends 'Test::Class';

####

That way I would have to update our hundreds of test scripts to use a
new syntax. :) If your project is to be considered a path forward from
Test::Class but with an incompatible syntax, it would be great if it
came with a script to find/replace the old syntax with the replacement.


Mark
Jonathan Swartz
2012-12-12 18:18:49 UTC
Permalink
+1 from me! I like Test::Class and would welcome a Moose-ish variety.
Post by Ovid
Hi all,
People keep asking me how to properly integrate Moose with Test::Class. I know about Test::Able and some alternatives, but I *generally* like Test::Class's interface (or maybe I'm just in a comfort zone). So I wrote my own Test::Class::Moose (it does not use Test::Class) and it uses subtests all the way down.
package TestsFor::Basic::Subclass;
use Test::Class::Moose parent => 'TestsFor::Basic';
sub test_startup {
my $test = shift;
$test->next::method;
# more startup here, but tests are not allowed
}
sub test_me {
my $test = shift;
my $class = $test->this_class;
ok 1, "I overrode my parent! ($class)";
}
before 'test_this_baby' => sub {
my $test = shift;
my $class = $test->this_class;
pass "This should run before my parent method ($class)";
};
sub this_should_not_run {
fail "We should never see this test";
}
sub test_this_should_be_run {
for ( 1 .. 5 ) {
pass "This is test number $_ in this method";
}
}
1;
Note that attributes are not required on the test methods. Methods which start with "test_" are considered test methods (you can override this behavior, of course). That includes the test control methods of test_startup(), test_setup(), and so on.
You should be able to consume roles or use the rest of Moose any way you think you should.
use Test::Class::Moose::Load qw(t/lib);
Test::Class::Moose->new({
# timing on a class and method level
show_timing => 0,
# how many classes, methods and tests
statistics => 1,
})->runtests;
#
# Executing tests for TestsFor::Basic::Subclass
#
# TestsFor::Basic::Subclass->test_me()
ok 1 - I overrode my parent! (TestsFor::Basic::Subclass)
1..1
ok 1 - test_me
# TestsFor::Basic::Subclass->test_this_baby()
ok 1 - This should run before my parent method (TestsFor::Basic::Subclass)
ok 2 - whee! (TestsFor::Basic::Subclass)
1..2
ok 2 - test_this_baby
# TestsFor::Basic::Subclass->test_this_should_be_run()
ok 1 - This is test number 1 in this method
ok 2 - This is test number 2 in this method
ok 3 - This is test number 3 in this method
ok 4 - This is test number 4 in this method
ok 5 - This is test number 5 in this method
1..5
ok 3 - test_this_should_be_run
1..3
ok 1 - TestsFor::Basic::Subclass
#
# Executing tests for TestsFor::Basic
#
# TestsFor::Basic->test_me()
ok 1 - test_me() ran (TestsFor::Basic)
ok 2 - this is another test (TestsFor::Basic)
1..2
ok 1 - test_me
# TestsFor::Basic->test_this_baby()
ok 1 - whee! (TestsFor::Basic)
1..1
ok 2 - test_this_baby
1..2
ok 2 - TestsFor::Basic
1..2
# Test classes: 2
# Test methods: 5
# Total tests run: 11
ok
All tests successful.
Files=1, Tests=2, 2 wallclock secs
Result: PASS
So, does this look useful for folks? Is there anything you would change? (It's trivial to assert plans for classes and the entire test suite rather than rely on done_testing(), but I haven't done that yet).
Cheers,
Ovid
--
Twitter - http://twitter.com/OvidPerl/
Buy my book - http://bit.ly/beginning_perl
Buy my other book - http://www.oreilly.com/catalog/perlhks/
Live and work overseas - http://www.overseas-exile.com/
Ovid
2012-12-13 07:05:47 UTC
Permalink
It's seriously alpha and has a nasty bug whereby an exception will halt the entire test suite.

I can fix that later, but I wanted to get something out for you folks.

https://github.com/Ovid/test-class-moose

 
Note that I elected to not use attributes because they're hard for *you* to customize, but it's trivial for you to override the get_test_classes() and get_test_methods() behavior for your own testing purposes.

More work needs to be done, but I think you'll be pleased at how short the code is to do this.

Cheers,
Ovid
--
Twitter - http://twitter.com/OvidPerl/
Buy my book - http://bit.ly/beginning_perl
Buy my other book - http://www.oreilly.com/catalog/perlhks/
Live and work overseas - http://www.overseas-exile.com/
________________________________
Sent: Wednesday, 12 December 2012, 19:18
Subject: Re: Anyone want Test::Class::Moose?
+1 from me! I like Test::Class and would welcome a Moose-ish variety.
Post by Ovid
Hi all,
People keep asking me how to properly integrate Moose with Test::Class. I know about Test::Able and some alternatives, but I *generally* like Test::Class's interface (or maybe I'm just in a comfort zone). So I wrote my own Test::Class::Moose (it does not use Test::Class) and it uses subtests all the way down.
    package TestsFor::Basic::Subclass;                                                                                                                 
    use Test::Class::Moose parent => 'TestsFor::Basic';
    sub test_startup {
        my $test = shift;
        $test->next::method;
        # more startup here, but tests are not allowed
    }
    sub test_me {
        my $test  = shift;
        my $class = $test->this_class;
        ok 1, "I overrode my parent! ($class)";
    }
    before 'test_this_baby' => sub {
        my $test  = shift;
        my $class = $test->this_class;
        pass "This should run before my parent method ($class)";
    };
    sub this_should_not_run {
        fail "We should never see this test";
    }
    sub test_this_should_be_run {
        for ( 1 .. 5 ) {
            pass "This is test number $_ in this method";
        }
    }
    1;
Note that attributes are not required on the test methods. Methods which start with "test_" are considered test methods (you can override this behavior, of course). That includes the test control methods of test_startup(), test_setup(), and so on.
You should be able to consume roles or use the rest of Moose any way you think you should.
    use Test::Class::Moose::Load qw(t/lib);
    Test::Class::Moose->new({
        # timing on a class and method level
        show_timing => 0,
        # how many classes, methods and tests
        statistics  => 1,
    })->runtests;
    #
    # Executing tests for TestsFor::Basic::Subclass
    #
        # TestsFor::Basic::Subclass->test_me()
            ok 1 - I overrode my parent! (TestsFor::Basic::Subclass)
            1..1
        ok 1 - test_me
        # TestsFor::Basic::Subclass->test_this_baby()
            ok 1 - This should run before my parent method (TestsFor::Basic::Subclass)
            ok 2 - whee! (TestsFor::Basic::Subclass)
            1..2
        ok 2 - test_this_baby
        # TestsFor::Basic::Subclass->test_this_should_be_run()
            ok 1 - This is test number 1 in this method
            ok 2 - This is test number 2 in this method
            ok 3 - This is test number 3 in this method
            ok 4 - This is test number 4 in this method
            ok 5 - This is test number 5 in this method
            1..5
        ok 3 - test_this_should_be_run
        1..3
    ok 1 - TestsFor::Basic::Subclass
    #
    # Executing tests for TestsFor::Basic
    #
        # TestsFor::Basic->test_me()
            ok 1 - test_me() ran (TestsFor::Basic)
            ok 2 - this is another test (TestsFor::Basic)
            1..2
        ok 1 - test_me
        # TestsFor::Basic->test_this_baby()
            ok 1 - whee! (TestsFor::Basic)
            1..1
        ok 2 - test_this_baby
        1..2
    ok 2 - TestsFor::Basic
    1..2
    # Test classes:    2
    # Test methods:    5
    # Total tests run: 11
    ok
    All tests successful.
    Files=1, Tests=2,  2 wallclock secs
    Result: PASS
So, does this look useful for folks? Is there anything you would change? (It's trivial to assert plans for classes and the entire test suite rather than rely on done_testing(), but I haven't done that yet).
Cheers,
Ovid
--
Twitter - http://twitter.com/OvidPerl/
Buy my book - http://bit.ly/beginning_perl
Buy my other book - http://www.oreilly.com/catalog/perlhks/
Live and work overseas - http://www.overseas-exile.com/
Justin DeVuyst
2012-12-14 18:54:25 UTC
Permalink
All,

What's wrong with Test::Able?

-jdv
Ovid
2012-12-15 22:37:04 UTC
Permalink
Hi Justin,

This is something I've wondered as well. Several times when people asked me about using Test::Class with Moose I pointed them to Test::Able, though I confess I never used that code. Invariably they would not choose it. I think what's going on is the interface. It looks "different" and that's possibly scaring people off. The interface for Test::Class::Moose looks very similar to Test::Class and that might make for easier adoption if I ever do enough to get it out of alpha.

Please note that I'm not saying that what I've written is better! However, the comfort level of the Test::Class::Moose interface may be appealing to some.
 
Cheers,
Ovid
--
Twitter - http://twitter.com/OvidPerl/
Buy my book - http://bit.ly/beginning_perl
Buy my other book - http://www.oreilly.com/catalog/perlhks/
Live and work overseas - http://www.overseas-exile.com/
________________________________
Sent: Friday, 14 December 2012, 19:54
Subject: Re: Anyone want Test::Class::Moose?
All,
What's wrong with Test::Able?
-jdv
Mark Stosberg
2012-12-17 14:14:57 UTC
Permalink
Post by Ovid
Hi Justin,
This is something I've wondered as well. Several times when people asked me about using Test::Class with Moose I pointed them to Test::Able, though I confess I never used that code. Invariably they would not choose it. I think what's going on is the interface. It looks "different" and that's possibly scaring people off. The interface for Test::Class::Moose looks very similar to Test::Class and that might make for easier adoption if I ever do enough to get it out of alpha.
Please note that I'm not saying that what I've written is better! However, the comfort level of the Test::Class::Moose interface may be appealing to some.
There's also Test::Sweet-- another Moose/Test::Class mashup which I
haven't developed an opinion of: https://metacpan.org/module/Test::Sweet

I can see that one difference is that it uses Devel::Declare.

As I looked more at Test::Class::Moose, one thing I really like is that
plans are completely gone. Thank you.

Two questions:

1. About this: "use Test::Class::Moose;"

Why not standard inheritance to add Test::Class functionality?

It looke the rationale here is to save a line of boilerplate with the
"use Moose" line.

2. About this syntax for extending a test class:
use Test::Class::Moose parent => 'TestsFor::Some::Class';

why not use standard inheritance in a class, and to extend a class using
test::Class? Or could you 'extends' in the import list here to look more
Moose-y?

Mark
Ovid
2012-12-17 14:49:15 UTC
Permalink
Hi Mark,
________________________________
As I looked more at Test::Class::Moose, one thing I really like is that
plans are completely gone. Thank you.
You're welcome. They're inferred at the suite and class level, but with an implicit "done_testing()" for each method. It's not perfect, but there alternatives seemed a touch worse.
1. About this: "use Test::Class::Moose;"
Why not standard inheritance to add Test::Class functionality?
It looke the rationale here is to save a line of boilerplate with the
"use Moose" line.
Test::Class::Moose is explicitly coupled with Moose, so having a "use Moose" line is both redundant and error-prone. If it's required and you forget it, oops. I've given you a source of bugs you didn't need. If it's not required, why write it?
  use Test::Class::Moose parent => 'TestsFor::Some::Class';
why not use standard inheritance in a class, and to extend a class using
test::Class? Or could you 'extends' in the import list here to look more
Moose-y?
I think "extends" might be better. Good call.

I don't use standard inheritance because the various solutions for that don't allow for both inheriting from a class and exporting functions (in this case, ok(), is(), eq_or_diff(), and so on).

Cheers,
Ovid
--
Twitter - http://twitter.com/OvidPerl/
Buy my book - http://bit.ly/beginning_perl
Buy my other book - http://www.oreilly.com/catalog/perlhks/
Live and work overseas - http://www.overseas-exile.com/

Loading...