JUnit Joys

Posted by anton
on Thursday, January 31, 2008

i have been wrestling with some legacy code recently. beating it over the head with a copy of Working Effectively with Legacy Code did not do much, so i started writing tests to see how it worked.

after a few minutes of waiting for eclipse + junit4.3.11 combo to load, i furiously coded a bunch of tests, and then realized that i could not make them fail. essentially it boiled down to the following:

assertEquals(1.0, 1.1);

...which quietly and happily passes. wtf?!! these are two doubles, just compare them and let’s move on with our lives!

then after a bit of thinking i recalled that most of my tests that involved doubles were written on projects that were still on jdk < 1.5, when method above simply won’t even compile, alerting me to the fact that junit expects assertEquals(double, double, delta), where delta is the precision you need.

why they couldn’t simplify my life by creating a couple of Double instances and calling equals() on them is beyond me. this is what i would want most of the time anyway.

since i was running tests under jdk1.5, autoboxing kicked in and now we have two Doubles on our hands. fine, this should not be a big deal, simply call double1.equals(double2) and be done with it – what’s a big deal?

but nooooooooo, check out this little bundle of joy:

private static boolean isEquals(Object expected, Object actual) {
    if (expected instanceof Number && actual instanceof Number)
        return ((Number) expected).longValue() == ((Number) actual).longValue();
    return expected.equals(actual);
}

what the hell?!! you correctly detect that this instance of Double is an instance of Numeric, then take its long value and throw fractional part out. quietly. so all my tests never even squeak. why?!!!

an obvious approach is to suck it up and appease the api:

assertEquals(1.0, 1.1, 0);

this will work. but damn! my eyes!!

TestNG

we all know that junit is legacy, and version 4 was an afterthought, so let’s see what testng does. the api is the same, and under jdk1.5 my primitives get autoboxed into Doubles and assertEquals(Object, Object) gets called:

public static void assertEquals(Object actual, Object expected, String message) {
    if (expected == null && actual == null)
        return;
    if (expected != null && expected.equals(actual)) {
        return;
    } else {
        failNotEquals(actual, expected, message);
        return;
    }
}

voila! this is exactly what i expected. and guess what – it actually works and correctly fails the original test.

Lessons

  • man, this alone would scare me away from junit in favor of testng
  • make sure your test fails before it ever works!
  • what really frightens me is how many more of these quiet autoboxing errors are lurking out there. what previously would be caught by the compiler now silently works in unpredictable ways
  • yet another thing to be aware of when upgrading your app from jdk1.[34] to jdk1.5+

Note

this junit bug has been fixed in junit4.4 and up

Ruby

interestingly enough, on a few skunk works projects this year i cobbled together a bunch of existing libraries with ruby glue and used (j)ruby’s Test::Unit (which conveniently comes with ruby distro) and rspec for testing the result.

it definitely makes sense for a project that is written in (j)ruby that uses many existing java libraries; it might be a bit of a mindset shift for a java project that is looking for simplified testing. for now i am keeping an eye on projects like JtestR

1 default version of junit that ships with eclipse 3.3

cafe babe 0

Posted by anton
on Saturday, October 27, 2007

background: 10K+ compiled class files and sources that got out of sync1; need to figure out which sources are valid, and which ones are not.

decompiling things is the last resort, since sources produced are not easily diff‘able against the sources you’ve got. the likes of diffj is not much help either, and i did not even want to go down the rabbit hole of normalization through obfuscators.

so if you do not feel like wielding antlr or javacc to normalize two sources, the obvious approach is to simply recompile and compare with the existing class files (just beware of missing class files that might not have any sources at all).

however, keep in mind that javac by default includes line number table in the class file it produces2. this means that even if you added or removed a line of comments or even a blank line before any sources, it would result in a classfile that is different from the original.

sometimes you have another class inside the .java file (not to be confused with inner classes). in this case it gets compiled into a separate class file. so if your main class’ source code has changed, it will affect the line number table of the other class as well. this means that even though another class’ source has not changed, its generated class file will be different.

in my case i also had to check which jdk compiler produced the class files. one can always opt for javap that prints minor and major versions, or if you are feeling manly enough, whip out your favorite hex editor and check bytes 6 and 7 (according to the vm spec). in general, javap is the easiest way to check the internal structure of the class file.

finally, to diff files and directories i simply used svn – check the originals into the local repo, then put your stuff in a working copy – it will do the rest. after all, this is what it’s good at.

oh and why CAFE BABE? look it up

1 this is a whole different and interesting topic – how any sort of generated content creates a possibility of this disconnect. all these xdoclets, jaxb-generated sources, and even compiled classfiles create artifacts that now have a potential of getting out of sync with the sources. yes, proper engineering practices mitigate the risks, but all things being equal, i like the fact that with scripting languages this problem is largely non-existent. what you see is what you run.

2 you could always run javac with -g:none to get rid of line numbers, but it was no help in my case.

some sort of pun on ruby, java, and gluing goes here. i got nothing. 0

Posted by anton
on Thursday, June 21, 2007

speaking of gluing things, below is a jruby script i cobbled together to get a backup of an archaic snipsnap instance.

as you might have guessed, it was just an excuse to play with ActiveRecord-JDBC, since all it really takes is just connecting to the database and pulling one table out.

still, it was fun and just a few lines of code, although you had to install ActiveRecord gem as well as ActiveRecord-JDBC gem (not to mention adding mysql jdbc driver in the classpath). as an excuse, i did not want to deal with low-level jdbc machinery, nor did i want to install another gem to get ruby's mysql connectivity.

although it takes an ungodly amount of time to startup, it works just fine. here's the best of both worlds - java's jdbc type4 driver prowess and ruby's terse and readable way of expressing yourself (plus the quick feedback of edit-run-swear-edit cycle):

#!jruby
require 'rubygems'
gem 'ActiveRecord-JDBC'
require 'jdbc_adapter'
require 'active_record'

require 'FileUtils'

ActiveRecord::Base.establish_connection(
          :adapter  => 'jdbc',
          :driver   => 'com.mysql.jdbc.Driver',
          :url      => 'jdbc:mysql://host/snipsnap',
          :username => 'username',
          :password => 'password'
)

class Wiki < ActiveRecord::Base
  set_table_name 'Snip'
end

pages = Wiki.find(:all, :order => 'name')

index = '<html><body><ul>'

pages.each do |p|
  name = p.name.gsub(/:/, '')
  FileUtils.mkpath name if !File.exist? name
  open(File.join(name, 'index.txt'), 'w') { |f| f.puts p.content }

  index << '<li><a href="' << name << '/index.txt">' << name << '</a></li>'
end

index << '</ul></body></html>'

open('index.html', 'w') { |f| f.puts index }

snipsnap does boast xml-rpc support, but it only provides a meager pingback ability.