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