21 November 2012

JUnit 4.11 - What's new? Rules

JUnit 4.11 is a major release of the popular testing framework. Amongst the things I contributed were changes to @Rule and @ClassRule, to allow these annotations to be used on a method as well as a public field. I wanted this because I use Rules heavily in Java, and I wanted to be able to use them in Scala as well. Mainly to make the transition from Java to Scala easier.

Let’s take an example:

public class JavaRuleTest {
  @Rule public ExpectedException expectedException = ExpectedException.none();
 
  @Test
  public void test() {
    expectedException.expect(NumberFormatException.class);
    Integer.valueOf("foo");
  }
}

So this test uses the ExpectedException rule which allows you to say that you expect to receive an exception, in this case a NumberFormatException. Which we do. So the test passes. If we translate this directly to Scala, we get:

class ScalaRuleTest {
  @Rule val expectedException = ExpectedException.none()
 
  @Test def test() {
    expectedException.expect(classOf[NumberFormatException])
    Integer.valueOf("foo")
  }
}

But this produces an error:

The @Rule 'expectedException' must be public.

Although we’re defining a ‘public’ field expectedException, Scala implements this as a private field with an accessor method. So JUnit expects a public field, but finds a private one; the test fails. But now, with JUnit 4.11, we can apply a @Rule to a method:

class ScalaRuleTest {
  val expectedException = ExpectedException.none()
 
  @Rule def expectedExceptionDef = expectedException
 
  @Test def test() {
    expectedException.expect(classOf[NumberFormatException])
    Integer.valueOf("foo")
  }
}

We still need the val expectedException, so that we can refer to it in test(), but we add expectedExceptionDef and move the @Rule annotation to there. And now the test passes.

Similarly, for @ClassRule, you can do the same, but Scala doesn’t have static variables/methods, so you’ll need to add it to the companion object:

object ScalaClassRuleTest {
  @ClassRule def externalResource = new ExternalResource() {
 protected override def before() = println("before")
 protected override def after() = println("after")
  }
}

class ScalaClassRuleTest {
  @Test def test() {
 System.out.println("a test")
  }
}

This change allows me to take my Java code and translate it (almost) directly into Scala, as a stepping stone to rewriting everything completely to use Scalatest or Specs2. Also, other Java programmers may find it useful :-)

12 January 2012

Generating bytecode with errors in Scala


Following this blog post from James Iry: Type errors as warnings, and as it's something I've been thinking about recently applying to the Scala-IDE (Eclipse plugin for Scala),
I thought I'd stick my oar in as well.

The Eclipse JDT compiler creates the .class file even when there are errors in the source code. For instance:
public class Foo {
public void error() {
int foo = "bar";
}

public void ok() {
int foo = 4;
}
}
If you take the above java as an example, the javac compiler would not generate the .class file, but the Eclipse JDT compiler does.
It produces the equivalent of the following (using JAD):
public class Foo {
public Foo() {}

public void error() {
throw new Error("Unresolved compilation problem: \n" +
"\tType mismatch: cannot convert from String to int\n");
}

public void ok() {
int foo = 4;
}
}
Note that this still shows up as a compilation error in Eclipse, in the Problems view. Note also that calling the error() method will never work.

So should we apply the same trick to Scala? Yes.

But why is this useful? When you're refactoring, or just plain developing, you don't have to fix all of your errors at once.
If you're doing a spike, you can do a single change, make sure it works by running the unit tests (for that class), and then move on to the next implementation.
You can experiment. You can see if a solution works.

Note that I am not suggesting relaxing any type constraints, or any constraints of any kind.
Note also that I've never seen problems come from this feature of Eclipse either; actually, I've never worked on a project where the Eclipse
compiler was used for producing production code. It's always been javac.
I'm also not suggesting that this be available with the normal scalac compiler.
I think it should only be available from the Eclipse IDE, to stop leakage into production code.