[Pdx-pm] Test::Builder calling convention

Michael G Schwern schwern at pobox.com
Fri Mar 13 22:24:36 PDT 2009


Eric Wilhelm wrote:
> # from Hans Dieter Pearcey
> # on Friday 13 March 2009 12:08:
> 
>> Trouble is, I can't remember what it bought us over a regular or
>> operator. 
>>>     $builder->ok( open my $fh, $file )
>>>             ->diag( "Errno: $!" )
>>>       or
>>>     $builder->is( $!, ENOBACON )
>>>             ->name( "Insufficient bacon" );
>>>
>>> That would appear to be perfectly sufficient, understandable to all
>>> and you get real short circuiting, not calling empty methods on an
>>> empty object.
> 
> But 'or' does not have sufficient precedence here (you are trying to 
> return the result object?)

I probably need a ||, that's fine too.


>> It seems weird to me that diag() in your second example is returning
>> whether or not the test passed.
> 
> Actually, I question diag() being applied directly to a result object 
> (e.g. $builder->ok(1)->diag("fail!")) -- Does the diag() merely 
> passthrough when called on a true result object?

Nope.  The result object stores the diagnostic and then the (currently
non-existent) output object decides what to output, where and how.

A TAP output object, using normal comment diagnostics, would simply not
display the diagnostic at all if the test passed.  So if it passed you might see:

    ok 1

and if it failed

    not ok 1
    # fail!

A future TAP object, using structured diagnostics, might always spit the
diagnostics out and the displayer decides what to do with them.  Consider the
diagnostics on an test.

    $builder->ok( $this eq $that )
            ->diag([
                have => $this,
                want => $that
            ]);

(Yes, there will be an is() to encapsulate this)

If $this and $that are both generated, it may be useful to know what they are:

    ok 23
      ---
      have: hubble bubble
      want: hubble bubble
      ...

This will be of particular interest to TAP archivers I'm sure.

Consider an even wider scope.  By fulling decoupling the output considerations
from the test logic, we enable the ability to use tests as comparison
functions.  Something I eluded to earlier.  This would potentially allow
something like Test::Deep's awesome complex structure comparison functions to
be used in production.

It's better when you think of the diagnostic as just more information, rather
than something redundant like "the test failed".


> But, should this example actually be like so?  That is, we want open() 
> to fail because the goal is to verify the errno.
> 
>   return $builder->ok( ! open my $fh, $file )
>           ->or->diag( "Expected open to fail" )
>           ->and
>           ->is( $!,  ENOBACON )
>           ->name( "Insufficient bacon" );

Your diagnostic is really more the name.  It might instead look like this:

    return $builder->ok( !open my $fh, $file )
             ->name("Can't open $file for reading")
             ->and
             ->is( $!, ENOBACON )
             ->name( "Insufficient bacon" );



> Or to put it another way:
> 
>   my $res = $builder->ok( ! open my $fh, $file );
>   $res->name( "Insufficient bacon" );
>   return $res->diag( "Expected open to fail" ) unless($res);
> 
>   return $res->is( $!, ENOBACON );

At that point there's no reason to reuse the result since the second call to
is() will simply make a new one.

    my $ok = $builder->ok( !open my $fh, $file )
               ->name("Can't open $file for reading");
    return $ok unless $ok;

    return $builder->is( $!, ENOBACON )
             ->name( "Insufficient bacon" );

Or

    return $builder->ok( !open my $fh, $file )
             ->name("Can't open $file for reading")
             &&
           $builder->is( $!, ENOBACON )
             ->name( "Insufficient bacon" );

A concrete benefit of removing and() and or() is the result object does not
need to know how to make another result object.  It doesn't need to know
$builder's methods, ok() and is() being methods of Test::Builder2, not the
result object.  To make and() and or() work, the result object would have to
store $builder so it can call it if necessary.  It would either have to know
$builder's result methods or simply handy anything it doesn't recognize
through.  The first is too tight a coupling.  The second makes it difficult to
diagnose mistakes calling methods on a result object.  You might accidentally
call a $builder method.


> hmm... $builder->ko( open my $fh, $file );  Groan, not this again!  
> Maybe we need an example with not so much negation here.

Still not making a "not ok" method.


-- 
Alligator sandwich, and make it snappy!


More information about the Pdx-pm-list mailing list