<rdf:RDF
    xmlns:s='http://snipsnap.org/rdf/snip-schema#'
    xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
    xml:base='http://community.moertel.com/ss/rdf'>
    <s:Snip rdf:about='http://community.moertel.com/ss/rdf#start/2005-02-08/1'
         s:name='start/2005-02-08/1'
         s:cUser='tmoertel'
         s:oUser='tmoertel'
         s:mUser='tmoertel'>
        <s:content>1 When worlds collide and then dance: Test::LectroTest meets Test::Builder {anchor:When worlds collide and then dance: Test::LectroTest meets Test::Builder}&#xA;It seems that LectroTest is picking up in popularity because&#xA;I am starting to get regular requests and feedback.  Recently,&#xA;two related requests came in regarding something&#xA;that I had put off:  Figuring out how to merge the world of&#xA;specification-based testing that relies upon thousands of&#xA;trials per property check with the world of case-based testing&#xA;where there is one trial per case.&#xA;&#xA;The rub is that case-base testing in Perl is managed by&#xA;Test::Builder and a related family of modules that are designed to&#xA;make that kind of testing easy.  One convenience they offer is that&#xA;calling test functions like ~~cmp_ok~~ not only performs a test, in&#xA;this case a general comparison, but also reports the result of the&#xA;test to the test harness.&#xA;&#xA;So what happens if somebody wants to perform a ~~cmp_ok~~ test from&#xA;within a LectroTest property specification?  When the property is&#xA;checked, LectroTest will test whether the property holds in one thousand random&#xA;trials (by default), each of which will end up calling ~~cmp_ok~~.&#xA;Can you see where this is going?  Yup, the test harness will end up&#xA;seeing one thousand separate tests instead of the single test of the&#xA;property.&#xA;&#xA;The solution to this problem, as to all problems of distinction, is&#xA;hackery.  The Test::* family of modules ends up filtering all calls to&#xA;test functions such as ~~cmp_ok~~ down to the Test::Builder module&apos;s&#xA;~~ok~~ method.  This method does two things.  First, it reports the&#xA;result of the test to the test harness without giving us a chance to&#xA;say otherwise. (Naughty!) Second, it returns the result of the test&#xA;back to the original caller.  As far as property checks are concerned,&#xA;the first part is bad, and the second, good.  So, I basically redefine&#xA;the ~~ok~~ method during property checks to throw out the first part.&#xA;&#xA;In a little more detail, here is how it works.  I created a new&#xA;module Test::LectroTest::Compat that exports a single function ~~holds~~.&#xA;This function is used to inject a property check into a plain-old&#xA;Test::Simple- or Test::More-style test plan.  For example:&#xA;&#xA;{code:none}&#xA;use Test::More tests =&gt; 2;&#xA;use Test::LectroTest::Compat;&#xA;&#xA;my $prop_nonnegative = Property {&#xA;    \##[ x &lt;- Int, y &lt;- Int ]##&#xA;    cmp_ok(my_function( $x, $y ), &apos;&gt;=&apos;, 0);&#xA;}, name =&gt; &quot;my_function output is non-negative&quot; ;&#xA;&#xA;__holds( $prop_nonnegative );__  # assert that the prop holds&#xA;cmp_ok( 0, &apos;&lt;&apos;, 1, &quot;trivial 0&lt;1 test&quot; );  # a &quot;normal&quot; assertion&#xA;\# ... and so on ...&#xA;{code}&#xA;&#xA;What, then, does ~~holds~~ do when called?  It redefines&#xA;Test::Builder&apos;s ~~ok~~ method, runs the property-check trials,&#xA;restores ~~ok~~, and finally reports the property check&apos;s results via the&#xA;newly-restored ~~ok~~.  From there, Test::Builder takes over and does&#xA;the magic necessary to incorporate the result into our&#xA;run-of-the-mill test plan per the test harness&apos;s expectations.&#xA;&#xA;The actual code is as follows:&#xA;&#xA;{code:none}&#xA;sub holds {&#xA;    my ($diag_store, $results) = check_property(@_);&#xA;    my $success = $results-&gt;success;&#xA;    (my $name   = $results-&gt;summary) =~ s/^.*?- /property /;&#xA;    $Test-&gt;ok($success, $name);&#xA;    my $details = $results-&gt;details;&#xA;    $details =~ s/^.*?\\\n//;     # remove summary line&#xA;    $details =~ s/^\\\# /    /mg; # replace commenting w/ indent&#xA;    $Test-&gt;diag(@$diag_store) if @$diag_store;&#xA;    $Test-&gt;diag($details) if $details;&#xA;    return $success;&#xA;}&#xA;&#xA;sub check_property {&#xA;    no strict &apos;refs&apos;;&#xA;    no warnings;&#xA;    my $diag_store = \[\];&#xA;    my $property = shift;&#xA;    local *Test::Builder::ok = \\\&amp;disconnected_ok;&#xA;    local *Test::Builder::diag = sub { shift; push @$diag_store, @_ };&#xA;    return ( $diag_store, &#xA;             Test::LectroTest::TestRunner-&gt;new(@_)-&gt;run($property) );&#xA;}&#xA;&#xA;sub disconnected_ok { $_\[1\] ? 1 : 0 }&#xA;{code}&#xA;&#xA;You can see that there is one extra bit of hackery going on.  I also&#xA;redefine Test::Builder&apos;s ~~diag~~ method to capture any diagnostic&#xA;output that may be emitted during the trials.  Typically, this would&#xA;occur only when a trial fails, and in this case the output is almost&#xA;certainly worth passing back to the user in context.  To ensure that&#xA;the user sees it in context, I hold on to the captured output until&#xA;the property check is complete and then roll it into the normal&#xA;LectroTest diagnostic output.  It looks great:&#xA;&#xA;{code:none}&#xA;not ok 3 - property &apos;x is a natural number&apos; falsified in 2 attempts&#xA;\#     Failed test (t/compat.t at line 32)&#xA;\#     &apos;0&apos;&#xA;\#         &gt;&#xA;\#     &apos;0&apos;&#xA;\#     Counterexample:&#xA;\#     $x = 0;&#xA;{code}&#xA;&#xA;In the first part you see the typical ~~cmp_ok~~ output that the&#xA;assertion 0 &amp;gt; 0 failed.  The second part is the LectroTest&#xA;counterexample that shows at what part of the test space the assertion&#xA;failed.&#xA;&#xA;It takes some complicated footwork, but the resulting dance is&#xA;beautiful.  To see two markedly different testing systems &amp;#8211; and&#xA;in some ways markedly different testing philosophies &amp;#8211; working&#xA;in step with one another is gratifying.  I suspect that most&#xA;real-world testing problems can be solved better by a combination of&#xA;the two approaches than by either alone.  Thus I am especially happy&#xA;about this integration.&#xA;&#xA;After some more refinement, I am going to incorporate&#xA;Test::LectroTest::Compat into the main distribution.&#xA;For now, you can get a copy in the [LectroTest/FAQs] section of the site.</s:content>
        <s:mTime>2005-02-08 20:56:50.34</s:mTime>
        <s:cTime>2005-02-08 01:28:39.298</s:cTime>
        <s:comments
             rdf:type='http://www.w3.org/1999/02/22-rdf-syntax-ns#Bag'/>
        <s:snipLinks>
            <rdf:Bag>
                <rdf:li rdf:resource='http://community.moertel.com/ss/rdf#start/2005-02-16/1'/>
                <rdf:li rdf:resource='#snipsnap-index'/>
                <rdf:li rdf:resource='#tmoertel'/>
                <rdf:li rdf:resource='http://community.moertel.com/ss/rdf#LectroTest/FAQs'/>
                <rdf:li rdf:resource='http://community.moertel.com/ss/rdf#Start/2005-02-08/1'/>
            </rdf:Bag>
        </s:snipLinks>
        <s:attachments
             rdf:type='http://www.w3.org/1999/02/22-rdf-syntax-ns#Bag'/>
    </s:Snip>
</rdf:RDF>
