Home > Groovy, java > From JUnit3 to Easyb

From JUnit3 to Easyb

Luty 26th, 2010

In this post I would like to show you a simple class and its tests and what was the evolution since JUnit3 to easyb.

I won't show it in the TDD or BDD way. We will start with the simple class and then focus on our tests. TDD and BDD are separate subjects that you should consider but today we want to focus purely on code, not the ideology.

Let's start with our class. It represents some money amount. Please remember that in real life it's not generally a good idea to use double for storing money due to precision problems.

Update: The source code below has the equals and hashCode methods removed. The valid implementation of equals is necessary here. Otherwise the tests will always fail.

public class Money {
  private double amount;
  private String currency;
 
  public Money(double amount, String currency) {
    this.amount = amount;
    this.currency = currency;
  }
 
  public Money add(Money element) {
    if (!currency.equals(element.currency)) {
      throw new IllegalArgumentException("Currencies don't match");
    }
    return new Money(amount + element.amount, currency);
  }
 
  public double getAmount() {
    return amount;
  }
 
  public String getCurrency() {
    return currency;
  }
 
  // removed equals and hashCode for clarity
}
 

Now, how would the test look like in JUnit3? Do you rember the need of extending the TestCase and keeping the naming conventions?

public class OldSchoolMoneyTest extends TestCase {
  public void testAdd() {
    Money money1 = new Money(12, "CHF");
    Money money2 = new Money(14, "CHF");
    Money expected = new Money(26, "CHF");
    Money result = money1.add(money2);
    assertTrue(expected.equals(result));
    Money money3 = new Money(15, "EURO");
    try {
      money1.add(money3);
      fail("You shouldn't add two different currencies");
    } catch(IllegalArgumentException e) {
      assertTrue(true);
    }
  }
}
 

Is there anything extreamly wrong with the test? Not really. It checks the amounts after adding and it checks for the exception in case of different currencies. Still it would be nice to make the code clearer.

Let's have a look at the test using JUnit4 (it's Java 5 world, we have annotations now). Code gets a little bit cleaner and some expectations are expressed in more declarative way.

public class MoneyTest {
  @Test(expected = IllegalArgumentException.class)
  public void add() {
    Money money1 = new Money(12, "CHF");
    Money money2 = new Money(14, "CHF");
    Money expected = new Money(26, "CHF");
    Money result = money1.add(money2);
    Assert.assertTrue(expected.equals(result));
    Money money3 = new Money(15, "EURO");
    money1.add(money3);
  }
}
 

We don't need the try/catch block anymore to express the expectation on the exception. Also the naming convetions don't have to be kept anymore.

So what about making the code a little bit more business readable?

public class BDDMoneyTest {
  @Test
  public void shouldAddAmountsCorrectly() {
    // GIVEN two money amounts with the same currencies
    Money money1 = new Money(12, "CHF");
    Money money2 = new Money(14, "CHF");
    Money expected = new Money(26, "CHF");
 
    // WHEN they are added
    Money result = money1.add(money2);
 
    // THEN the new amount should be a correct sum
    Assert.assertTrue(expected.equals(result));
  }
 
  @Test(expected = IllegalArgumentException.class)
  public void shouldFailForDifferentCurrencies() {
    // GIVEN two money amounts with different currencies
    Money money1 = new Money(12, "CHF");
    Money money2 = new Money(14, "EURI");
 
    // WHEN they are added
    Money result = money1.add(money2);
 
    // THEN the operation should fail
  }
}
 

We introduced a BDD style template for our test. Have a look at the tests again. Don't you think they are much more readable and expressive? The name of the method describes a scenario ("should add amounts correctly"). Then the comments build our scenario:

  1. // GIVEN two money amounts with the same currencies
  2. // WHEN they are added
  3. // THEN the new amount should be a correct sum

If you start writing your test with just the comments above you will now how to implements the test (and your class):

  1. Prepare two Money instances
  2. Call the add operation
  3. A new instance should be returned which is a sum of two given instances

