Saturday, 9 February 2008

What do I mean by a "Scripting DSL"?

I have had a couple of e-mail questions recently, from people who have read the Concordion documentation, asking for more detail about what I mean by a DSL (Domain-Specific Language) for scripting, as shown in this diagram:


I think it's easiest to explain with a realistic implementation of a fixture:


public String getGreetingFor(String firstName) {
User user = new UserBuilder()
.withReasonableDefaults()
.withFirstName(firstName)
.build();
BrowserSession session = new BrowserSession();
new UserAdminAction(session).setUp(user);
new LoginDriver(session).login(user);
return new HomePageDriver(session).scrapeGreeting()
}

In this case, the DSL is made up of the Builder, Action and Driver classes. These implement and hide all the dirty work of setting up the user and navigating around the website.

The Driver classes extend an AbstractDriver class. The abstract base class provides protected methods for clicking on links, scraping the contents of elements, typing text, selecting radio buttons etc.

public abstract class AbstractDriver {

private BrowserSession session;

public AbstractDriver(BrowserSession session) {
this.setSession(session);
}

protected HtmlPage getHtmlPage() {
return session.getHtmlPage();
}

protected HtmlElement getElement(String id) {
return getHtmlPage().getHtmlElementById(id);
}

protected String scrapeText(String id) {
return getElement(id).asText();
}

protected void click(String id) {
((ClickableElement) getElement(id)).click();
}

// etc.

}

The subclasses use these methods, but do not reveal them publicly. The public interface is at a higher level of abstraction.

public class HomePageDriver extends AbstractDriver {

public HomePageDriver(BrowserSession session) {
super(session);
}

public String scrapeGreeting() {
return scrapeText(HomePage.GREETING_ID);
}

public void clickSearch() {
click(HomePage.SEARCH_LINK_ID);
}

// etc.
}

Actions encapsulate a series of driver clicks to perform some higher-level action and remove duplication across tests. For example, behind the scenes the UserAdminAction may use a LoginDriver, UserAdminPageDriver, UserDetailsPageDriver, LogoutDriver. Actions may be nested.

You should also read Nat Pryce's series of blog posts on Test Data Builders.