Home > Test Driven Development, Wicket > Writing EVEN MORE readable and maintainable tests

Writing EVEN MORE readable and maintainable tests

If you follow my blog you know that few weeks ago I posted an article about writing readable and maintainable tests. My understanding of readable and maintainable tests evolves in time, my views and opinions change and  thus my posts need update.

I thought that the easiest way to show you the changes will be an example. As before, examples are shown for Wicket application, however the rules can be applied to any other frontend frameworks (JSF applications can use for instance JSFUnit project). Secondly, if you haven’t read my article about readable and maintainable tests, I encourage you to do so, since this post is simply update of the previous one. Furthermore post about WicketTestBase should be helpful as well, since all examples inherit from WicketTestBase class.

But lets move on to our example. Imagine we are told to implement user story “Add new Applicant”.  General steps are as follows:

  1. User enters welcome page
  2. User clicks link called ‘create new applicant’
  3. User is given a form that he fills with Applicant data (name, surname and age)
  4. Users is presented with Applicants list, where newly added Applicant is visible
  5. Applicant is added to the Application he is applying to

Before I explain you all the  necessary details, please let me show you how would the final tests for this scenario look like. I want you to see them now, just to let you know how readable tests can really be. For the purpose of this  example I will only show two tests, however it should be obvious that in real life overall number of tests can be much bigger. The tests look as follow:

test #1  – Should show ApplicantsList after adding applicant

public void shouldShowApplicantsListAfterAddingApplicant
                  throws Exception {
 // given
 user.enters().welcomePage();
 user.clicks().createNewApplicationLink();

 // when
 user.fills().addApplicantForm("Adam", "Bauer", 27);
 user.clicks().saveAndExitButton();

 // then
 onpage.applicantsList.exists().isVisible().and().isOfType(ListView.class);
}

test #2 – Should show added applicant informations

public void shouldShowAddedApplicantInformations
                  throws Exception {
 // given
 user.enters().welcomePage();
 user.clicks().createNewApplicationLink();

 // when
 user.fills().addApplicantForm("Adam", "Bauer", 27);
 user.clicks().saveAndExitButton();

 // then
 onpage.applicantsList
            .containsElements(application.getApplicants());
 onpage.applicantsList
            .element(0, "appliocantName").isLabelWithValue("Adam");
 onpage.applicantsList
            .element(0, "appliocantSurname").isLabelWithValue("Bauer");
 onpage.applicantsList
            .element(0, "appliocantAge").isLabelWithValue("27");
}

Readable isn’t it? The tests themselves can be read as a normal sentences. They should give you this feeling that you are reading a book. This is what self-documenting code is all about. When you read the test, it is quite clear what the implementation should do. Test are understandable, test base is comprehensive. And you know what? It will still be, even after one year. Or maybe you are soon planning to hire new developer? Not a problem at all, he will easily understand them as well. That is what all the readability is all about!
Notice that in fact when reading those tests you are not even aware of the fact that they are testing Wicket application. Remember that tests should always represent the requirements! If those test fail and fixing them is not a matter of change in one or two lines of code, then it only means the requirements changed. However if the button gets different name or ApplicantsList suddenly gets different path, then fixing those tests is simply change in one line of code. This is what all the maintainability is all about!

The question remains then, what changes were applied. If you were asked to write similar tests and you would follow my previous guidelines, you would probably end up with something like this:

test #1 – Should show ApplicantsList after adding applicant

public void shouldShowApplicantsListAfterAddingApplicant
                  throws Exception {
 // given
 enterer.entersWelcomePage();
 clicker.clickCreateNewApplicationLink();

 // when
 formFiller.fillsAddApplicantForm("Adam", "Bauer", 27);
 clicker.clicksSaveAndExitButton();

 // then
 String pathToApplicantsList = "path:to:applicantslist";
 tester.getComponentFromLastRenderedPage(pathToApplicantsList);
 tester.assertVisible(pathToApplicantsList);
 tester.assertComponent(pathToApplicantsList, ListView.class);
}

test #2 – Should show added applicant informations

public void shouldShowAddedApplicantInformations
                  throws Exception {
 // given
 enterer.entersWelcomePage();
 clicker.clickCreateNewApplicationLink();

 // when
 formFiller.fillsAddApplicantForm("Adam", "Bauer", 27);
 clicker.clicksSaveAndExitButton();

 // then
 String pathToApplicantsList = "path:to:applicantslist";
 tester.assertListView(pathToApplicantsList, application.getApplicants());
 tester.assertLabel(pathToApplicantsList + "applicantName", "Adam");
 tester.assertLabel(pathToApplicantsList + "applicantSurname", "Bauer");
 tester.assertLabel(pathToApplicantsList + "applicantAge", "27");
}

