ISubmitBlogPosts - a nice twist on Hungarian Notation for Interfaces...

I've been pairing with Andy Palmer over this last week. I have to say it's been a lot of fun... and I think we've learned a lot from the experience. One of the things I learned from Andy this week was an innovative use of Hungarian notation for interfaces... Andy told me about Udi Dahan's presentation on intentions and interfaces (PDF)

Basically, Udi suggests an innovative usage of Hungarian Notation...

Hungarian Notation is when you give a prefix that indicates the type of something... for example, strName to indicate it is a string... this is rarely used now. Hungarian Notation, however, is used in C# where interfaces are prefixed with the letter "I". For example, "ISerializable" instead of just "Serializable".

Many argue against this... however... Udi suggests an innovative twist... Using the "I" prefix. So, instead of ISerializable you would call the interface ISerializeObjects. I really, really like this approach!!

IWasPairingWithAndy

Andy and I were writing a Web Browser Facade in Java that abstracts Actions performed by automated acceptance tests from Selenium RC. We'd started to use Udi's approach... for example, we had a IPerformAnAction interface implemented by classes like LoginAction and an IHaveAnXPath interface for an enum that returns the XPath locator for an element... for example LoginPage.USER_NAME_FIELD.getXPath().

IHaveAProblemToSolve

These tests need to work with AJAX applications so waiting for pages to load doesn't work... You have to wait for a condition to be true... Under the hood of this Web Browser Facade is Selenium... In Selenium there is a waitForCondition() method. We didn't want to expose this and wanted to make it easy to add new conditions... specifically, we didn't want to have to write javascript for every condition...

Unfortunately, we got to discuss the ideas but didn't get to finish it on Friday... so I had a bash at sketching out the idea on Saturday morning... So, what we want to be able to do in our Actions (that implement IPerformAnAction) is write:

    browser.waitUntil(CustomerSearch.LAST_NAME_FIELD, isPresent());

INeedSomeCode

So, we need a new method in our browser...

public interface IBrowseTheWeb
{
	//browser's existing methods...

	void waitUntil (IHaveAnXpath element, ICheckAnElement condition);
	
}

And we already have:

public interface IEvaluateACondition
{
	boolean isSatisfied(IHaveAnXPath element);
}

public interface IHaveAnXPath 
{
	String getXPath();
}

IHaveAnXPath

So, looking at our IHaveAnXPath.... let's say the application has a 'Last Name' field on the customer search view... We would have an enum that implements IHaveAnXPath for CustomerSearch that has LAST_NAME_FIELD("//some/xpath"); that returns this xpath when you call CustomerSearch.LAST_NAME_FIELD.getXPath();

ICheckAnElement

So, for our conditions we now need something like:

public interface ICheckAnElement
{
	ICheckAnElement checking(IHaveAnXPath element);
	IEvaluateACondition usingThis(IBrowseTheWeb browser);
}

This is so that the browser's waitUntil(IHaveAnXpath element, ICheckAnElement condition) method can call the condition as follows...

IBrowseTheWeb browser=this;
if(condition.checking(element).usingThis(browser).isSatisfied()) {
    return;
}

That is of course wrapped in a loop.

Oh, let's not forget the method isPresent(), well that's a static method on our implementation of the condition (used as a static import earlier)...

IWantToKnowHowToImplementThis

So, how does this work? Well, we need an ElementIsPresentCondition that has a static method isPresent() that is a factory method returning an ICheckAnElement.

We also need an instance method checking() that adds data to the object, returning ICheckAnElement...

And we need to tell our condition that it is usingThis(browser), returning an IEvaluateACondition so that we can see if the condition is satisfied...

public class ElementIsPresentCondition implements IEvaluateACondition, ICheckAnElement
{
	private IHaveAnXPath elementToCheck;
	private IBrowseTheWeb browser;
	
	private ElementIsPresentCondition()
	{//cannot be constructed externally
	}
	
	public static ICheckAnElement isPresent() 
	{
		return new ElementIsPresentCondition ();
	}
	
	@Override
	public ICheckAnElement checking(IHaveAnXPath elementLocator) 
	{
		elementToCheck = elementLocator;
		return this;
	}
	
	@Override
	public IEvaluateACondition usingThis(IBrowseTheWeb browser) 
	{
		assert(elementToCheck!=null);
		this.browser=browser;
		return this;
	}
	
	@Override
	public boolean isSatisfied() 
	{
		return browser.getXPathCountFor(elementToCheck) > 0;
	}
} 

This can be applied to checking to see if an element has some other property... such as text...

browser.waitUntil(Application.ACTIVE_USER,contains("Antony Marcano"));

The method contains() is a static method on a ElementContainsTextCondition...

