Follow by Email

Saturday, November 24, 2007

Thanksgiving

1. In advance of this Thanksgiving, I borrowed the Wii of my friends, Nick and Jerry. It took two days to earn our scar.


2. On Thanksgiving Day Beth and I served 17 guests. It should be no surprise that I thought about my mother for several days leading up to the holiday. I wanted to make some speech about Thanksgiving, either something of historical interest or perhaps a reminder of our blessings. I wrote two different speeches, but in the end I didn't use either and spoke off the cuff. I'm very happy with the result. I don't recall the exact words, but this is the part I want to share:

Before we start I'd like you to take a moment and reflect upon the blessings you have in your life. Most of you already know that my family suffered a terrible loss this year. It's important to use a day like today to remember that even when you have lost someone close, the life you had with them, while gone, is still a blessing today.

3. Making soup is one of my favorite things to do, and today I did it! After dinner, I made broth from the carcass, and today, I made turkey and rice soup. Turkey rice soup is something my mother used to make and while I don't have her recipe, I was close enough for my satisfaction. I used basmati rice instead of traditional long-grained rice. Since I like substantial soups, I added extra carrots and celery, though it turns out, I could have used less rice and more vegetables. I kept my spices to a minimum: salt, pepper, white pepper, and a little celery salt. Recalling a recent Thai meal, I also sliced fresh jalepeƱo and added it on the side.

I hope everyone enjoyed their Thanksgiving holiday.


Update: My wife says the soup makes the house smell good.

Tuesday, November 06, 2007

Code spacing and Blogger

I'm really getting frustrated with Blogger's inability to keep spacing when I provide well-indented code. It makes me want to not supply code samples. It makes me want to not post.

How do people successfully post code samples to Blogger? I could use some help. I don't feel any particular loyalty to Blogger, but making such a switch is always a pain.

Since I work for Google, I feel compelled to say: these are my own opinions and not those of my employer.

TestNG and Expected Exceptions

I've started reading Next Generation Java Testing which was just released last week. So far it's interesting enough. I've seen multiple presentations on TestNG, and read Cedric Beust's blog posts that describe the impetus for a new Java software testing approach, so the first chapter hasn't told me much I hadn't already known.

If this sounds like a lackluster introduction, I apologize. It's wonderfully written and informative so far.

Still, I've come across something that bothers me. Consider this example from the book:
public class BookingTest {
  private Plane plane;

  @BeforeMethod
  public void init() {
    plane = createPlane();
  }

  @Test(expectedExceptions = PlaneFullException.class)
  public void shouldThrowIfPlaneIsFull() {
    plane.bookAllSeats();
    plane.bookPlane(createValidItinerary(), null);
  }
}
(One Very Nice Thing about TestNG is that it's easier to read than JUnit, even if you don't know either testing API.)

My specific concern is the notion of expected exceptions in TestNG. The book explains that the expectedExceptions annotation is a reasonable replacement for the try/fail/catch pattern you find in JUnit:
public void testShouldThrowIfPlaneIsFull() {
  plane.bookAllSeats();
  try {
    plane.bookPlane(createValidItinerary(), null);
    fail("Exception expected");
  } catch(PlaneFullException expected) {
    // do nothing; exception expected.
  }
}
(Who puts semicolons in comments? I only put them in example comments. That makes them exemplary comments.)

I agree that the TestNG example is still easier to read than the JUnit example, but the problem is they're not effectively testing the same thing. Here's a more correct translation of the TestNG test using JUnit:
public void testShouldThrowIfPlaneIsFull() {
  try {
    plane.bookAllSeats();
    plane.bookPlane(createValidItinerary(), null);
    fail("Exception expected");
   } catch(PlaneFullException expected) {
     // do nothing; exception expected.
   }
 }
Did you catch the difference? The difference is in how exceptions thrown from plane.bookAllSeats() re handled. In the first JUnit example, if plane.bookAllSeats() throws any exception, the test will fail. In both the TestNG example and the second JUnit example, if plane.bookAllSeats() throws PlaneFullException, the test will still pass.

This bothered me all day. Let's say the engineer was very careful when he constructed this TestNG test. He would have known knew that plane.bookAllSeats() did not throw a PlaneFullException, so he could accept this mild semantic difference for the sake of readability. If, some time later, plane.bookAllSeats() is refactored to throw a PlaneFullException, and in addition, is accidentally broken, the TestNG test will falsely pass, and my JUnit test will correctly fail.