It’s good already, but it has its flaws. First lets look at helper classes, they great at aggregating common actions (entering pages, clicking and form filling), but when we read the test

 (...)
 // given
 enterer.entersWelcomePage();
 clicker.clickCreateNewApplicationLink();

 // when
 formFiller.fillsAddApplicantForm("Adam", "Bauer", 27);
 clicker.clicksSaveAndExitButton();
(...)

it doesn’t sound right. Try to read above code out loud. If you do that, you will quickly learn that there is something missing. Lets try it, read this out loud:

“Enterer enters welcome page”
“Clicker clicks create new application link”
“FormFiller fills add applicant form”

What is wrong with it? Why doesn’t it sound right? Well we basically miss person who is responsible for all the clicking, entering and form filling. We miss here the User! Whenever I read test, I want to hear sentences like this:

User enters welcome page”
User clicks create new application link”
User fills add applicant form”

I mean who is this enterer, what is this clicker, were did this formFiller came from? It is User who does the whole work. Clicker, Enterer and FormFiller are just representations of the actions he does. Taking that fact into consideration I created another helper class called Userer.

public class Userer {

  private Clicker clicker;
  private Enterer enterer;
  private FormFiller formFiller;

  public Userer(Clicker clicker,
                Enterer enterer,
                FormFiller formFiller) {
      this.clicker = clicker;
      this.enterer = enterer;
      this.formFiller = formFiller;
  }

  public Clicker clicks() {
      return clicker;
  }

  public FormFiller fills() {
      return formFiller;
  }

  public Enterer enters() {
      return enterer;
  }
}

This class simply aggregates Clicker, FormFiller and Enterer. Userer becomes field in WicketTestBase (base class for all my Wicket tests) as shown below:

public class WicketTestBase {

    (...)
    protected Clicker clicker;
    protected Enterer enterer;
    protected FormFiller formFiller;

    protected Userer user;
    (...)

    private void initHelpers() {
        clicker = new Clicker(tester, enhancedTester);
        formFiller = new FormFiller(tester, enhancedTester);
        enterer = new Enterer(tester, enhancedTester, clicker, formFiller);
        user = new Userer(clicker, enterer, formFiller);
    }
    (...)

From now on you can use this ‘user’ field in your tests. Now we can refactor our test:

  1. calls to clicker become calls to user.clicks() e.g clicker.clicksSaveLink becomes user.clicks().saveLink() (notice we also changed clickSaveLink method name to saveLink)
  2. calls to formFiller become calls to user.fills() e.g formFiller.fillsAddApplicantForm(“Adam”, “Bauer”, 27) becomes user.fills().addApplicantForm(“Adam”, “Bauer”, 27)
  3. calls to enterer become calls to user.eneters() e.g enterer.entersWelcomePage() becomes users.enters.welcomePage()

Now our previous test will look like this:

test #1 – Should show ApplicantsList after adding applicant

public void shouldShowApplicantsListAfterAddingApplicant
                  throws Exception {
 // given
 user.enters().welcomePage();
 user.clicks().createNewApplicationLink();

 // when
 user.fills().addApplicantForm("Adam", "Bauer", 27);
 user.clicks().saveAndExitButton();

 // then
 String pathToApplicantsList = "path:to:applicantslist";
 tester.getComponentFromLastRenderedPage(pathToApplicantsList);
 tester.assertVisible(pathToApplicantsList);
 tester.assertComponent(pathToApplicantsList, ListView.class);
}

test #2 – Should show added applicant informations

public void shouldShowAddedApplicantInformations
                  throws Exception {
 // given
 user.enters().welcomePage();
 user.clicks().createNewApplicationLink();

 // when
 user.fills().addApplicantForm("Adam", "Bauer", 27);
 user.clicks().saveAndExitButton();

 // then
 String pathToApplicantsList = "path:to:applicantslist";
 tester.assertListView(pathToApplicantsList, application.getApplicants());
 tester.assertLabel(pathToApplicantsList + "applicantName", "Adam");
 tester.assertLabel(pathToApplicantsList + "applicantSurname", "Bauer");
 tester.assertLabel(pathToApplicantsList + "applicantAge", "27");
}

Pretty readable right? All looks good, but imagine what will happen, when path to ApplicationList change or it will become PropertyListView (instead of ListView)? Some time ago I realized that every action of the test scenarios falls into one of four categories. User either enters application, clicks a link or button, fills form with data or assert state of the application. For entering, filling and clicking I created helper classes that encapsulate those actions (Enterer, FormFiller and Clicker), however assertion still did not get its representation. Thus I created another helper class called Weberer.

public class Weber {

