A theory about testable web UI II: JSUnit

Feb 14, 2009 07:48

Last time, I talked about the hypothetical allure of small, fast, reliable, parallelizable unit tests, and embarking on something of a research project to get a feel for what it's like to test-drive an HTML interface.

The first thing to do is pick a project, I have bad product sense, no imagination, and an unhealthy fixation on games, so I'm going to go with a character details widget for some hypothetical RPG. That means that our requirements lie somewhere along these lines:
  • I want to see my character's picture, name, and statistics
  • I want to be able to pick some equipment for my character
  • I want to see how my character's equipment affects its statistics
Second, here are my rules:
  • I am not going to go into details with respect to the backend at all. That is a subject that warrants a completely different discussion on its own.
  • I am only going to worry about FireFox 3. Making web applications that run on all major browsers is important, but I am primarily interested in learning how to test drive a web application that works, not learning how to write a compatible web application.
  • Full disclosure: I do not have much background in HTML/CSS/JS, so if you see me do something that looks stupid, it's probably because I'm doing something stupid.
The first thing I always look for when coding in an unfamiliar language is an xUnit. Fortunately, there is a such a thing as JsUnit, so I didn't have to go long without having something to install and set up. There was some weirdness surrounding Firefox's security restrictions around JavaScript running from the local machine: I hacked around it just by setting up an Apache server on my workstation.

Since we are talking about a user interface here, I'm going to start with some markup:

andy


HP:
5

Kickboxing:
6

Linear Algebra:
7

Cross-stitching:
8

One thing I do know is that node IDs have to be unique across the whole page, so I'm going to take a swing at avoiding them for things that do not really, really need to be uniquely identified. We'll see how that pans out.

I haven't even written any JS yet, but I'm staring at that blob of crap that I just dumped into my own lap, and I'm already worried that anything I code will be riddled with bugs caused by me fudging up the DOM. The first tests I want to write will simply be testing that I can affect the display properly:

... that thing I pasted up above ...

Now that I have a blob of HTML as a piece of data to hang my StatusWidget on, and a red test, all I have to do is make it pass:

StatusWidget = function(id) {
this.id = id;
};

StatusWidget.prototype = {
getHitPoints : function() {
var xpath = "//div[@id='" + this.id + "']//*[@class='hit_point_score']";
var i = document.evaluate(
xpath, document, null, XPathResult.ANY_TYPE, null
);
var e = i.iterateNext();
return parseInt(e.innerHTML);
}
};
Green bar means victory! Now I follow through and write tests to ensure that the rest all work. Normally, this might be considered redundant and pointless, but I don't expect my markup to stay as-is forever, and I just know I'll want to know when (not if) I go and screw the basics up.

function testCanFetchStats() {
var sw = new StatusWidget("avatar-1");

assertEquals("andy", sw.getName());
assertEquals(5, sw.getHitPoints());
assertEquals(6, sw.getKickboxingScore());
assertEquals(7, sw.getLinearAlgebraScore());
assertEquals(8, sw.getCrossStitchingScore());
}
And the adjusted implementation:

StatusWidget.prototype = {
__getChild : function(className) {
var xpath = "//div[@id='" + this.id + "']//*[@class='" + className + "']";
var i = document.evaluate(
xpath, document, null, XPathResult.ANY_TYPE, null
);
return i.iterateNext();
},

__getInt : function(className) {
return parseInt(this.__getChild(className).innerHTML);
},

getName : function() {
return this.__getChild("name").innerHTML;
},

getHitPoints : function() {
return this.__getInt("hit_point_score");
},

getKickboxingScore : function() {
return this.__getInt("kickboxing_score");
},

// etc
}
It works, so I'm pretty confident that I've reached some sort of baseline level of functionality. Next time, I will try pulling the statistics themselves from a web service. (and mocking!)

Source code

code

Previous post Next post
Up