February 16, 2005

Shuffling between Java and Perl: Testing libraries

I code Java for work during the day, Perl for fun during my spare time (Sometimes I get to use Perl during the day as well, but not as a rule.) People who first get into a language with very different naming/formatting rules can complain angrily about them – every once in a while there’s a post bitching about Java syntax in Perlmonks. And try googling for people hating Perl syntax…

Some people get stuck on the sigils, camel-case vs. underscores, dot vs. arrow, etc. It's just syntax, and you do it for long enough you get used to it.[1] (And if you can't get used to it, maybe you should find another profession.) If I've been doing a lot of one vs the other for an unbroken stretch it may take my brain 15 seconds to adjust. Big deal.

However, one thing my little brain keeps tripping over is a seemingly inocuous difference in arguments used in standard testing libraries. Both the libraries have a set of simple assertions but they order the arguments to those assertions completely differently.

So in Perl we have Test::More which has a set of assertions organized like this:

assertion-type( $actual, $expected, [ $optional_msg ] );

Your test might look like:

use strict;
use Test::More tests => 2;

my $name = 'Frobozz'; is( $name, 'frobozz' ); like( $name, qr/boz$/, 'Wizard name matches pattern' ); </pre>

When the tests fail you'll see:

not ok 1
#     Failed test (sample.t at line 5)
#          got: 'Frobozz'
#     expected: 'frobozz'
not ok 2 - Wizard name matches pattern
#     Failed test (sample.t at line 6)
#                   'Frobozz'
#     doesn't match '(?-xism:boz$)'
# Looks like you failed 2 tests of 2.

Onto Java. JUnit is the standard Java testing library. Its asssertions look like this:

  assertEquals( expected, actual );
  assertEquals( optionalMessage, expected, actual );

The Java implementation of the above Perl test might look like this:

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import junit.framework.TestCase;
import junit.textui.TestRunner;
  
public class SampleTest extends TestCase {
    private String name;
  
    public void testWizardMatch() {
        assertEquals( "frobozz", name );
    }
  
    public void testWizardPattern() {
        Matcher m = Pattern.compile( "boz$" ).matcher( name );
        assertTrue( m.find() );
    }
  
    protected void setUp() {
        name = "Frobozz";
    }
 
    public static void main( String[] args ) {
        TestRunner.run( SampleTest.class );
    }
}

(In practice I'd put both name checks in the same method, but I wanted to be able to show failures for both.)

And when those assertions fail you'll see:

.F.F
Time: 0.01
There were 2 failures:
1) testWizardMatch(SampleTest)junit.framework.ComparisonFailure: expected: but was:
        at SampleTest.testWizardMatch(SampleTest.java:10)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at SampleTest.main(SampleTest.java:23)
2) testWizardPattern(SampleTest)junit.framework.AssertionFailedError
        at SampleTest.testWizardPattern(SampleTest.java:15)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at SampleTest.main(SampleTest.java:23)

FAILURES!!! Tests run: 2, Failures: 2, Errors: 0 </pre>

(Discussion of which error message is more helpful or can be more easily duplicated to run in a standard test harness is reserved until later, as is a rant about the paucity of assertions in JUnit...)

Part of the reason is built into the language -- because perl routines can take a variable number of arguments you typically put them at the end so you can do:

sub my_routine {
    my ( $required_foo, $required_bar, $optional_msg ) = @_;
    $optional_msg ||= 'some default value';
}

Since Java has overloading this is less common, although I do think it's weird to have optional arguments at the front of an overloaded method. (But some OO overlords wrote this, so it must be good. I kid because I love, really.)

The sneaky part is that when you mixup the 'expected' and 'actual' order, everything will still work! The only way you know is from the assertion's failure message and, assuming you have not been doing TDD and getting frequent assertion failures, you've probably written a while bunch of assertions using the wrong order when you finally do hit a failure. Doh!


[1] That said, if you're going against the standard conventions in a language you'd better have a damned good reason, especially if those conventions are well-documented. I'm looking at you, CPAN modules that use camel-case!

Next: First car repair on the Honda
Previous: Segue? Who needs a segue?