Skip navigation.

JNarrate & ScreenPlay (formerly known as WebUser) Framework... what do you think?

acceptance test tools | agile | test driven development

Update: The 'new' WebUser's name has been changed to ScreenPlay4j. All rererences to the new WebUser below should be taken as 'User'.

At the first Agile Alliance Functional Test Tools workshop, I briefly showed a spike I'd done that illustrated how straight forward it would be to write acceptance tests in code. It was somewhat inspired by work already done on things like JBehave and RSpec. However, while they moved further away from code and towards plain text, I went in the opposite direction.

The reason I went in the opposite direction is because I'm not sure how much is saved by going down the plain text path. I figured if I can make the code as readable as possible then 'customers' or 'product owners' wouldn't be completely lost. It would also encourage developers to collaborate with the product owner because the product owner wouldn't be able to write the acceptance tests on their own so would have to work with the developer. This would require a conversation as they write the tests.

I also had to make it easy... and fast and simple and easy to maintain.

Last December I sat down and wrote JNarrate.

JNarrate

I played around with all sorts of ways of writing the tests and actually wrote two completely different versions of the API and tried it out on a few people. Non-technical friends gave me feedback and suggestions. One of those was my 10 year old son. I also bounced the APIs off several programmers and testers and spent some time refactoring it with the help of my buddy Andy Palmer. The result was an API that allowed you to write acceptance tests that look like this:

public class EditAPageStory extends AbstractExampleStory {
	/*
	 * As a Web User
	 * I want to be able to change the content of the wiki page
	 * So that I can communicate my own information to other readers
	 */
	
	@Test public void shouldBeAbleToEditAPage() {
		Given.thatThe(wiki).wasAbleTo(beAtThe(PointWhereItHasBeen.JUST_INSTALLED));
		And.thatThe(user).wasAbleTo(navigateToTheHomePage());

		When.the(user).attemptsTo(changeTheContent().to("Welcome to Acceptance Test Driven Development")); 

		Then.the(textOnTheScreen().ofThe(user)).shouldBe("Welcome to Acceptance Test Driven Development");
	}
}

You might be looking at the code sample above and be thinking that looks hard to use... well, let's see...

Shall we focus on the line:

		When.the(user).attemptsTo(changeTheContent().to("Welcome to Acceptance Test Driven Development")); 

See this video: JNarrate - When the user attempts to change the content

If you're wondering where the "user" variable appeared from - he is described in the AbstractExampleStory:

	protected ActorPlayingThe user= ActorPlayingThe.roleInTheCharacterOf(new WebUser());

Then, the only thing I had to implement was the specific activity that the user was performing... which is to changeTheContent(). This is actually a static import from a class called "ChangeTheContent" with a static creation method called (you guessed it) :

public class ChangeTheContent extends AbstractActionForAWebUser {

	private String newContent;

	public void perform() {
		webUser().clickOn(Options.Menu.EDIT_BUTTON);
		webUser().clearContentsOf(Editor.Page.CONTENT_PANE);
		webUser().type(newContent, into(Editor.Page.CONTENT_PANE));
		webUser().clickOn(Editor.Page.SAVE_BUTTON);
	}

	public static ChangeTheContent changeTheContent() {
		return new ChangeTheContent();
	}

	public PerformableAsA to(String newContent) {
		this.newContent = newContent;
		return this;
	}
}

At the time of writing this, the steps that explain how to change the content use a very simple abstraction called a WebUser. That WebUser was something I threw together quickly but wasn't what I really wanted it to look like. That WebUser contains a number of methods like "clickOn" and "type". In this case these methods delegate to a WebDriver instance behind the scenes. The reason for that is to make the code that explains the specific steps of changing the content in the wiki as readable as possible.

WebUser

Some time ago, Andy and I were having a conversation about the problems associated with this approach. We love the compactness of WebDriver but this WebUser class quickly ruins that by having a long list of methods. In fact it is 98 lines long!! That is way too big as far as we are concerned. That inspired me to think of a better way of doing this... It occurred to me that the same Action pattern as in JNarrate would also work for a more compact WebUser. This fit nicely with another idea I had to generate user documentation for an application from the acceptance tests - or more importantly in this case - from the JNarrate Actions.

With the new WebUser the above perform method would look like this:

	public void perform() {
		you.need(To.doTheFollowing(
			Click.onThe(OptionsMenu.EDIT_BUTTON),
			ClearTheContent.OfThe(Editor.CONTENT_PANEL),
			Type.theText(newContent).intoThe(Editor.CONTENT_PANEL),
			Click.onThe(Editor.SAVE_BUTTON)
			));
	}

Note: the web user is created in the parent class:

  WebBrowserWithA<WebDriver> browser = new WebBrowserWithA<WebDriver>(driver);
  WebUserUsingA<WebDriver> you = new WebUserUsingA<WebDriver>(browser);

