mascip has asked for the wisdom of the Perl Monks concerning the following question:

Hi all,
I'm a test-beginner, what i say might be stupid/naive.

Test::Class is what people use for OO testing, and it seems very good at it (i barely used it, but read Ovid's instructions carefully).

Test::Spec is used for testing scripts, and i love its sugar functions.

My question is : would there be any way to use Test::Spec with Test::Class?
(I'm almost sure that the answer is "no", but wish someone could show me the contrary.)
Or any Test::* framework which would go in that direction ?

Here is a small example of what Test::Spec does (PS : i just realized that the example in the POD documentation is simpler and clearer - just click the link). I very much like it's simple, descriptive, and nested structure. Which i prefer to Test::Class's $test variable, which caries all the variables that i will use in different tests, and doesn't read well.

#!/usr/bin/perl use 5.012003; # Perl version requirement + use strict; use warnings; use Game::Bowling; use Test::Spec 0.45; describe "A Game::Bowling" => sub { my $game; my @rolls; my $expected_score; before each => sub { $game = new Game::Bowling; @rolls = (2)x20; $expected_score = 40; }; describe 'counts that the score is' => sub { it '0, if 0 pins are shot' => sub { @rolls = (0)x20; is $game->played_with_rolls(\@rolls)->score(), 0; }; it '40, if 2 pins are shot at each roll' => sub { is $game->played_with_rolls(\@rolls)->score(), $expected_s +core; }; it '41, if 1 more pin is shot at one roll' => sub { $rolls[5] = 3; $expected_score += (-2+3); is $game->played_with_rolls(\@rolls)->score(), $expected_s +core; }; }; describe 'takes into account a spare' => sub { it 'in the 2nd frame' => sub { @rolls[3,4] = (8,5); $expected_score += (-2+8+5) + (-2+5); is $game->played_with_rolls(\@rolls)->score(), $expected_s +core; }; it 'in the last frame' => sub { $rolls[19] = 8; push @rolls, 3; $expected_score += (-2+8+3) + 3; is $game->played_with_rolls(\@rolls)->score(), $expected_s +core; }; }; describe 'takes into account a strike' => sub { it 'in the 2nd frame' => sub { @rolls[2,3,4] = (10,7,1); $expected_score += (-2-2+10+7+1) + (-2+7) + (-2+1); pop @rolls; is $game->played_with_rolls(\@rolls)->score(), $expected_s +core; }; it 'in the last frame' => sub { @rolls[18,19] = (10,7); push @rolls, 2; $expected_score += (-2-2+10+7+2) + (+7) + (+2); is $game->played_with_rolls(\@rolls)->score(), $expected_s +core; }; }; describe 'with zero players' => sub { it 'is not interesting' => sub { ok(1); }; }; }; runtests unless caller;
produces this output :
ok 1 - A Game::Bowling counts that the score is 0, if 0 pins are shot ok 2 - A Game::Bowling counts that the score is 40, if 2 pins are shot + at each roll ok 3 - A Game::Bowling counts that the score is 41, if 1 more pin is s +hot at one roll ok 4 - A Game::Bowling takes into account a spare in the 2nd frame ok 5 - A Game::Bowling takes into account a spare in the last frame ok 6 - A Game::Bowling takes into account a strike in the 2nd frame ok 7 - A Game::Bowling takes into account a strike in the last frame ok 8 - A Game::Bowling with zero players is not interesting 1..8

I've tried to do the same with Test::Class and it's a lot less friendly to read. Yes, i know, you wouldn't use Test::Class for such a simple example. But still, when i will need it on bigger examples, i'd like it to be reader-friendly.

~ ~ ~
A few hours later, i ate, relaxed, and searched a bit more on the Tinterweb. (i tend to do this, late, and thus answer my own questions myself)

Test::More allows nested subtests, with the "subtest" keyword. But it's not "sweet" enough : i still prefer Test::Spec sugar. But it might make my variables declaration nicer, i have to try on monday.
I really don't like the $test hash in Test::Class : @{$test->{rolls}} is a lot less nice to read than @rolls. 1nd i'm lazy, to have to copy the variables that i need from $test, at the beginning of each test.

Test::Group enables to group subtests together, and if a whole subtests-group passes, then only one line is displayed, to say that the test was passed. I might try it. It's nice for the output, but doesn't help to make the test code sweeter.

I'm wondering if by subclassing Test::Class with My::Test::Class, by grouping tests in "Tests" subgroups, and creating my own simple sugar functions, i could write something that feels a bit like Test::Spec...

Replies are listed 'Best First'.
Re: Test::Class with Test::Spec's sugar ?
by kcott (Archbishop) on Jul 14, 2012 at 04:34 UTC

    I haven't used either Test::Spec or Test::Class previously. I managed to get these interacting at a very basic level: calling Test::Class::->runtests; from within a Test::Spec it() function.

    The main script:

    ken@ganymede: ~/tmp $ cat pm_class_spec_comb.pl #!/usr/bin/env perl use strict; use warnings; use Test::Spec; use Test::Class::Example::Hello::Tests; it 'runs Test::Spec and Test::Class tests' => sub { diag('Hello, world! (from Test::More::diag() as: diag())'); print qq{Hello, world! (from Test::Spec::it() as: it CODE\n}; Test::Class::->runtests; diag('Last it() statement.'); }; runtests unless caller;

    The Test::Class::Example::Hello::Tests module:

    ken@ganymede: ~/tmp/Test/Class/Example/Hello $ cat Tests.pm package Test::Class::Example::Hello::Tests; use base 'Test::Class'; sub test_Hello : Test { print qq{Hello, world! (from Test::Class as: sub test_Hello : Test +\n}; return 1; } 1;

    Sample run:

    $ pm_class_spec_comb.pl # Hello, world! (from Test::More::diag() as: diag()) Hello, world! (from Test::Spec::it() as: it CODE 1..1 Hello, world! (from Test::Class as: sub test_Hello : Test ok 1 # skip 1 # Last it() statement.

    I'll leave you to experiment further.

    -- Ken

      Thank you :o)

      It's quite unexpected, but also not quite what i expected.
      What you did here is run a Test::Class inside a Test::Spec.
      What i would like would be to write a Test::Class in Test::Spec style, with the "describe/it" nested structure.

      I still tried to add a "describe" assertion in the Test::Spec file, and a Test::More::ok() assertion into the Test.pm file, hoping that the TAP would produce a mixed output (i'm not sure how it would be useful, but i'm still interested by the experiment). But it didn't.
      I added in the pm_class_spec_comb.pl file :

      describe 'A mixed Test' => sub { it 'runs Test::Spec and Test::Class tests' => sub { [...] }; };
      and in the Test.pm file :
      use Test::More;
      and
      ok(1);
      and it printed
      ok 1 - test Hello
      which is the normal Test::Class output. While i was hoping for it to write something mixed, like
      "A mixed Test runs Test::Spec and Test::Class tests test Hello"

      But anyway, this would still not be what i want.

      Let me try to rephrase what i want.
      In Test::Spec i can build "nested statements". For example,

      describe 'A Fruit' => { [general tests on a Fruit, here] describe 'attached to its branch' => { [tests on Fruits which are still on their branch] [example :] it 'grows' => sub { ok(1); } }; describe 'fallen on the floor' => { [tests on Fruits which have fallen on the floor] }; describe 'on a pie' => sub { [tests on Fruits which are on a pie] }; };
      And in each section, the output for each assertion will start with the corresponding context. For example i will read :
      ok 1 - A fruit attached to its branch grows
      This nested structure also enables nested declaration of the variables that i need for my tests. So, all the tests that are grouped together share the same variables, which can be re-initialized before each test, with the "before each" function. This way i don't need a $test variable to reinitialise and carry variables that are used in several tests : i can directly use my variables in several tests

      See the @roll variable in my Game::Bowling example, for example. With Test::Class i would need to use $test->{roll} all the time, and i think i wouldn't be able to initialize it differently for different groups of tests.

      I don't know if such a thing can be achieved with Test::Class.
      I should maybe write my Game::Bowling example with Test::Class, to give a comparizon.

Re: Test::Class with Test::Spec's sugar ?
by Khen1950fx (Canon) on Jul 14, 2012 at 07:50 UTC
    You can create your own sugar with Test::Class::Sugar. Here's a variation on your theme:
    use Test::Class::Sugar; testclass exercises Test::Class { my $test; startup >> 1 { ok $test->subject; } test autonaming { ok $test->subject; } test the naming of parts { ok $test->current_method, 'score'; } test multiple assertions >> 2 { ok ref($test), 'Test::Class::Bowling'; ok $test->current_method, 'test_multiple_assertions'; } sub score { my $game = Bowling->new; my @rolls = (2) x 20; my $expected_score = 40; } } Test::Class->runtests;
    I still prefer saccharin:-).

      The best idea i've come up with, right now, is using a similar technique to the one used by Damian Conway in the Perl6::Slurp tests with Test::More, and extend it to Test::Class. Here is what he does, to avoid duplicating the test name (Test::Class does this automatically, with the subroutine name) :

      my $desc; sub TEST { $desc = $_[0] }; TEST "can't slurp in void context"; # your test here ok 1, $desc;

      What i did to extend it to groups of tests in Test::Class, is to use Tests() groups, and having the TEST subroutine declare a test description for each test, and append the subroutine name at the beginning. Like this :

      use strict; use warnings; #comment before releasing use Perl6::Say; use Time::TimeTick; # TEST : # my function for displaying information about each test, # in a Test::Class Test subroutine with several tests my $desc; sub TEST { my $test_name = $_[0]; my $caller_func = (caller(1))[3]; # the subroutine full name my $test_group = (split('::', $caller_func))[-1]; # only the subroutine name $test_group =~ s/_/ /g; # replace underscores by spaces $desc = "$test_group $test_name"; }; use base qw(Test::Class); use Test::More 0.98; __PACKAGE__->runtests unless caller; sub A_Fruit__On_a_branch : Tests(3) { TEST "Grows"; ok 1, $desc; TEST "Can rot"; ok 1, $desc; TEST "Can fall"; ok 1, $desc; }; sub A_Fruit__On_the_floor__Rots : Test { ok 1; } done_testing(); 1;
      The output is
      1..4 ok 1 - A Fruit On a branch Grows ok 2 - A Fruit On a branch Can rot ok 3 - A Fruit On a branch Can fall ok 4 - A Fruit On the floor Rots

      Conclusion :
      This does part of what i want : my (one level) nested structure enables me to avoid duplication in test names.
      And it doesn't do some of the things that i want (and that Test::Spec does - just check the synopsis) :
      - more than one level nested structure.
      - different "before" setup functions in different test groups.
      - nested "before" setup functions : "The setup work done in each before block cascades from one level to the next"

      PS : i just realized, i have no idea to which extent this would work with the Test::Class inheritance process. I guess i'll have to try.

      And same thing if i implemented a bit of Test::Spec into Test::Class (i thought of a possible not-too-hard way to implement "describe" and "before each"), i'm not sure that it would work with inheritance. You can inherit a subroutine, but how do you inherit it's context ("describe" and "before each" nested structure) ?

      Cheers =)

      If i understood well, Test::Class::Sugar is one particular type of sugar. And i would need to use Test::Class::Sugar::CodeGenerator to create my own, which feels too complex for me now.
      I also checked the Test::Spec code, with the same result : too complex for me to build something equivalent.