Here we have our problem: the expectedExceptions annotation attribute can't be used on the statement level. If I could annotate on the statement level, I could have this:
@Test
public void shouldThrowIfPlaneIsFull() {
  plane.bookAllSeats();
  @ExceptionExpected(class = PlaneFullException.class) {
    plane.bookPlane(createValidItinerary(), null);
  }
}
Semantically, you can't put annotations on the statement level, it doesn't exist in Java (though I have seen JLS proposals for this.)

Of course, I could simply use the try/fail/catch pattern in TestNG, but that's not The TestNG Way. (I want to say it's not "TestEnGee-Eee" or "TestNGish", but they both sound stupid.)

Let's analyze this test a little further. The first statement in the test is actually just more setup before calling plane.bookPlane(). I could move the set-up code to a specialized method annotated with @BeforeMethod, but given that that kind of set-up operations specialized for each test, readability reduces as the number of @BeforeMethod methods grows. Besides, it's nice to read each specific test as a sequence of statements.

Finally I realized an easy way to make this work:
public class BookingTest {
   private Plane plane;
   private boolean setupComplete;

   @BeforeMethod
   public void init() {
     plane = createPlane();
   }

  private void setupComplete() {
    this.setupComplete = true;
  }

  @AfterMethod
  public void verify() {
    if (!setupComplete) {
      throw new RuntimeException(
        "exception thrown before running critical test statement");
    }
  }

  @Test(expectedExceptions = PlaneFullException.class)
    public void shouldThrowIfPlaneIsFull() {
      plane.bookAllSeats();
      setupComplete();
      plane.bookPlane(createValidItinerary(), null);
    }
  }
In this case, setupComplete() operates as a gate. Now your test doesn't just pass if a PlaneFullException is thrown, you have to get past the setupComplete() method to be considered a successful test.

This solution works, but I don't quite love it. What about using Closures?
@Test
public void shouldThrowIfPlaneIsFull() {
  plane.bookAllSeats();
  expectException(PlaneFullException.class) {
    plane.bookPlane(createValidItinerary(), null);
  }
}

This looks both correct and concise. I love it.

Post-script

1. The authors are sensible enough to point out that there are plenty of times when using the expectedExceptions annotation attribute should be avoided, and they cover those cases. I just don't think I like expectedExceptions at all.

2. Would adding closures to the Java language obviate the need for statement-level annotations? It does here.

3. Those of you paying close attention are screaming that I missed something. Who said createValidItinerary() doesn't throw PlaneFullException?
@Test
public void shouldThrowIfPlaneIsFull() {
  plane.bookAllSeats();

  Itinerary itinerary = createValidItinerary();
  expectException(PlaneFullException.class) {
    plane.bookPlane(itinerary, null);
  }
}
4. This is yet another good argument for writing specialized exceptions for your use cases. It's somewhat unlikely that plane.bookAllSeats() and createValidItinerary() would throw PlaneFullException, but consider the difficulties if a Lazy Programmer designed the Plane API and used ArrayIndexOutOfBoundsException everywhere instead of PlaneFullException. Separating exeptions in a test method are just the beginning of that guy's troubles.

Friday, November 02, 2007

Run DMC Alert!

Run DMC on Reading Rainbow.

Oh how LeVar Burton make me laugh.

Why isn't this funny?

About two years ago I wrote a very small blog called "Just One Monkey". In it there were five posts that contained snippets from a monkey experiment. I like the idea, it's kind of cute, but it's not *funny*. I'm willing to admit the premise is bad; I'm also willing to admit it just doesn't work. The problem is that I can't tell why it's not funny.

The blog remains untouched these past two years, save the title. "Just One Monkey" doesn't seem to work, so I renamed it "Notes from a Monkey."

I have a large, loyal reader base*, so I ask you, large and loyal readers, help me understand: why doesn't this joke work?

http://notesfromamonkey.blogspot.com/

Please provide feedback in the comments section.

* In fact, my large loyal reader base comes from the future, where, for instance, someone from high school will accidentally stumble on this blog and be driven to read about my life in 2007. Thank you, dear old high school friend.

Thursday, November 01, 2007