public class ElementContainsTextCondition implements IEvaluateACondition,ICheckAnElement
{
	private String expectedText;
	private IHaveAnXPath elementToCheck;
	private IBrowseTheWeb browser;
	
	private ElementContainsTextCondition (String expectedText) 
	{
		this.expectedText=expectedText;
	}

	public static ICheckAnElement contains(String expectedText) 
	{
		return new ElementContainsTextCondition(expectedText);
	}
	
	@Override
	public ICheckAnElement checking(IHaveAnXPath element) 
	{
		elementToCheck = element;
		return this;
	}
	
	@Override
	public IEvaluateACondition usingThis(IBrowseTheWeb browser) 
	{
		assert(elementToCheck!=null);
		this.browser=browser;
		return this;
	}
	
	
	@Override
	public boolean isSatisfied() 
	{		
		if (browser.getXPathCountFor(elementToCheck)>0) 
		{
			String actualText = browser.getTextFor(elementToCheck);
			
			return actualText.equals(expectedText);
		}
		throw new ElementNotPresentException("Failed to locate :" + nameOf(elementToCheck));
	
	}
}
 

This still works the same from within waitUntil():

if(condition.checking(element).usingThis(browser).isSatisfied()) {
    return;
}

ILikeThisStyleOfNamingInterfaces

I love the way this all reads... Of course some inspiration for some aspects of the general coding style I've used has been inspired by Hamcrest (although I'm not using Hamcrest itself) but I really like how Udi's interface naming approach enriches the readability of the code...

Ok, so the section headings in this blog post might be a bit much (but I thought that would make the post a little more fun)... but I've found it makes my code so much more expressive... I love that... So my thanks go to Andy for telling me about Udi's approach!!

I mean... c'mon... check out this declaration!!

public static ICheckAnElement contains(String expectedText){...}

I love it!

Anyways.... all this is just sketching it out in an experimental sort of way... a spike if you will... let's see how it turns out when Andy and I have to implement for real... and then hand this over to our mutual client...

Oh and one last thing...

IWantToRemoveDuplication

Spotted the duplication?? Sure... checking() and usingThis(browser) could be pulled up into a AbstractBrowserCondition but I'm not doing that now... time to go enjoy the sunny weather on a Sunday afternoon :-)

Comments

That's pretty funny Anthony. I do like it but I haven't decided yet whether that's because of the novelty or because I think it has value.

I'll play with a little before I make up my mind.

I hear you Kevin... it felt funny for me when we first started using it but that quickly passed... it just felt right... and it made the whole experience that much more fun :-)

Hope you do give it a try.

Antony Marcano

Antony, we read and discussed this post today at a reading group we hold over lunch hour where I work. The idea was new to almost everyone, so we had a good talk about it.

IBrowseTheWeb seemed like an especially well chosen role. One of the other readers told a familiar story. At the beginning of a project you might only test on Firefox. As the project grows, eventually it becomes necessary to test on other browsers. Depending how the code is structured, introducing the other browser could involve significant refactoring. Using IBrowseTheWeb as a role right from the beginning should lead to code where the browser would be easy to change.

IHaveAnXPath on the other hand, left most people feeling flat. After thinking about it, I think I understand why. IBrowseTheWeb and ICheckAnElement both describe what the role must do. IHaveAnXPath though, seems to describe how it will do it.

I can sort of picture this conversation happening:

  • Alright, we need to test the site. Everybody pitch in where you can. What can you do to help?
  • I can check elements to make sure they are correct, once we have them.
  • Don't worry, you'll have elements, because I can browse the web.
  • I have lots of XPath!
  • ...
  • What are we going to do with XPath?

A more natural conversation flow might be:

  • Alright, we need to test the site. Everybody pitch in where you can. What can you do to help?
  • I can check elements to make sure they are correct, once we have them.
  • Don't worry, you'll have elements, because I can browse the web.
  • The web is huge! You'll never find the elements without me, because I know where elements are located.
  • Sounds like a plan.

IKnowWhereElementsAreLocated is a bit long. A few shorter alternatives of I can think of are IKnowLocations or IHaveALocator. With a bit of code rearranging, ILocateElements might make sense, although the role would have changed slightly.

Describing it by the role seems a bit more thought provoking too. Instead of sticking with XPath, these objects that know locations might have css, id, name or even javascript based selectors. They might even be able to cooperate with the other roles to overcome some problems. For example, XPath is brutally slow in IE6, so perhaps an IKnowLocations implementation could return XPath for Firefox, but css for IE6.

I do like the example. Selenium testing is totally familiar, and always seems to need a bit of design effort to prevent it from turning in to wide, flat classes.