Lessons learned on writing acceptance tests

Yesterday I was working with Jacopo on finishing up the acceptance tests for a story. The story was something like “as a user, I want to see a page with a list of the email alerts I subscribed to.” The test looked like this:


@Test
public void shouldShowAlerts() throws Exception {
    Date today = new Date();
    String vodafone6wayKey = "...";
    alertEngineNewsBroker.store(new IndividualCompanyRule(...));
    
    WebResponse response = getResponse("/xxx/alerts/all.html");
    
    WebTable details = response.getTableWithID("details");
    assertEquals("Type", details.getCellAsText(0, 0));
    assertEquals("Name", details.getCellAsText(0, 2));
    assertEquals("Details", details.getCellAsText(0, 3));
    
    assertEquals("News", details.getCellAsText(1, 0)); //type
    assertTrue(isSameDay(today, dateFrom(details.getCellAsText(1, 1))));
    assertEquals("VODAFONE GRP.", details.getCellAsText(1, 2));
    assertEquals("All news stories", details.getCellAsText(1, 3));
}    

This acceptance test was going to be implemented with an end-to-end test running through the http interface of the web application. But I also saw that my collegue had this table sketched on his copybook.

Sketch of AT Table

The sketch was written during a conversation with the analyst, who in this project acts as the customer role. It represents the text labels that the analyst expects to see in the various cases.

It was going to be difficult to extend the above test to cover all the cases. Also, the shape of the test was wildly different from the concepts that the sketch table makes so clear. We thought about how we would set up this acceptance test in Fitnesse, which we are not using in this project. From there we got the inspiration to write the test like this:


@Test
public void shouldShowAlertNames() throws Exception {
    assertNameIs("VODAFONE GRP.", new IndividualCompanyRule(...));
    assertNameIs("ALL", new AllCompaniesRule(...));
    assertNameIs("FTSE 100", new IndexRule(...));
    assertNameIs("General Financial", new SectorRule(...));
    assertNameIs("DELL INC", new NotListedCompanyRule(...));
    assertNameIs("Nome di watchlist", new WatchlistRule(...));
    assertNameIs("Nome di portfolio", new PortfolioRule(...));
}

Now the test, while still containing a lot of technical clutter, has roughly the same shape of the sketch. All that remains is to implement the custom assertion, assertNameIs. One option was to call the code from the first test; but going through http and html seems a useless detour, when all we care about is the text displayed in different cases. Why not hook directly into the business logic that produces the label? This way the test is much more focused; and fast too.

We had unit tests already for that business logic; we agreed to have a bit of duplication in the tests, because the way the AT was written was more communicative in the eyes of the customer.

Jacopo was not happy, though, that we were ditching the end-to-end test completely. This way there was no test that shows that our page shows up at all. We agreed that we would keep one end-to-end test: no need to test all possible cases, since that is already covered in the previous test.

Lessons learned:

  • When the business logic is complex enough that you must write down details and lists of cases, it’s a good idea to write a test explicitly for testing the business logic.
  • Acceptance tests work better when they are clear and uncluttered, focused on the aspect of the story that we want to demonstrate to the customer. Fitnesse-style tables and custom assertions are useful tools here.
  • End-to-end integration tests can take an awful lot of time to write. Shore and Warden suggest to avoid them, relying instead on what they call focused integration tests, that is, tests that check the interaction with a single external system, rather than a complete tour from GUI to DB and back. Shore says that it’s more productive to test the UI interactively (which you have to do anyway) and uncover with exploratory tests any holes that your focused integration tests don’t cover. Then you refine your focused integration tests. Koskela says that using end-to-end test, or going divide-and-conquer with focused integration tests, is a judgment call: “it depends”.
  • Always time-box your activities. If you expected to spend, say, 4 pomodori on implementing ATs, and after that time you are still far from done, don’t just plow ahead and take as much time as it will ever take. Stop and reflect, ask for help, and come up with a simpler way. Do ask for help; and start over, trying to get the same value in a simpler way. See this from Francesco Cirillo: Quando la coppia scoppia (in Italian).

One Response to “Lessons learned on writing acceptance tests”

  1. Guard test, my brand new friend « Software Engineering Slave Says:

    […] too. you decide to focus on the business rule and isolate from real subsystems. [Matteo wrote a great essay on this, telling a story from one of our current customers. […]

Leave a Reply