<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="/stylesheets/rss.css" type="text/css"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">
  <channel>
    <title>Tom Moertel's Weblog: When worlds collide and then dance: Test::LectroTest meets Test::Builder</title>
    <link>http://blog.moertel.com/articles/2005/02/08/when-worlds-collide-and-then-dance-test-lectrotest-meets-test-builder</link>
    <language>en-us</language>
    <ttl>40</ttl>
    <description>Quality rants on programming theory and stuff geeks like</description>
    <item>
      <title>When worlds collide and then dance: Test::LectroTest meets Test::Builder</title>
      <description>&lt;p&gt;It seems that &lt;a href="http://community.moertel.com/ss/space/LectroTest"&gt;LectroTest&lt;/a&gt; is picking up in popularity because I am starting to get regular requests and feedback. Recently, two related requests came in regarding something that I had put off: Figuring out how to merge specification-based testing, which samples thousands of trials per property check, with case-based testing, which  runs one trial per case.&lt;/p&gt;


	&lt;p&gt;The rub is that case-base testing in Perl is managed by &lt;a href="http://search.cpan.org/perldoc?Test::Builder"&gt;Test::Builder&lt;/a&gt; and a related family of modules that are designed to make that kind of testing easy. One convenience they offer is that calling test functions like &lt;em&gt;cmp_ok&lt;/em&gt; not only performs a test, in this case a general comparison, but also reports the result of the test to the test harness.&lt;/p&gt;


	&lt;p&gt;So what happens if somebody wants to perform a &lt;em&gt;cmp_ok&lt;/em&gt; test from within a LectroTest property specification? When the property is checked, LectroTest will test whether the property holds by sampling a thousand random trials (by default), each of which will end up calling &lt;em&gt;cmp_ok&lt;/em&gt;. Can you see where this is going? Yup, the test harness will end up seeing one thousand separate tests instead of the single test of the property.&lt;/p&gt;


	&lt;p&gt;The solution to this problem, as to all problems of distinction, is hackery. The Test::* family of modules ends up filtering all calls to test functions such as &lt;em&gt;cmp_ok&lt;/em&gt; down to the Test::Builder module&amp;#8217;s &lt;em&gt;ok&lt;/em&gt; method. This method does two things. First, it reports the result of the test to the test harness without giving us a chance to say otherwise. (Naughty!) Second, it returns the result of the test back to the original caller. As far as property checks are concerned, the first part is bad, and the second is good.&lt;/p&gt;


	&lt;p&gt;To get rid of the bad part, I redefine the &lt;em&gt;ok&lt;/em&gt; method during property checks.   I put the implementation into a new module, Test::LectroTest::Compat, that exports a single function &lt;em&gt;holds&lt;/em&gt;. This function is used to inject a property check into a plain-old Test::Simple- or Test::More-style test plan. For example, here&amp;#8217;s a test plan that uses the new module:&lt;/p&gt;


&lt;pre&gt;&lt;code&gt;use Test::More tests =&amp;gt; 2;
use Test::LectroTest::Compat;

my $prop_nonnegative = Property {
    ##[ x &amp;lt;- Int, y &amp;lt;- Int ]##
    cmp_ok(my_function( $x, $y ), '&amp;gt;=', 0);
}, name =&amp;gt; "my_function output is non-negative" ;

holds( $prop_nonnegative ); # assert that the prop holds
cmp_ok( 0, '&amp;lt;', 1, "trivial 0&amp;lt;1 test" ); # a "normal" assertion
# ... and so on ...
&lt;/code&gt;&lt;/pre&gt;

	&lt;p&gt;What does &lt;em&gt;holds&lt;/em&gt; do when called? It redefines Test::Builder&amp;#8217;s &lt;em&gt;ok&lt;/em&gt; method, runs the property-check trials, restores &lt;em&gt;ok&lt;/em&gt;, and finally reports the property check&amp;#8217;s result via the newly-restored &lt;em&gt;ok&lt;/em&gt;. From there, Test::Builder takes over and does the magic necessary to incorporate the result into the test session&amp;#8217;s &lt;a href="http://perldoc.perl.org/Test/Harness/TAP.html" title="Test Anything Protocol"&gt;&lt;span class="caps"&gt;TAP&lt;/span&gt;&lt;/a&gt; output.&lt;/p&gt;


	&lt;p&gt;The code that does all this is as follows:&lt;/p&gt;