    // components
    public PageElement applicantsList;

    // testers
    private WicketTester tester;
    private EnhancedWicketTester enhancedWicketTester;

    public Weber(WicketTester tester,
                         EnhancedWicketTester enhancedWicketTester) {
        this.tester = tester;
        this.enhancedWicketTester = enhancedWicketTester;

        createPageElements(tester, enhancedWicketTester);
    }

    private void createPageElements(WicketTester tester,
                                    EnhancedWicketTester enhancedWicketTester) {
        applicantsList = new PageElement("applicationForm:panel:listApplicantsPanel:applicants",
                                         tester, enhancedWicketTester);
    }
}

This class represents informations presented on page and helps to perform various assertion actions on those objects. Weberer holds reference to public fields of type PageElement.

public class PageElement implements IPageElement {

    private final WicketTester tester;
    private final EnhancedWicketTester enhancedWicketTester;
    private final String path;

    public PageElement(String path, WicketTester tester, EnhancedWicketTester enhancedWicketTester) {
        this.tester = tester;
        this.enhancedWicketTester = enhancedWicketTester;
        this.path = path;
    }
    public PageElement exists() {
        tester.getComponentFromLastRenderedPage(path);
        return this;
    }

    public PageElement isVisible() {
        tester.assertVisible(path);
        return this;
    }

    (....)

Those fields represent different elements that can be found on page. So for example if suddenly you need to make some assertions on login form, then you will add to Weberer object new field:

public class Weber {

    // components
    public PageElement loginForm;
    (...)

    private void createPageElements(WicketTester tester,
                                                         EnhancedWicketTester enhancedWicketTester) {
        loginForm = new PageElement("path:to:login:form",
                                                            tester, enhancedWicketTester);
        (...)
    }

Since Weberer is also a filed in WicketTestBase class:

public class WicketTestBase {

    (...)
    protected Userer user;
    protected Weber onpage;
    (...)

you can access your newly created element directly from your tests:

   onpage.loginForm.exists();
   onpage.loginForm.isVisible();

When we apply those rules into our test, they will final look like this:

test #1 – Should show ApplicantsList after adding applicant

public void shouldShowApplicantsListAfterAddingApplicant
                  throws Exception {
 // given
 user.enters().welcomePage();
 user.clicks().createNewApplicationLink();

 // when
 user.fills().addApplicantForm("Adam", "Bauer", 27);
 user.clicks().saveAndExitButton();

 // then
 onpage.applicantsList.exists().isVisible().and().isOfType(ListView.class);
}

test #2 – Should show added applicant informations

public void shouldShowAddedApplicantInformations
                  throws Exception {
 // given
 user.enters().welcomePage();
 user.clicks().createNewApplicationLink();

 // when
 user.fills().addApplicantForm("Adam", "Bauer", 27);
 user.clicks().saveAndExitButton();

 // then
 onpage.applicantsList
            .containsElements(application.getApplicants());
 onpage.applicantsList
            .element(0, "appliocantName").isLabelWithValue("Adam");
 onpage.applicantsList
            .element(0, "appliocantSurname").isLabelWithValue("Bauer");
 onpage.applicantsList
            .element(0, "appliocantAge").isLabelWithValue("27");
}

The test code is now easy to read and easy to maintain. All the Wicket specific code is hidden behind those helper classes. This not only helps to maintain test in a short time (when for example path to some components change) but also in a long run (when for example API of the WicketTester change in the 1.5 version). It will be lot easier to apply those changes only to 5 helper classes, then to base of few hundred tests already implemented in your application.

All code presented here will be soon (in a matter of days) added to WicketCool project, so you will be able to see WicketTestBase in action.

  1. Rogério Liesenfeld
    August 26, 2009 at 12:40 pm

    I don’t find code like the following readable:

    user.clicks().createNewApplicationLink(); 
    

    On the contrary, it’s quite confusing. When I see “user” I think of a domain object, which I don’t expect to have methods like “enters()” or “clicks()”. The code may read like plain english, but it makes no sense from a conventional application design perspective.
    If I see a method like “enters()” I can’t help but
    ask myself: “what can this possibly do?!?”; and that’s not “readable”, if it raises questions and doubts instead of being clear.

    Also, the line of code above is written at too low a level of abstraction. It should instead be something like:

    user.requestsNewApplication();
    
  2. Jakub Holý
    August 28, 2009 at 2:01 pm

    A very nice post, thanks.

    For me “user.clicks().createNewApplicationLink();” reads quite well as I read is as a tester and not as a developer 🙂

    Regards, Jakub

  1. No trackbacks yet.

Leave a reply to Rogério Liesenfeld Cancel reply