Each step is an interaction with a static method that creates an instance of that interaction. These interactions are put into a sequence of steps and the WebUser (you) executes them in order. This means that the WebUser can be a very compact class and can be completely generic - not really caring whether you use WebDriver or any other browser automation framework:

package org.jnarrate.webuser;

import org.jnarrate.webuser.interaction.To;

public class WebUserUsingA<DRIVER> {

  private final WebBrowserWithA<DRIVER> browser;
    
  public WebUserUsingA(WebBrowserWithA<DRIVER> browser) {
    this.browser = browser;
  }

  public void needTo(INeedABrowserWithA<DRIVER> interaction) {
      need(To.doTheFollowing(interaction));
  }
  
  public void need(To<DRIVER> doTheseThings) {
    for (INeedABrowserWithA<DRIVER> doThis : doTheseThings) {
      doThis.usingThis(browser).now();
    }
  }
}

This class will now never bloat, even as new capabilities are released in your chosen framework. If the WebUser class had a method for each interaction you'd have to add a new method in the WebUser. With the approach I've taken, you just add a new interaction class. Or, if you want to extend the WebUser's capabilities yourself, you don't need to extend and override - you just plug in your own interactions. Here is an example of one that uses WebDriver:

package org.jnarrate.webuser.interaction.webdriver;

public class Click extends WebDriverInteraction {

  private final XPath location;

  public void now() {
    atThis(location).click();
  }

  public static Click onThe(XPath location) {
    return new Click(location);
  }

  private Click(XPath location) {
    this.location = location;
  }
}

The common aspects of a WebDriver Interaction are in the abstract class:

public abstract class WebDriverInteraction implements INeedABrowserWithA, Interaction {

  protected WebDriver browser;
  
  public Interaction usingThis(WebBrowserWithA<WebDriver> browser) {
    this.browser = browser.driver();
    return this;
  }
  
  protected WebElement atThis(XPath location) {
    return browser.findElement(By.xpath(location.toString()));
  }
}

My plan is this... to fully support WebDriver for the first release of this and then encourage others to implement support for other frameworks.

What does it do? How do I use it?

This question is really about the the software that you are building. The acceptance tests specify what the system should do and make sure that it does it. With the help of JNarrate and WebUser we can also start to document (once, and once only) how the users should use it....

Perhaps you can already see how the examples above can be used to generate user documentation? Let's say we are developing a wiki and we have the following JNarrate action (or activity) as used by your acceptance test (see above):

public class ChangeTheContent extends AbstractActionForAWebUser {

	private String newContent;

	public void perform() {
		you.need(To.doTheFollowing(
			Click.onThe(OptionsMenu.EDIT_BUTTON),
			ClearTheContent.OfThe(Editor.CONTENT_PANEL),
			Type.theText(newContent).intoThe(Editor.CONTENT_PANEL),
			Click.onThe(Editor.SAVE_BUTTON)
			));
	}

	public static ChangeTheContent changeTheContent() {
		return new ChangeTheContent();
	}

	public PerformableAsA<WebUser> to(String newContent) {
		this.newContent = newContent;
		return this;
	}
}

It shouldn't be a huge mental leap to see that it wouldn't be hard to get from the content of the above perform() method to:

To Change The Content you need to do the following:

  • Click on the Options Menu Edit Button
  • Clear the content of the Editor Content Panel
  • Type the new content into the Editor Content Panel
  • Click on the Editor Save Button

So, the acceptance tests specify what the system can do. The actions (or activities), like ChangeTheContent, describe how to use it. To do that in a way that reduces the waste of having to re-document the same information in the user guide, we need WebUser.

Hopefully you can see where I'm going with this... but this is just the beginning. I'll be working with Andy Palmer, who has already contributed to this, to develop this further so you should see something you can use very soon... and knowing how we work together it's only going to get better.

This is still just the beginning of my vision of the future, so watch this space for more interesting developments.

You can find the code for the above amongst the testingReflections mercurial repositories

Seems so GUI-focused?

I'm a fan of natural language and have no problems with this framework. Am I wrong to think this is for driving the GUI only?

Maybe because I'm currently working with an app which is designed in a way that makes it hard to have any test layer in between unit and GUI, I feel like we have plenty of good GUI test tools already. I'd like to see something that helps teams to ATDD at a functional, behind-the-GUI level, because my experience is that a tool such as FitNesse works better than a GUI tool for that purpose.

And I guess FitNesse is fine, I just wish it had an IDE with capabilities similar to what C# and Java programmers get with their IDEs. Auto complete, search/replace, refactoring, ... Might be naive of me.
-- Lisa

Comment viewing options

Select your preferred way to display the comments and click 'Save settings' to activate your changes.