&lt;pre&gt;&lt;code&gt;sub holds {
    my ($diag_store, $results) = check_property(@_);
    my $success = $results-&amp;gt;success;
    (my $name   = $results-&amp;gt;summary) =~ s/^.*?- /property /;
    $Test-&amp;gt;ok($success, $name);
    my $details = $results-&amp;gt;details;
    $details =~ s/^.*?\n//;     # remove summary line
    $details =~ s/^\# /    /mg; # replace commenting w/ indent
    $Test-&amp;gt;diag(@$diag_store) if @$diag_store;
    $Test-&amp;gt;diag($details) if $details;
    return $success;
}

sub check_property {
    no strict 'refs';
    no warnings;
    my $diag_store = [];
    my $property = shift;
    local *Test::Builder::ok = \&amp;#38;disconnected_ok;
    local *Test::Builder::diag = sub { shift; push @$diag_store, @_ };
    return ( $diag_store, 
             Test::LectroTest::TestRunner-&amp;gt;new(@_)-&amp;gt;run($property) );
}

sub disconnected_ok { $_[1] ? 1 : 0 }
&lt;/code&gt;&lt;/pre&gt;

	&lt;p&gt;You can see that there is one extra bit of hackery going on. I also redefine Test::Builder&amp;#8217;s &lt;em&gt;diag&lt;/em&gt; method to capture any diagnostic output that may be emitted during the trials. Typically, this occurs only when a trial fails, and in that case the output is almost certainly worth passing back to the user in context. To ensure that the user sees it in context, I hold on to the captured output until the property check is complete and then roll it into the normal LectroTest diagnostic output. It looks great:&lt;/p&gt;


&lt;pre&gt;&lt;code&gt;not ok 3 - property 'x is a natural number' falsified in 2 attempts
#     Failed test (t/compat.t at line 32)
#     '0'
#         &amp;gt;
#     '0'
#     Counterexample:
#     $x = 0;
&lt;/code&gt;&lt;/pre&gt;

	&lt;p&gt;In the first part you see the typical &lt;em&gt;cmp_ok&lt;/em&gt; output that the assertion 0 &amp;gt; 0 failed. The second part is the LectroTest counterexample that shows at what part of the test space the assertion failed.&lt;/p&gt;


	&lt;p&gt;It takes some complicated footwork, but the resulting dance is beautiful. To see two markedly different testing systems &amp;#8211; and in some ways markedly different testing philosophies &amp;#8211; working in step with one another is gratifying. I suspect that most real-world testing problems can be solved better by a combination of the two approaches than by either alone. Thus I am especially happy about this integration.&lt;/p&gt;


	&lt;p&gt;After some more refinement, I am going to incorporate Test::LectroTest::Compat into the main distribution. For now, you can get a copy in the &lt;a href="http://community.moertel.com/ss/space/LectroTest/FAQs"&gt;LectroTest/FAQs&lt;/a&gt; section of the &lt;a href="http://community.moertel.com/"&gt;Community Projects site&lt;/a&gt;.&lt;/p&gt;</description>
      <pubDate>Tue, 08 Feb 2005 12:00:00 -0500</pubDate>
      <guid isPermaLink="false">urn:uuid:08b12d2a0fdce3d861ee958238ebef60</guid>
      <author>Tom Moertel</author>
      <link>http://blog.moertel.com/articles/2005/02/08/when-worlds-collide-and-then-dance-test-lectrotest-meets-test-builder</link>
      <category>perl</category>
      <category>testing</category>
      <category>perl</category>
      <category>testing</category>
      <category>lectrotest</category>
      <trackback:ping>http://blog.moertel.com/articles/trackback/39</trackback:ping>
    </item>
  </channel>
</rss>
