Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

Writing code using Test::More -- how to fail within subs?

by John M. Dlugosz (Monsignor)
on Feb 08, 2003 at 06:11 UTC ( [id://233656]=perlquestion: print w/replies, xml ) Need Help??

John M. Dlugosz has asked for the wisdom of the Perl Monks concerning the following question:

I wrote a test, then want a bunch more almost like it but with different data. So I put all the guts into reusable sub's and drive the actual test case with a couple calls and only code that's really different for this case.

The code in the sub's will do things like open ... or die. If I change the die to a fail then it doesn't stop trying to go on, when it needs to jump to the next whole test case.

If I put a eval around the top-level test case code, I have more duplication in the "catch" part.

Hmm, I suppose I can always fail (printing the right message) and then die in the common code, but that's less elegant than the "...or die" idiom. It turns into an if block, or more cryptically a do block in the "or" part. If I make a die-ing fail wrapper, I'd need to do similar to every Test::More handy function.

There are lots of ways to write it on one line anyway, and maybe a few ways to reuse the code. How would you do it? I'm interested in generalizing the idiom to work with any of the test primitives, and keeping the code clear and short.

TIMTOWTDI,
—John

#start the test case. my $x= new blahblah; # what I'm testing. # The above and following method calls will be different for each test +. $x->foo (5000); $x->bar(200); # output_test is my common code I'll use i'll use to validate all the +similar tests. my $ot= output_test->new ("out1"); # ready... different name for each +. my $outfile= $ot->openout(); # get set... (common code to generate fi +le name, open it for output, check for errors) $x->emit ($outfile); # this method call will differ for each test cas +e, so it's not part of the common code. $ot->ok_files_match(); # and back to the common code again to compare + the resulting file with a reference "correct" file. # do all that again, with differences. # ...
How should the $ot-> methods contain the common code to "fail" or otherwise validate things as it goes?

P.S. Perhaps I must have a "catch" (if ($@) { ...) for this top-level code that contains the fail, because the code I'm calling (e.g. emit()) might die, and that's not specially-written test code.

Replies are listed 'Best First'.
Re: Writing code using Test::More -- how to fail within subs?
by adrianh (Chancellor) on Feb 08, 2003 at 10:13 UTC

    Test::Class would be one solution. Something like this should do the job (untested code):

    { package BlahBlah::Test; use base qw(Test::Class); sub test_object_emit { my ($self, $test_object, $emit_method) = @_; my $ot = output_test->new( $self->current_method ); my $outfile = $ot->openout(); $test_object->$emit_method($outfile); $ot->ok_files_match(); }; sub out1 : Test(tests => 4) { my $self = shift; my $x = new BlahBlah $x->foo(5000); $x->bar(200); $self->test_object_emit($x, "emit"); }; sub out2 : Test(tests => 4) { my $self = shift; my $x = new Fribble $x->ni(12); $self->test_object_emit($x, "super_emit"); }; # ... etc ... }; BlahBlah::Test->runtests;

    We name our test methods after each output_test. Since $self->current_method returns the name of the running test method we can use this to create the appropriate output_test object in test_object_emit.

    It you die in a Test::Class test method the remaining tests in that method are automatically failed. So if anything in test_object_emit dies all the tests in the calling test method will fail, and you'll go on to the next test case (aka method).

    I'm assuming that ok_files_match runs four tests (hence the tests => 4).

      Thanks, I'll read the docs on Test::Class. But what's an xUnit? The description explains “Easily create test classes in an xUnit style.” and that's not very enlightening since I've never heard of xUnit style.

        xUnit has become the "generic" name for OO test frameworks similar to Test::Class and Test::Unit.

        The "Unit" is from "unit testing" (although they're useful in other testing areas too) and the "x" refers to the language involved (JUnit == Java, PUnit == Python, etc.) This styles follows SUnit, Kent Beck's Smalltalk testing framework.

        If you're interested take a look at Kent Beck's original paper on SUnit and this list of other xUnit frameworks.

Re: Writing code using Test::More -- how to fail within subs?
by adrianh (Chancellor) on Feb 08, 2003 at 10:24 UTC

    Another way :-)

    If you don't mind running an indeterminate number of tests, just use Test::Exception's lives_ok. For example:

    use Test::More 'no_plan'; use Test::Exception; lives_ok { my $x= new blahblah; $x->foo (5000); $x->bar(200); my $ot= output_test->new ("out1"); my $outfile= $ot->openout(); $x->emit ($outfile); $ot->ok_files_match(); } 'test case out1 worked'; # ... etc ...
      I think that's close to what I did:
      sub tryit (&$) { my ($code, $label)= @_; eval { &$code }; if ($@) { my $error= $@; # remember that in case fail() changes it. fail $label; diag "$label fails:", $error; } }
      My code ends in an ok...() type statement, so if it reaches the end the tryit wrapper doesn't need to do anything. But if there is an exception, then the wrapper issues the NOT OK line. So, I don't get two tests out on success or one on failure like you seem to be illustrating re 'no_plan'.

      —John

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://233656]
Approved by Paladin
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others wandering the Monastery: (None)
    As of 2024-04-25 01:13 GMT
    Sections?
    Information?
    Find Nodes?
    Leftovers?
      Voting Booth?

      No recent polls found