One of the adavantages of this template is that the initial comments can be written together with business people and also verified by them.

So what does easyb bring in? A one step further towards readability of the tests. Actually we should abandon the word "test" now as easyb uses the terms "story" and "specification". Instead of our test with template we can now write a story:

scenario "Two amounts with the same currencies are added", {
  given "Two different amounts with the same currencies", {
    money1 = new Money(12, "CHF")
    money2 = new Money(14, "CHF")
    expected = new Money(26, "CHF")
  }
  when "Add given amounts" , {
    result = money1.add(money2)
  }
  then "New amount is sum of two given ones", {
    result.equals(expected).shouldBe true
  }
}
 
scenario "Two amounts with different currencies are added", {
  given "Two amounts with different currencies", {
    money1 = new Money(12, "CHF")
    money2 = new Money(14, "EURO")
  }
  when "Add given amounts", {
    add = {
      money1.add(money2)
    }
  }
  then "Operation should fail", {
    ensureThrows(IllegalArgumentException) {
      add()
    }
  }
}
 

At the begining you can just start with this:

scenario "Two amounts with the same currencies are added", {
  given "Two different amounts with the same currencies"
  when "Add given amounts"
  then "New amount is sum of two given ones"
}
 
scenario "Two amounts with different currencies are added", {
  given "Two amounts with different currencies"
  when "Add given amounts"
  then "Operation should fail"
}
 

Easyb will just mark this scenarios as pending (meaning not fully implemented).

What's the benefit? The easyb way of defining the stories is very expressive and human readable (we geeks are not exactly humans, are we?). You can sit with business people and write the stories. You don't need to do it in text doc files or anything else, you can write them directly as easyb stories.

This is just a simple approach. The bigger picture is BDD (Behavior Driven Development). Please see the references below for more reading.

I hope that this simple example will make you eager to get to know easyb a little. If not have a look at this easyb video:

References:

Groovy, java

  1. Luty 27th, 2010 at 19:23 | #1

    You missed „equals” method in Money class. Without it, test „Assert.assertTrue(expected.equals(result));” will always fail, as it is comparing different instances.

  2. Luty 27th, 2010 at 19:25 | #2

    There is a comment in source code that is has been removed for clarity. I will update the post with a note that is it necessary.

  3. Marzec 1st, 2010 at 21:23 | #3

    Great post! I always have to explain to folks that easyb doesn’t offer dramatically different testing capabilities over JUnit version.x — if you are already unit testing, easyb doesn’t change that. Yes, there are some slight syntax changes, but they aren’t the interesting part. What easyb brings to the party is a bit of a semantic shift — your source code for the tests become a bit more self-documenting, and the easyb HTML reports really shine when you put them in front of the end users. Nice work!

  4. kara
    Marzec 23rd, 2010 at 01:52 | #4

    Very nice BDD post. I know this is not the point of the post but the mixing 2 scenarios in single method is usually not a good idea (especially when using @Test(expected=…)) as it can lead to false green. See 2nd JUnit code version (MoneyTest) where exception in 1st scenario is failure but it will be handled as successful test. Sorry for nitpicking. ;-)

  5. Marzec 23rd, 2010 at 08:13 | #5

    Yor’re absolutelly right. We may know it but an unexperienced user may take it as an example. Thank you for pointing this out.

  6. Marzec 26th, 2010 at 16:26 | #6

    There is still an error in the code:

    then „New amount is sum of two given ones”, {
    result.equals(expected)
    }

    This never fails, because it is not an assertion. It should be:

    then „New amount is sum of two given ones”, {
    result.equals(expected).shouldBe true
    }

  7. Marzec 27th, 2010 at 13:23 | #7

    Thanks Jonas for spotting the problem. It was due to some copy & paste changes.

  8. Marzec 28th, 2010 at 18:47 | #8

    I like the introductory note about easyb. Gonna use it in my next testing endeavor.

    It seems I’m not the only one enjoying the post. Congrats for short yet enough for starters blog entry. Thanks Mateusz.

  1. No trackbacks yet.