Tag: kranthi

Form-Field Validation: The Errors-Only Approach


  

Error pages for form-field validation are dreadful. You’ve just filled out 20 form fields, yet you get the same bloated page thrown back in your face because a single field failed to validate.

I clearly recall the often loud sighs of despair during our last usability study each time a test subject encountered a validation error page.

We also noticed that test subjects who had been exposed to validation errors began to take preventive actions to avoid them in subsequent steps, by writing things such as “N/A� in the “Company name� field if in doubt about whether the field was optional.

Form Field Validation Error Page at BlueNile.com
When getting the exact same page but with an error message, the user will feel they have made little or no progress, despite having typed 90% of the form fields correctly. (Image: Blue Nile)

Some of the frustration with validation error pages likely stems from the user being returned to the same page they came from. Being returned to the exact same page is problematic for a couple of reasons:

  1. With all form fields still displayed (valid or not), the user might have difficulty identifying the few erroneous fields among the many valid ones.
  2. More critically, seeing the same page twice makes it seem like the user has made no progress, despite having just filled in numerous form fields correctly.

At Baymard Institute, we reflected on this problem and got an idea that we call “error fields onlyâ€� — which is exactly what this article is about. Before exploring this idea, let’s look at three traditional types of validation techniques: “same page reload,â€� “optimized same page reloadâ€� and “live inline validation.â€�

1. The Traditional Way: Same Page Reload

Here’s a typical validation error page from Staples’ checkout process:

Staples validation error
The current error page for Staples’ checkout process. Besides having a subpar indication of errors, Staples also breaks a handful of checkout usability guidelines.

When the user first submits the page, the entire page is reloaded, but with indications of validation errors. A message at the top of the page tells the user they have made an error and describes what the error is; further down the page, the label for the erroneous field is in bold and red.

This is significantly better than the sad practice some websites adopt of only highlighting the erroneous field in red or bold (without any description) and letting the user guess what went wrong. But the implementation could be much more thorough. Let’s look at how Staples’ page could be improved.

2. Same Page Reload: Optimized

To have a fairer baseline for comparison, we’ve made three changes to substantially improve Staples’ error page:

Mock-up of an optimized Staples validation error page. Click for full size.
A simple mockup of an optimized version of Staples’ error page. Notice the anchor link at the top and the tailored description near the erroneous fields.

The three changes are:

  1. The error description at the top indicates the number of errors (if there’s more than one) and lists them.
  2. These listed errors are links that take the user directly to the corresponding field (especially important in long forms).
  3. A tailored message for each erroneous field shows either an example of correctly formatted data (for example, john@example.com) or a tip on what might be wrong with the data (for example, “Looks like the ending in the email address you provided is missing (.com, .org, etc.),â€� instead of just “Email wrong — please correct.â€�

Now, in addition to being able to locate the erroneous fields and spot multiple errors more easily, the user actually has guidance on how to correct their data. Some input errors are plain cases of mistyping or obvious details being forgotten, which most users will spot immediately; but if the user lacks clues and can’t instantly see why the data is invalid and has to guess in order to proceed, then they will likely abandon the process.

While better, this second implementation (and the first) still result in a poor experience. The user still gets the whole page with all 31 form fields thrown back at them, despite having inputted 90% of the fields correctly. The signal-to-noise ratio is still high (two errors among all valid fields). The user will likely scroll up and down the form to make sure all errors have been fixed and, finally, scroll down to click that “Continue� button once again. This diminishes the user’s sense of accomplishment and makes their effort to resolve the errors unnecessarily cumbersome.

3. Live Inline Validation

A very effective technique that resolves some of the issues with the last method is “live inline validation.�

Twitter use Live Inline Validation at their sign-up page. Image credit: Twitter.com

Here, each form field is validated separately as the user types. The error handling is most often instant, with the user being told that their data doesn’t match the expected format (although the user can scroll past and try to submit the form anyway). Luke Wroblewski has done some excellent usability research on the inline validation techniques that work best.

Inline validation alleviates the aforementioned issues by indicating progress and by pointing out the erroneous fields (since the page does not reload). This makes the technique useful for forms in which the fields can be validated independently. In other cases, the data isn’t as simple as a user name, password and email address; sometimes the data needing validation is an array or a set of data. In the realm of e-commerce, one might need an address or credit card to be validated.

To live validate a credit card, you could perform a Luhn check to verify the format of the number, and you could verify the expiration date and security code (or “card verification valueâ€�) for the correct number and type of characters. However, the validation could still fail if the data doesn’t all match up when the payment vendor tries to authorize the card or if the card is declined. With live inline validation, the user would be first presented with a green checkmark as they input data in each field, and then they would see an error message after submitting the form if any of the fields didn’t check out. Alternatively, live inline validation could be disabled for just those fields for which the data has to be checked remotely. However, this has the drawback of an inconsistent UI, whereby some fields are validated live while others aren’t.

For address validators, the format of the inputted data could be correct, but the address itself could still fail validation (for example, if the address doesn’t exist). Again, live inline validation would begin here with checkmarks indicating to the user that the inputted data is correct, but then, when the user submits the address form, the website would (confusingly) change its mind and tell the user that it doesn’t recognize the address after all.

Our suggested approach, the fourth and last validation technique, tackles these problems.

4. Error Fields Only Approach

As we’ve seen, there are different ways to display error messages, each with its own strengths and weaknesses. Based on these observations, we thought of a validation technique better suited to complex data. What if we removed all validated fields on the error page that reloads? What if we displayed only those fields that failed validation? So, instead of reloading the entire page and showing all 20 fields of the form when only the “Phone� and “Email� fields have errors, you would simply show a page with those two fields and the corresponding messages.

With this approach, the picture is quite different. The user now gets a new page, or an overlay, with just a couple of error fields. A summary of the validated data would also be displayed, along with an “Edit� link in case the user spots something they want to correct. Staples’ error page would then look something like this:

Mock-up of Error Fields Only approach
A simple mockup of what Staples’ error page would look like with this fourth approach. Only erroneous fields would be shown, and all validated data would be summarized below with an “Edit� link.

This approach makes the error page much more digestible than the traditional technique, and it makes abundantly clear which fields are the problem, which is particularly helpful in long forms.

Now, the user simply has to fix the fields shown and hit “Continueâ€� — no scrolling, no having to pick out erroneous fields from valid ones, no repetition of the same page, just a simple page explaining exactly what to fix and how to proceed.

When To Use Each Validation Technique

Compared to the two traditional reloading techniques (i.e. 1 and 2), the “live inline validation� and “error fields only� techniques both offer the user a sense of progression and a clear distinction between erroneous and valid fields.

The “error fields only� approach is usually best when inline validation wouldn’t quite work. In April 2012, we benchmarked the top 100 e-commerce websites in the world and found that only 8% use live inline validation during checkout (likely due to having to validate both postal addresses and credit cards). In general, the longer the form and the more complex the inputted data and its dependencies, the more likely the error-fields-only approach is the best choice.

Inline validation is effective for simpler forms. When the data is an array or set, such as with postal addresses and credit cards, then the method becomes problematic. In this case, the UI would be illogical (the user would see each field validated individually and then suddenly fail collectively) or inconsistent (only some fields would validate as the user types). Of course, this technique would still require the page to be reloaded as a fallback, in case the user submits the form regardless of inline error messages (or if they have disabled JavaScript); therefore, the page reload techniques (the traditional and newer versions) might be best even for simple forms.

On smartphones, the error-fields-only approach has an advantage over the same-page-reload technique, because users typically lack an overview and context of the form due to the small screen. In such cases, displaying only the erroneous fields would help the user focus on the task at hand.

Rethinking Validation Error Pages

The error-field-only approach is merely a concept, and it needs both refinement and testing. An even better solution to these user experience problems most likely exists. Maybe having a traditional (although optimized) error page with green checkmarks next to the validated fields on the error page (to indicate the user’s progress) would be a better solution; or perhaps applying a slight fade to validated fields, making the erroneous ones stand out, while maintaining the context of the page.

The error-fields-only approach is more an attempt to inspire and a call to action to rethink how we handle validation errors and thus provide a better user experience.

While we can agree that validation pages aren’t the sexiest part of Web design, we should give them attention because their quality will determine whether the user comes to a screeching halt or feels a small bump on the road.

Got your own examples, mockups and ideas for validation errors? Share them in the comments!

(al)


© Christian Holst for Smashing Magazine, 2012.


Introduction To JavaScript Unit Testing


  

You probably know that testing is good, but the first hurdle to overcome when trying to write unit tests for client-side code is the lack of any actual units; JavaScript code is written for each page of a website or each module of an application and is closely intermixed with back-end logic and related HTML. In the worst case, the code is completely mixed with HTML, as inline events handlers.

This is likely the case when no JavaScript library for some DOM abstraction is being used; writing inline event handlers is much easier than using the DOM APIs to bind those events. More and more developers are picking up a library such as jQuery to handle the DOM abstraction, allowing them to move those inline events to distinct scripts, either on the same page or even in a separate JavaScript file. However, putting the code into separate files doesn’t mean that it is ready to be tested as a unit.

What is a unit anyway? In the best case, it is a pure function that you can deal with in some way — a function that always gives you the same result for a given input. This makes unit testing pretty easy, but most of the time you need to deal with side effects, which here means DOM manipulations. It’s still useful to figure out which units we can structure our code into and to build unit tests accordingly.

Building Unit Tests

With that in mind, we can obviously say that starting with unit testing is much easier when starting something from scratch. But that’s not what this article is about. This article is to help you with the harder problem: extracting existing code and testing the important parts, potentially uncovering and fixing bugs in the code.

The process of extracting code and putting it into a different form, without modifying its current behavior, is called refactoring. Refactoring is an excellent method of improving the code design of a program; and because any change could actually modify the behaviour of the program, it is safest to do when unit tests are in place.

This chicken-and-egg problem means that to add tests to existing code, you have to take the risk of breaking things. So, until you have solid coverage with unit tests, you need to continue manually testing to minimize that risk.

That should be enough theory for now. Let’s look at a practical example, testing some JavaScript code that is currently mixed in with and connected to a page. The code looks for links with title attributes, using those titles to display when something was posted, as a relative time value, like “5 days ago�:

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>Mangled date examples</title>
	<script>
	function prettyDate(time){
		var date = new Date(time || ""),
			diff = ((new Date().getTime() - date.getTime()) / 1000),
			day_diff = Math.floor(diff / 86400);

		if (isNaN(day_diff) || day_diff < 0 || day_diff >= 31) {
			return;
		}

		return day_diff == 0 && (
				diff < 60 && "just now" ||
				diff < 120 && "1 minute ago" ||
				diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
				diff < 7200 && "1 hour ago" ||
				diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
			day_diff == 1 && "Yesterday" ||
			day_diff < 7 && day_diff + " days ago" ||
			day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago";
	}
	window.onload = function(){
		var links = document.getElementsByTagName("a");
		for (var i = 0; i < links.length; i++) {
			if (links[i].title) {
				var date = prettyDate(links[i].title);
				if (date) {
					links[i].innerHTML = date;
				}
			}
		}
	};
	</script>
</head>
<body>

<ul>
<li class="entry" id="post57">
	<p>blah blah blah…</p>
	<small class="extra">
		Posted <a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z">January 28th, 2008</a>
		by <a href="/john/">John Resig</a>
	</small>
</li>
<!-- more list items -->
</ul>

</body>
</html>

If you ran that example, you’d see a problem: none of the dates get replaced. The code works, though. It loops through all anchors on the page and checks for a title property on each. If there is one, it passes it to the prettyDate function. If prettyDate returns a result, it updates the innerHTML of the link with the result.

Make Things Testable

The problem is that for any date older then 31 days, prettyDate just returns undefined (implicitly, with a single return statement), leaving the text of the anchor as is. So, to see what’s supposed to happen, we can hardcode a “current� date:

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>Mangled date examples</title>
	<script>
	function prettyDate(now, time){
		var date = new Date(time || ""),
			diff = (((new Date(now)).getTime() - date.getTime()) / 1000),
			day_diff = Math.floor(diff / 86400);

		if (isNaN(day_diff) || day_diff < 0 || day_diff >= 31) {
			return;
		}

		return day_diff == 0 && (
				diff < 60 && "just now" ||
				diff < 120 && "1 minute ago" ||
				diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
				diff < 7200 && "1 hour ago" ||
				diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
			day_diff == 1 && "Yesterday" ||
			day_diff < 7 && day_diff + " days ago" ||
			day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago";
	}
	window.onload = function(){
		var links = document.getElementsByTagName("a");
		for (var i = 0; i < links.length; i++) {
			if (links[i].title) {
				var date = prettyDate("2008-01-28T22:25:00Z", links[i].title);
				if (date) {
					links[i].innerHTML = date;
				}
			}
		}
	};
	</script>
</head>
<body>

<ul>
<li class="entry" id="post57">
	<p>blah blah blah…</p>
	<small class="extra">
		Posted <a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z">January 28th, 2008</a>
		by <a href="/john/">John Resig</a>
	</small>
</li>
<!-- more list items -->
</ul>

</body>
</html>

Now, the links should say “2 hours ago,� “Yesterday� and so on. That’s something, but still not an actual testable unit. So, without changing the code further, all we can do is try to test the resulting DOM changes. Even if that did work, any small change to the markup would likely break the test, resulting in a really bad cost-benefit ratio for a test like that.

Refactoring, Stage 0

Instead, let’s refactor the code just enough to have something that we can unit test.

We need to make two changes for this to happen: pass the current date to the prettyDate function as an argument, instead of having it just use new Date, and extract the function to a separate file so that we can include the code on a separate page for unit tests.

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>Refactored date examples</title>
	<script src="prettydate.js"></script>
	<script>
	window.onload = function() {
		var links = document.getElementsByTagName("a");
		for ( var i = 0; i < links.length; i++ ) {
			if (links[i].title) {
				var date = prettyDate("2008-01-28T22:25:00Z", links[i].title);
				if (date) {
					links[i].innerHTML = date;
				}
			}
		}
	};
	</script>
</head>
<body>

<ul>
<li class="entry" id="post57">
	<p>blah blah blah…</p>
	<small class="extra">
		Posted <a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z">January 28th, 2008</a>
		by <a href="/john/">John Resig</a>
	</small>
</li>
<!-- more list items -->
</ul>

</body>
</html>

 

Here’s the contents of prettydate.js:

function prettyDate(now, time){
	var date = new Date(time || ""),
		diff = (((new Date(now)).getTime() - date.getTime()) / 1000),
		day_diff = Math.floor(diff / 86400);

	if (isNaN(day_diff) || day_diff < 0 || day_diff >= 31) {
		return;
	}

	return day_diff == 0 && (
			diff < 60 && "just now" ||
			diff < 120 && "1 minute ago" ||
			diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
			diff < 7200 && "1 hour ago" ||
			diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
		day_diff == 1 && "Yesterday" ||
		day_diff < 7 && day_diff + " days ago" ||
		day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago";
}

Now that we have something to test, let’s write some actual unit tests:

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>Refactored date examples</title>
	<script src="prettydate.js"></script>
	<script>
	function test(then, expected) {
		results.total++;
		var result = prettyDate("2008-01-28T22:25:00Z", then);
		if (result !== expected) {
			results.bad++;
			console.log("Expected " + expected + ", but was " + result);
		}
	}
	var results = {
		total: 0,
		bad: 0
	};
	test("2008/01/28 22:24:30", "just now");
	test("2008/01/28 22:23:30", "1 minute ago");
	test("2008/01/28 21:23:30", "1 hour ago");
	test("2008/01/27 22:23:30", "Yesterday");
	test("2008/01/26 22:23:30", "2 days ago");
	test("2007/01/26 22:23:30", undefined);
	console.log("Of " + results.total + " tests, " + results.bad + " failed, "
		+ (results.total - results.bad) + " passed.");
	</script>
</head>
<body>

</body>
</html>
  • Run this example. (Make sure to enable a console such as Firebug or Chrome’s Web Inspector.)

This will create an ad-hoc testing framework, using only the console for output. It has no dependencies to the DOM at all, so you could just as well run it in a non-browser JavaScript environment, such as Node.js or Rhino, by extracting the code in the script tag to its own file.

If a test fails, it will output the expected and actual result for that test. In the end, it will output a test summary with the total, failed and passed number of tests.

If all tests have passed, like they should here, you would see the following in the console:

Of 6 tests, 0 failed, 6 passed.

To see what a failed assertion looks like, we can change something to break it:

Expected 2 day ago, but was 2 days ago.

Of 6 tests, 1 failed, 5 passed.

While this ad-hoc approach is interesting as a proof of concept (you really can write a test runner in just a few lines of code), it’s much more practical to use an existing unit testing framework that provides better output and more infrastructure for writing and organizing tests.

The QUnit JavaScript Test Suite

The choice of framework is mostly a matter of taste. For the rest of this article, we’ll use QUnit (pronounced “q-unit�), because its style of describing tests is close to that of our ad-hoc test framework.

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>Refactored date examples</title>

	<link rel="stylesheet" href="qunit.css" />
	<script src="qunit.js"></script>
	<script src="prettydate.js"></script>

	<script>
	test("prettydate basics", function() {
		var now = "2008/01/28 22:25:00";
		equal(prettyDate(now, "2008/01/28 22:24:30"), "just now");
		equal(prettyDate(now, "2008/01/28 22:23:30"), "1 minute ago");
		equal(prettyDate(now, "2008/01/28 21:23:30"), "1 hour ago");
		equal(prettyDate(now, "2008/01/27 22:23:30"), "Yesterday");
		equal(prettyDate(now, "2008/01/26 22:23:30"), "2 days ago");
		equal(prettyDate(now, "2007/01/26 22:23:30"), undefined);
	});
	</script>
</head>
<body>
	<div id="qunit"></div>
</body>
</html>

Three sections are worth a closer look here. Along with the usual HTML boilerplate, we have three included files: two files for QUnit (qunit.css and qunit.js) and the previous prettydate.js.

Then, there’s another script block with the actual tests. The test method is called once, passing a string as the first argument (naming the test) and passing a function as the second argument (which will run the actual code for this test). This code then defines the now variable, which gets reused below, then calls the equal method a few times with varying arguments. The equal method is one of several assertions that QUnit provides. The first argument is the result of a call to prettyDate, with the now variable as the first argument and a date string as the second. The second argument to equal is the expected result. If the two arguments to equal are the same value, then the assertion will pass; otherwise, it will fail.

Finally, in the body element is some QUnit-specific markup. These elements are optional. If present, QUnit will use them to output the test results.

The result is this:

With a failed test, the result would look something like this:

Because the test contains a failing assertion, QUnit doesn’t collapse the results for that test, and we can see immediately what went wrong. Along with the output of the expected and actual values, we get a diff between the two, which can be useful for comparing larger strings. Here, it’s pretty obvious what went wrong.

Refactoring, Stage 1

The assertions are currently somewhat incomplete because we aren’t yet testing the n weeks ago variant. Before adding it, we should consider refactoring the test code. Currently, we are calling prettyDate for each assertion and passing the now argument. We could easily refactor this into a custom assertion method:

test("prettydate basics", function() {
	function date(then, expected) {
		equal(prettyDate("2008/01/28 22:25:00", then), expected);
	}
	date("2008/01/28 22:24:30", "just now");
	date("2008/01/28 22:23:30", "1 minute ago");
	date("2008/01/28 21:23:30", "1 hour ago");
	date("2008/01/27 22:23:30", "Yesterday");
	date("2008/01/26 22:23:30", "2 days ago");
	date("2007/01/26 22:23:30", undefined);
});

Here we’ve extracted the call to prettyDate into the date function, inlining the now variable into the function. We end up with just the relevant data for each assertion, making it easier to read, while the underlying abstraction remains pretty obvious.

Testing The DOM manipulation

Now that the prettyDate function is tested well enough, let’s shift our focus back to the initial example. Along with the prettyDate function, it also selected some DOM elements and updated them, within the window load event handler. Applying the same principles as before, we should be able to refactor that code and test it. In addition, we’ll introduce a module for these two functions, to avoid cluttering the global namespace and to be able to give these individual functions more meaningful names.

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>Refactored date examples</title>
	<link rel="stylesheet" href="qunit.css" />
	<script src="qunit.js"></script>
	<script src="prettydate2.js"></script>
	<script>
	test("prettydate.format", function() {
		function date(then, expected) {
			equal(prettyDate.format("2008/01/28 22:25:00", then), expected);
		}
		date("2008/01/28 22:24:30", "just now");
		date("2008/01/28 22:23:30", "1 minute ago");
		date("2008/01/28 21:23:30", "1 hour ago");
		date("2008/01/27 22:23:30", "Yesterday");
		date("2008/01/26 22:23:30", "2 days ago");
		date("2007/01/26 22:23:30", undefined);
	});

	test("prettyDate.update", function() {
		var links = document.getElementById("qunit-fixture").getElementsByTagName("a");
		equal(links[0].innerHTML, "January 28th, 2008");
		equal(links[2].innerHTML, "January 27th, 2008");
		prettyDate.update("2008-01-28T22:25:00Z");
		equal(links[0].innerHTML, "2 hours ago");
		equal(links[2].innerHTML, "Yesterday");
	});

	test("prettyDate.update, one day later", function() {
		var links = document.getElementById("qunit-fixture").getElementsByTagName("a");
		equal(links[0].innerHTML, "January 28th, 2008");
		equal(links[2].innerHTML, "January 27th, 2008");
		prettyDate.update("2008-01-28T22:25:00Z");
		equal(links[0].innerHTML, "Yesterday");
		equal(links[2].innerHTML, "2 days ago");
	});
	</script>
</head>
<body>
	<div id="qunit"></div>
	<div id="qunit-fixture">
		<ul>
			<li class="entry" id="post57">
				<p>blah blah blah…</p>
				<small class="extra">
					Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z">January 28th, 2008</a></span>
					by <span class="author"><a href="/john/">John Resig</a></span>
				</small>
			</li>
			<li class="entry" id="post57">
				<p>blah blah blah…</p>
				<small class="extra">
					Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-27T22:24:17Z">January 27th, 2008</a></span>
					by <span class="author"><a href="/john/">John Resig</a></span>
				</small>
			</li>
		</ul>
	</div>
</body>
</html>

Here’s the contents of prettydate2.js:

var prettyDate = {
	format: function(now, time){
		var date = new Date(time || ""),
			diff = (((new Date(now)).getTime() - date.getTime()) / 1000),
			day_diff = Math.floor(diff / 86400);

		if (isNaN(day_diff) || day_diff < 0 || day_diff >= 31) {
			return;
		}

		return day_diff === 0 && (
				diff < 60 && "just now" ||
				diff < 120 && "1 minute ago" ||
				diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
				diff < 7200 && "1 hour ago" ||
				diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
			day_diff === 1 && "Yesterday" ||
			day_diff < 7 && day_diff + " days ago" ||
			day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago";
	},

	update: function(now) {
		var links = document.getElementsByTagName("a");
		for ( var i = 0; i < links.length; i++ ) {
			if (links[i].title) {
				var date = prettyDate.format(now, links[i].title);
				if (date) {
					links[i].innerHTML = date;
				}
			}
		}
	}
};

The new prettyDate.update function is an extract of the initial example, but with the now argument to pass through to prettyDate.format. The QUnit-based test for that function starts by selecting all a elements within the #qunit-fixture element. In the updated markup in the body element, the <div id="qunit-fixture">…</div> is new. It contains an extract of the markup from our initial example, enough to write useful tests against. By putting it in the #qunit-fixture element, we don’t have to worry about DOM changes from one test affecting other tests, because QUnit will automatically reset the markup after each test.

Let’s look at the first test for prettyDate.update. After selecting those anchors, two assertions verify that these have their initial text values. Afterwards, prettyDate.update is called, passing along a fixed date (the same as in previous tests). Afterwards, two more assertions are run, now verifying that the innerHTML property of these elements have the correctly formatted date, “2 hours ago� and “Yesterday.�

Refactoring, Stage 2

The next test, prettyDate.update, one day later, does nearly the same thing, except that it passes a different date to prettyDate.update and, therefore, expects different results for the two links. Let’s see if we can refactor these tests to remove the duplication.

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>Refactored date examples</title>
	<link rel="stylesheet" href="qunit.css" />
	<script src="qunit.js"></script>
	<script src="prettydate2.js"></script>
	<script>
	test("prettydate.format", function() {
		function date(then, expected) {
			equal(prettyDate.format("2008/01/28 22:25:00", then), expected);
		}
		date("2008/01/28 22:24:30", "just now");
		date("2008/01/28 22:23:30", "1 minute ago");
		date("2008/01/28 21:23:30", "1 hour ago");
		date("2008/01/27 22:23:30", "Yesterday");
		date("2008/01/26 22:23:30", "2 days ago");
		date("2007/01/26 22:23:30", undefined);
	});

	function domtest(name, now, first, second) {
		test(name, function() {
			var links = document.getElementById("qunit-fixture").getElementsByTagName("a");
			equal(links[0].innerHTML, "January 28th, 2008");
			equal(links[2].innerHTML, "January 27th, 2008");
			prettyDate.update(now);
			equal(links[0].innerHTML, first);
			equal(links[2].innerHTML, second);
		});
	}
	domtest("prettyDate.update", "2008-01-28T22:25:00Z:00", "2 hours ago", "Yesterday");
	domtest("prettyDate.update, one day later", "2008-01-29T22:25:00Z:00", "Yesterday", "2 days ago");
	</script>
</head>
<body>
	<div id="qunit"></div>
	<div id="qunit-fixture">
		<ul>
			<li class="entry" id="post57">
				<p>blah blah blah…</p>
				<small class="extra">
					Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z">January 28th, 2008</a></span>
					by <span class="author"><a href="/john/">John Resig</a></span>
				</small>
			</li>
			<li class="entry" id="post57">
				<p>blah blah blah…</p>
				<small class="extra">
					Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-27T22:24:17Z">January 27th, 2008</a></span>
					by <span class="author"><a href="/john/">John Resig</a></span>
				</small>
			</li>
		</ul>
	</div>
</body>
</html>

Here we have a new function called domtest, which encapsulates the logic of the two previous calls to test, introducing arguments for the test name, the date string and the two expected strings. It then gets called twice.

Back To The Start

With that in place, let’s go back to our initial example and see what that looks like now, after the refactoring.

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>Final date examples</title>
	<script src="prettydate2.js"></script>
	<script>
	window.onload = function() {
		prettyDate.update("2008-01-28T22:25:00Z");
	};
	</script>
</head>
<body>

<ul>
<li class="entry" id="post57">
	<p>blah blah blah…</p>
	<small class="extra">
		Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z"><span>January 28th, 2008</span></a></span>
		by <span class="author"><a href="/john/">John Resig</a></span>
	</small>
</li>
<li class="entry" id="post57">
	<p>blah blah blah…</p>
	<small class="extra">
		Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-27T22:24:17Z"><span>January 27th, 2008</span></a></span>
		by <span class="author"><a href="/john/">John Resig</a></span>
	</small>
</li>
<li class="entry" id="post57">
	<p>blah blah blah…</p>
	<small class="extra">
		Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-26T22:24:17Z"><span>January 26th, 2008</span></a></span>
		by <span class="author"><a href="/john/">John Resig</a></span>
	</small>
</li>
<li class="entry" id="post57">
	<p>blah blah blah…</p>
	<small class="extra">
		Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-25T22:24:17Z"><span>January 25th, 2008</span></a></span>
		by <span class="author"><a href="/john/">John Resig</a></span>
	</small>
</li>
<li class="entry" id="post57">
	<p>blah blah blah…</p>
	<small class="extra">
		Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-24T22:24:17Z"><span>January 24th, 2008</span></a></span>
		by <span class="author"><a href="/john/">John Resig</a></span>

	</small>
</li>
<li class="entry" id="post57">
	<p>blah blah blah…</p>
	<small class="extra">
		Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-14T22:24:17Z"><span>January 14th, 2008</span></a></span>
		by <span class="author"><a href="/john/">John Resig</a></span>
	</small>
</li>
<li class="entry" id="post57">
	<p>blah blah blah…</p>
	<small class="extra">
		Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-04T22:24:17Z"><span>January 4th, 2008</span></a></span>
		by <span class="author"><a href="/john/">John Resig</a></span>
	</small>
</li>
<li class="entry" id="post57">
	<p>blah blah blah…</p>
	<small class="extra">
		Posted <span class="time"><a href="/2008/01/blah/57/" title="2007-12-15T22:24:17Z"><span>December 15th, 2008</span></a></span>
		by <span class="author"><a href="/john/">John Resig</a></span>
	</small>
</li>
</ul>

</body>
</html>
  • Run this example.
  • For a non-static example, we’d remove the argument to prettyDate.update. All in all, the refactoring is a huge improvement over the first example. And thanks to the prettyDate module that we introduced, we can add even more functionality without clobbering the global namespace.

    Conclusion

    Testing JavaScript code is not just a matter of using some test runner and writing a few tests; it usually requires some heavy structural changes when applied to code that has been tested only manually before. We’ve walked through an example of how to change the code structure of an existing module to run some tests using an ad-hoc testing framework, then replacing that with a more full-featured framework to get useful visual results.

    QUnit itself has a lot more to offer, with specific support for testing asynchronous code such as timeouts, AJAX and events. Its visual test runner helps to debug code by making it easy to rerun specific tests and by providing stack traces for failed assertions and caught exceptions. For further reading, check out the QUnit Cookbook.

    (al) (km)


    © Joern Zaefferer for Smashing Magazine, 2012.


Introduction To JavaScript Unit Testing


  

You probably know that testing is good, but the first hurdle to overcome when trying to write unit tests for client-side code is the lack of any actual units; JavaScript code is written for each page of a website or each module of an application and is closely intermixed with back-end logic and related HTML. In the worst case, the code is completely mixed with HTML, as inline events handlers.

This is likely the case when no JavaScript library for some DOM abstraction is being used; writing inline event handlers is much easier than using the DOM APIs to bind those events. More and more developers are picking up a library such as jQuery to handle the DOM abstraction, allowing them to move those inline events to distinct scripts, either on the same page or even in a separate JavaScript file. However, putting the code into separate files doesn’t mean that it is ready to be tested as a unit.

What is a unit anyway? In the best case, it is a pure function that you can deal with in some way — a function that always gives you the same result for a given input. This makes unit testing pretty easy, but most of the time you need to deal with side effects, which here means DOM manipulations. It’s still useful to figure out which units we can structure our code into and to build unit tests accordingly.

Building Unit Tests

With that in mind, we can obviously say that starting with unit testing is much easier when starting something from scratch. But that’s not what this article is about. This article is to help you with the harder problem: extracting existing code and testing the important parts, potentially uncovering and fixing bugs in the code.

The process of extracting code and putting it into a different form, without modifying its current behavior, is called refactoring. Refactoring is an excellent method of improving the code design of a program; and because any change could actually modify the behaviour of the program, it is safest to do when unit tests are in place.

This chicken-and-egg problem means that to add tests to existing code, you have to take the risk of breaking things. So, until you have solid coverage with unit tests, you need to continue manually testing to minimize that risk.

That should be enough theory for now. Let’s look at a practical example, testing some JavaScript code that is currently mixed in with and connected to a page. The code looks for links with title attributes, using those titles to display when something was posted, as a relative time value, like “5 days ago�:

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>Mangled date examples</title>
	<script>
	function prettyDate(time){
		var date = new Date(time || ""),
			diff = ((new Date().getTime() - date.getTime()) / 1000),
			day_diff = Math.floor(diff / 86400);

		if (isNaN(day_diff) || day_diff < 0 || day_diff >= 31) {
			return;
		}

		return day_diff == 0 && (
				diff < 60 && "just now" ||
				diff < 120 && "1 minute ago" ||
				diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
				diff < 7200 && "1 hour ago" ||
				diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
			day_diff == 1 && "Yesterday" ||
			day_diff < 7 && day_diff + " days ago" ||
			day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago";
	}
	window.onload = function(){
		var links = document.getElementsByTagName("a");
		for (var i = 0; i < links.length; i++) {
			if (links[i].title) {
				var date = prettyDate(links[i].title);
				if (date) {
					links[i].innerHTML = date;
				}
			}
		}
	};
	</script>
</head>
<body>

<ul>
<li class="entry" id="post57">
	<p>blah blah blah…</p>
	<small class="extra">
		Posted <a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z">January 28th, 2008</a>
		by <a href="/john/">John Resig</a>
	</small>
</li>
<!-- more list items -->
</ul>

</body>
</html>

If you ran that example, you’d see a problem: none of the dates get replaced. The code works, though. It loops through all anchors on the page and checks for a title property on each. If there is one, it passes it to the prettyDate function. If prettyDate returns a result, it updates the innerHTML of the link with the result.

Make Things Testable

The problem is that for any date older then 31 days, prettyDate just returns undefined (implicitly, with a single return statement), leaving the text of the anchor as is. So, to see what’s supposed to happen, we can hardcode a “current� date:

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>Mangled date examples</title>
	<script>
	function prettyDate(now, time){
		var date = new Date(time || ""),
			diff = (((new Date(now)).getTime() - date.getTime()) / 1000),
			day_diff = Math.floor(diff / 86400);

		if (isNaN(day_diff) || day_diff < 0 || day_diff >= 31) {
			return;
		}

		return day_diff == 0 && (
				diff < 60 && "just now" ||
				diff < 120 && "1 minute ago" ||
				diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
				diff < 7200 && "1 hour ago" ||
				diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
			day_diff == 1 && "Yesterday" ||
			day_diff < 7 && day_diff + " days ago" ||
			day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago";
	}
	window.onload = function(){
		var links = document.getElementsByTagName("a");
		for (var i = 0; i < links.length; i++) {
			if (links[i].title) {
				var date = prettyDate("2008-01-28T22:25:00Z", links[i].title);
				if (date) {
					links[i].innerHTML = date;
				}
			}
		}
	};
	</script>
</head>
<body>

<ul>
<li class="entry" id="post57">
	<p>blah blah blah…</p>
	<small class="extra">
		Posted <a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z">January 28th, 2008</a>
		by <a href="/john/">John Resig</a>
	</small>
</li>
<!-- more list items -->
</ul>

</body>
</html>

Now, the links should say “2 hours ago,� “Yesterday� and so on. That’s something, but still not an actual testable unit. So, without changing the code further, all we can do is try to test the resulting DOM changes. Even if that did work, any small change to the markup would likely break the test, resulting in a really bad cost-benefit ratio for a test like that.

Refactoring, Stage 0

Instead, let’s refactor the code just enough to have something that we can unit test.

We need to make two changes for this to happen: pass the current date to the prettyDate function as an argument, instead of having it just use new Date, and extract the function to a separate file so that we can include the code on a separate page for unit tests.

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>Refactored date examples</title>
	<script src="prettydate.js"></script>
	<script>
	window.onload = function() {
		var links = document.getElementsByTagName("a");
		for ( var i = 0; i < links.length; i++ ) {
			if (links[i].title) {
				var date = prettyDate("2008-01-28T22:25:00Z", links[i].title);
				if (date) {
					links[i].innerHTML = date;
				}
			}
		}
	};
	</script>
</head>
<body>

<ul>
<li class="entry" id="post57">
	<p>blah blah blah…</p>
	<small class="extra">
		Posted <a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z">January 28th, 2008</a>
		by <a href="/john/">John Resig</a>
	</small>
</li>
<!-- more list items -->
</ul>

</body>
</html>

 

Here’s the contents of prettydate.js:

function prettyDate(now, time){
	var date = new Date(time || ""),
		diff = (((new Date(now)).getTime() - date.getTime()) / 1000),
		day_diff = Math.floor(diff / 86400);

	if (isNaN(day_diff) || day_diff < 0 || day_diff >= 31) {
		return;
	}

	return day_diff == 0 && (
			diff < 60 && "just now" ||
			diff < 120 && "1 minute ago" ||
			diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
			diff < 7200 && "1 hour ago" ||
			diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
		day_diff == 1 && "Yesterday" ||
		day_diff < 7 && day_diff + " days ago" ||
		day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago";
}

Now that we have something to test, let’s write some actual unit tests:

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>Refactored date examples</title>
	<script src="prettydate.js"></script>
	<script>
	function test(then, expected) {
		results.total++;
		var result = prettyDate("2008-01-28T22:25:00Z", then);
		if (result !== expected) {
			results.bad++;
			console.log("Expected " + expected + ", but was " + result);
		}
	}
	var results = {
		total: 0,
		bad: 0
	};
	test("2008/01/28 22:24:30", "just now");
	test("2008/01/28 22:23:30", "1 minute ago");
	test("2008/01/28 21:23:30", "1 hour ago");
	test("2008/01/27 22:23:30", "Yesterday");
	test("2008/01/26 22:23:30", "2 days ago");
	test("2007/01/26 22:23:30", undefined);
	console.log("Of " + results.total + " tests, " + results.bad + " failed, "
		+ (results.total - results.bad) + " passed.");
	</script>
</head>
<body>

</body>
</html>
  • Run this example. (Make sure to enable a console such as Firebug or Chrome’s Web Inspector.)

This will create an ad-hoc testing framework, using only the console for output. It has no dependencies to the DOM at all, so you could just as well run it in a non-browser JavaScript environment, such as Node.js or Rhino, by extracting the code in the script tag to its own file.

If a test fails, it will output the expected and actual result for that test. In the end, it will output a test summary with the total, failed and passed number of tests.

If all tests have passed, like they should here, you would see the following in the console:

Of 6 tests, 0 failed, 6 passed.

To see what a failed assertion looks like, we can change something to break it:

Expected 2 day ago, but was 2 days ago.

Of 6 tests, 1 failed, 5 passed.

While this ad-hoc approach is interesting as a proof of concept (you really can write a test runner in just a few lines of code), it’s much more practical to use an existing unit testing framework that provides better output and more infrastructure for writing and organizing tests.

The QUnit JavaScript Test Suite

The choice of framework is mostly a matter of taste. For the rest of this article, we’ll use QUnit (pronounced “q-unit�), because its style of describing tests is close to that of our ad-hoc test framework.

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>Refactored date examples</title>

	<link rel="stylesheet" href="qunit.css" />
	<script src="qunit.js"></script>
	<script src="prettydate.js"></script>

	<script>
	test("prettydate basics", function() {
		var now = "2008/01/28 22:25:00";
		equal(prettyDate(now, "2008/01/28 22:24:30"), "just now");
		equal(prettyDate(now, "2008/01/28 22:23:30"), "1 minute ago");
		equal(prettyDate(now, "2008/01/28 21:23:30"), "1 hour ago");
		equal(prettyDate(now, "2008/01/27 22:23:30"), "Yesterday");
		equal(prettyDate(now, "2008/01/26 22:23:30"), "2 days ago");
		equal(prettyDate(now, "2007/01/26 22:23:30"), undefined);
	});
	</script>
</head>
<body>
	<div id="qunit"></div>
</body>
</html>

Three sections are worth a closer look here. Along with the usual HTML boilerplate, we have three included files: two files for QUnit (qunit.css and qunit.js) and the previous prettydate.js.

Then, there’s another script block with the actual tests. The test method is called once, passing a string as the first argument (naming the test) and passing a function as the second argument (which will run the actual code for this test). This code then defines the now variable, which gets reused below, then calls the equal method a few times with varying arguments. The equal method is one of several assertions that QUnit provides. The first argument is the result of a call to prettyDate, with the now variable as the first argument and a date string as the second. The second argument to equal is the expected result. If the two arguments to equal are the same value, then the assertion will pass; otherwise, it will fail.

Finally, in the body element is some QUnit-specific markup. These elements are optional. If present, QUnit will use them to output the test results.

The result is this:

With a failed test, the result would look something like this:

Because the test contains a failing assertion, QUnit doesn’t collapse the results for that test, and we can see immediately what went wrong. Along with the output of the expected and actual values, we get a diff between the two, which can be useful for comparing larger strings. Here, it’s pretty obvious what went wrong.

Refactoring, Stage 1

The assertions are currently somewhat incomplete because we aren’t yet testing the n weeks ago variant. Before adding it, we should consider refactoring the test code. Currently, we are calling prettyDate for each assertion and passing the now argument. We could easily refactor this into a custom assertion method:

test("prettydate basics", function() {
	function date(then, expected) {
		equal(prettyDate("2008/01/28 22:25:00", then), expected);
	}
	date("2008/01/28 22:24:30", "just now");
	date("2008/01/28 22:23:30", "1 minute ago");
	date("2008/01/28 21:23:30", "1 hour ago");
	date("2008/01/27 22:23:30", "Yesterday");
	date("2008/01/26 22:23:30", "2 days ago");
	date("2007/01/26 22:23:30", undefined);
});

Here we’ve extracted the call to prettyDate into the date function, inlining the now variable into the function. We end up with just the relevant data for each assertion, making it easier to read, while the underlying abstraction remains pretty obvious.

Testing The DOM manipulation

Now that the prettyDate function is tested well enough, let’s shift our focus back to the initial example. Along with the prettyDate function, it also selected some DOM elements and updated them, within the window load event handler. Applying the same principles as before, we should be able to refactor that code and test it. In addition, we’ll introduce a module for these two functions, to avoid cluttering the global namespace and to be able to give these individual functions more meaningful names.

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>Refactored date examples</title>
	<link rel="stylesheet" href="qunit.css" />
	<script src="qunit.js"></script>
	<script src="prettydate2.js"></script>
	<script>
	test("prettydate.format", function() {
		function date(then, expected) {
			equal(prettyDate.format("2008/01/28 22:25:00", then), expected);
		}
		date("2008/01/28 22:24:30", "just now");
		date("2008/01/28 22:23:30", "1 minute ago");
		date("2008/01/28 21:23:30", "1 hour ago");
		date("2008/01/27 22:23:30", "Yesterday");
		date("2008/01/26 22:23:30", "2 days ago");
		date("2007/01/26 22:23:30", undefined);
	});

	test("prettyDate.update", function() {
		var links = document.getElementById("qunit-fixture").getElementsByTagName("a");
		equal(links[0].innerHTML, "January 28th, 2008");
		equal(links[2].innerHTML, "January 27th, 2008");
		prettyDate.update("2008-01-28T22:25:00Z");
		equal(links[0].innerHTML, "2 hours ago");
		equal(links[2].innerHTML, "Yesterday");
	});

	test("prettyDate.update, one day later", function() {
		var links = document.getElementById("qunit-fixture").getElementsByTagName("a");
		equal(links[0].innerHTML, "January 28th, 2008");
		equal(links[2].innerHTML, "January 27th, 2008");
		prettyDate.update("2008-01-28T22:25:00Z");
		equal(links[0].innerHTML, "Yesterday");
		equal(links[2].innerHTML, "2 days ago");
	});
	</script>
</head>
<body>
	<div id="qunit"></div>
	<div id="qunit-fixture">
		<ul>
			<li class="entry" id="post57">
				<p>blah blah blah…</p>
				<small class="extra">
					Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z">January 28th, 2008</a></span>
					by <span class="author"><a href="/john/">John Resig</a></span>
				</small>
			</li>
			<li class="entry" id="post57">
				<p>blah blah blah…</p>
				<small class="extra">
					Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-27T22:24:17Z">January 27th, 2008</a></span>
					by <span class="author"><a href="/john/">John Resig</a></span>
				</small>
			</li>
		</ul>
	</div>
</body>
</html>

Here’s the contents of prettydate2.js:

var prettyDate = {
	format: function(now, time){
		var date = new Date(time || ""),
			diff = (((new Date(now)).getTime() - date.getTime()) / 1000),
			day_diff = Math.floor(diff / 86400);

		if (isNaN(day_diff) || day_diff < 0 || day_diff >= 31) {
			return;
		}

		return day_diff === 0 && (
				diff < 60 && "just now" ||
				diff < 120 && "1 minute ago" ||
				diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
				diff < 7200 && "1 hour ago" ||
				diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
			day_diff === 1 && "Yesterday" ||
			day_diff < 7 && day_diff + " days ago" ||
			day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago";
	},

	update: function(now) {
		var links = document.getElementsByTagName("a");
		for ( var i = 0; i < links.length; i++ ) {
			if (links[i].title) {
				var date = prettyDate.format(now, links[i].title);
				if (date) {
					links[i].innerHTML = date;
				}
			}
		}
	}
};

The new prettyDate.update function is an extract of the initial example, but with the now argument to pass through to prettyDate.format. The QUnit-based test for that function starts by selecting all a elements within the #qunit-fixture element. In the updated markup in the body element, the <div id="qunit-fixture">…</div> is new. It contains an extract of the markup from our initial example, enough to write useful tests against. By putting it in the #qunit-fixture element, we don’t have to worry about DOM changes from one test affecting other tests, because QUnit will automatically reset the markup after each test.

Let’s look at the first test for prettyDate.update. After selecting those anchors, two assertions verify that these have their initial text values. Afterwards, prettyDate.update is called, passing along a fixed date (the same as in previous tests). Afterwards, two more assertions are run, now verifying that the innerHTML property of these elements have the correctly formatted date, “2 hours ago� and “Yesterday.�

Refactoring, Stage 2

The next test, prettyDate.update, one day later, does nearly the same thing, except that it passes a different date to prettyDate.update and, therefore, expects different results for the two links. Let’s see if we can refactor these tests to remove the duplication.

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>Refactored date examples</title>
	<link rel="stylesheet" href="qunit.css" />
	<script src="qunit.js"></script>
	<script src="prettydate2.js"></script>
	<script>
	test("prettydate.format", function() {
		function date(then, expected) {
			equal(prettyDate.format("2008/01/28 22:25:00", then), expected);
		}
		date("2008/01/28 22:24:30", "just now");
		date("2008/01/28 22:23:30", "1 minute ago");
		date("2008/01/28 21:23:30", "1 hour ago");
		date("2008/01/27 22:23:30", "Yesterday");
		date("2008/01/26 22:23:30", "2 days ago");
		date("2007/01/26 22:23:30", undefined);
	});

	function domtest(name, now, first, second) {
		test(name, function() {
			var links = document.getElementById("qunit-fixture").getElementsByTagName("a");
			equal(links[0].innerHTML, "January 28th, 2008");
			equal(links[2].innerHTML, "January 27th, 2008");
			prettyDate.update(now);
			equal(links[0].innerHTML, first);
			equal(links[2].innerHTML, second);
		});
	}
	domtest("prettyDate.update", "2008-01-28T22:25:00Z:00", "2 hours ago", "Yesterday");
	domtest("prettyDate.update, one day later", "2008-01-29T22:25:00Z:00", "Yesterday", "2 days ago");
	</script>
</head>
<body>
	<div id="qunit"></div>
	<div id="qunit-fixture">
		<ul>
			<li class="entry" id="post57">
				<p>blah blah blah…</p>
				<small class="extra">
					Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z">January 28th, 2008</a></span>
					by <span class="author"><a href="/john/">John Resig</a></span>
				</small>
			</li>
			<li class="entry" id="post57">
				<p>blah blah blah…</p>
				<small class="extra">
					Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-27T22:24:17Z">January 27th, 2008</a></span>
					by <span class="author"><a href="/john/">John Resig</a></span>
				</small>
			</li>
		</ul>
	</div>
</body>
</html>

Here we have a new function called domtest, which encapsulates the logic of the two previous calls to test, introducing arguments for the test name, the date string and the two expected strings. It then gets called twice.

Back To The Start

With that in place, let’s go back to our initial example and see what that looks like now, after the refactoring.

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>Final date examples</title>
	<script src="prettydate2.js"></script>
	<script>
	window.onload = function() {
		prettyDate.update("2008-01-28T22:25:00Z");
	};
	</script>
</head>
<body>

<ul>
<li class="entry" id="post57">
	<p>blah blah blah…</p>
	<small class="extra">
		Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z"><span>January 28th, 2008</span></a></span>
		by <span class="author"><a href="/john/">John Resig</a></span>
	</small>
</li>
<li class="entry" id="post57">
	<p>blah blah blah…</p>
	<small class="extra">
		Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-27T22:24:17Z"><span>January 27th, 2008</span></a></span>
		by <span class="author"><a href="/john/">John Resig</a></span>
	</small>
</li>
<li class="entry" id="post57">
	<p>blah blah blah…</p>
	<small class="extra">
		Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-26T22:24:17Z"><span>January 26th, 2008</span></a></span>
		by <span class="author"><a href="/john/">John Resig</a></span>
	</small>
</li>
<li class="entry" id="post57">
	<p>blah blah blah…</p>
	<small class="extra">
		Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-25T22:24:17Z"><span>January 25th, 2008</span></a></span>
		by <span class="author"><a href="/john/">John Resig</a></span>
	</small>
</li>
<li class="entry" id="post57">
	<p>blah blah blah…</p>
	<small class="extra">
		Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-24T22:24:17Z"><span>January 24th, 2008</span></a></span>
		by <span class="author"><a href="/john/">John Resig</a></span>

	</small>
</li>
<li class="entry" id="post57">
	<p>blah blah blah…</p>
	<small class="extra">
		Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-14T22:24:17Z"><span>January 14th, 2008</span></a></span>
		by <span class="author"><a href="/john/">John Resig</a></span>
	</small>
</li>
<li class="entry" id="post57">
	<p>blah blah blah…</p>
	<small class="extra">
		Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-04T22:24:17Z"><span>January 4th, 2008</span></a></span>
		by <span class="author"><a href="/john/">John Resig</a></span>
	</small>
</li>
<li class="entry" id="post57">
	<p>blah blah blah…</p>
	<small class="extra">
		Posted <span class="time"><a href="/2008/01/blah/57/" title="2007-12-15T22:24:17Z"><span>December 15th, 2008</span></a></span>
		by <span class="author"><a href="/john/">John Resig</a></span>
	</small>
</li>
</ul>

</body>
</html>
  • Run this example.
  • For a non-static example, we’d remove the argument to prettyDate.update. All in all, the refactoring is a huge improvement over the first example. And thanks to the prettyDate module that we introduced, we can add even more functionality without clobbering the global namespace.

    Conclusion

    Testing JavaScript code is not just a matter of using some test runner and writing a few tests; it usually requires some heavy structural changes when applied to code that has been tested only manually before. We’ve walked through an example of how to change the code structure of an existing module to run some tests using an ad-hoc testing framework, then replacing that with a more full-featured framework to get useful visual results.

    QUnit itself has a lot more to offer, with specific support for testing asynchronous code such as timeouts, AJAX and events. Its visual test runner helps to debug code by making it easy to rerun specific tests and by providing stack traces for failed assertions and caught exceptions. For further reading, check out the QUnit Cookbook.

    (al) (km)


    © Joern Zaefferer for Smashing Magazine, 2012.


Do-It-Yourself Caching Methods With WordPress


  

There are different ways to make your website faster: specialized plugins to cache entire rendered HTML pages, plugins to cache all SQL queries and data objects, plugins to minimize JavaScript and CSS files and even some server-side solutions.

But even if you use such plugins, using internal caching methods for objects and database results is a good development practice, so that your plugin doesn’t depend on which cache plugins the end user has. Your plugin needs to be fast on its own, not depending on other plugins to do the dirty work. And if you think you need to write your own cache handling code, you are wrong. WordPress comes with everything you need to quickly implement varying degrees of data caching. Just identify the parts of your code to benefit from optimization, and choose a type of caching.

WordPress implements two different caching methods:

  1. Non-persistent
    The data remains in the cache during the loading of the page. (WordPress uses this to cache most database query results.)
  2. Persistent
    This depends on the database to work, and cached data can auto-expire after some time. (WordPress uses this to cache RSS feeds, update checks, etc.)

Non-Persistent Cache

When you use functions such as get_posts() or get_post_meta(), WordPress first checks to see whether the data you require is cached. If it is, then you will get data from the cache; if not, then a database query is run to get the data. Once the data is retrieved, it is also cached. A non-persistent cache is recommended for database results that might be reused during the creation of a page.

The code for WordPress’ internal non-persistent cache is located in the cache.php file in the wp-includes directory, and it is handled by the WP_Object_Cache class. We need to use two basic functions: wp_cache_set() and wp_cache_get(), along with the additional functions wp_cache_add(), wp_cache_replace(), wp_cache_flush() and wp_cache_delete(). Cached storage is organized into groups, and each entry needs its own unique key. To avoid mixing with WordPress’ default data, using your own unique group names is best.

Example

For this example, we will a create function named d4p_get_all_post_meta(), which will retrieve all meta data associated with a post. This first version doesn’t involve caching.

function d4p_get_all_post_meta($post_id) {
    global $wpdb;

    $data = array();
    $raw = $wpdb->get_results( "SELECT meta_key, meta_value FROM $wpdb->postmeta WHERE post_id = $post_id", ARRAY_A );

    foreach ( $raw as $row ) {
        $data[$row['meta_key']][] = $row['meta_value'];
    }

    return $data;
}

Every time you call this function for the same post ID, an SQL query will be executed. Here is the modified function that uses WordPress’ non-persistent cache:

function d4p_get_all_post_meta($post_id) {
    global $wpdb;

    if ( ! $data = wp_cache_get( $post_id, 'd4p_post_meta' ) ) {
        $data = array();
        $raw = $wpdb->get_results( "SELECT meta_key, meta_value FROM $wpdb->postmeta WHERE post_id = $post_id", ARRAY_A );

        foreach ( $raw as $row ) {
            $data[$row['meta_key']][] = $row['meta_value'];
        }

        wp_cache_add( $post_id, $data, 'd4p_post_meta' );
    }

    return $data;
}

Here, we are using a cache group named d4p_post_meta, and post_id is the key. With this function, we first check to see whether we need any data from the cache (line 4). If not, we run the normal code to get the data and then add it to the cache in line 13. So, if you call this function more than once, only the first one will run SQL queries; all other calls will get data from the cache. We are using the wp_cache_add function here, so if the key-group combination already exists in the store, it will not be replaced. Compare this with wp_cache_set, which will always overwrite an existing value without checking.

As you can see, we’ve made just a small change to the existing code but potentially saved a lot of repeated database calls during the page’s loading.

Important Notes

  1. Non-persistent cache is available only during the loading of the current page; once the next page loads, it will be blank once again.
  2. The storage size is limited by the total available memory for PHP on the server. Do not store large data sets, or you might end up with an “Out of memory� message.
  3. Using this type of cache makes sense only for operations repeated more than once in the creation of a page.
  4. It works with WordPress since version 2.0.

Database-Driven Temporarily Persistent Cache

This type of cache relies on a feature built into WordPress called the Transient API. Transients are stored in the database (similar to most WordPress settings, in the wp_options table). Transients need two records in the database: one to store the expiration time and one to store the data. When cached data is requested, WordPress checks the timestamp and does one of two things. If the expiration time has passed, WordPress removes the data and returns false as a result. If the data has not expired, another query is run to retrieve it. The good thing about this method is that the cache persists even after the page has loaded, and it can be used for other pages for as long as the transient’s expiration time has not passed.

If your database queries are complex and/or produce results that might not change often, then storing them in the transient cache is a good idea. This is an excellent solution for most widgets, menus and other page elements.

Example

Let’s say we wanted an SQL query to retrieve 20 posts from the previous month, along with some basic author data such as name, email address and URL. But we want posts from only the top 10 authors (sorted by their total number of posts in that month). The results will be displayed in a widget.

When tested on my local machine, this SQL query took 0.1710 seconds to run. If we had 1000 page views per day, this one query would take 171 seconds every 24 hours, or 5130 seconds per month. Relatively speaking, that is not much time, but we could do much better by using the transient cache to store these results with an expiration time of 30 days. Because the results of this query will not change during the month, the transient cache is a great way to optimize resources.

Returning to my local machine, the improved SQL query to get data from the transient cache is now only 0.0006 seconds, or 18 seconds per month. The advantage of this method is obvious in this case: we’ve saved 85 minutes each month with this one widget. Not bad at all. There are cases in which you could save much, much more (such as with very complex menus). More complex SQL queries or operations would further optimize resources.

Let’s look at the actual code, both before and after implementing the transient cache. Below is the normal function to get the data. In this example, the SQL query is empty (because it is long and would take too much space here), but the entire widget is linked to at the end of this article.

function d4p_get_query_results() {
    global $wpdb;

    $data = $wpdb->get_results(' // SQL query // ');

    return $data;
}

And here is the function using the transient cache, with a few extra lines to check whether the data is cached.

function d4p_get_query_results() {
    global $wpdb;

    $data = get_transient('my_transient_key');

    if ($data === false) {
        $data = $wpdb->get_results(' // SQL query // ');
        set_transient('my_transient_key', $data, 3600 * 24);
    }

    return $data;
}

The function get_transient (or get_site_transient for a network) needs a name for the transient record key. If the key is not found or the record has expired, then the function will return false. To add a new transient cache record, you will need the record key, the object with the data and the expiration time (in seconds), and you will need to use the set_transient function (or set_site_transient for a network).

If your data changes, you can remove it from the cache. You will need the record key and the delete_transient function (or delete_site_transient for a network). In this example, if the post in the cache is deleted or changed in some way, you could delete the cache record with this:

delete_transient('my_transient_key');

Important Notes

  1. The theoretical maximum size of data you can store in this way is 4 GB. But usually you would keep much smaller amounts of data in transient (up to couple of MB).
  2. Use this method only for data (or operations) that do not change often, and set the expiration time to match the cycle of data changes.
  3. In effect, you are using it to render results that are generated through a series of database queries and storing the resulting HTML in the cache.
  4. The name of the transient record may not be longer than 45 characters, or 40 characters for “site transients� (used with multi-sites to store data at the network level).
  5. It works with WordPress since version 3.0.

Widget Example: Using Both Types Of Cache

Based on our SQL query, we can create a widget that relies on both caching methods. These are two approaches to the problem, and the two widgets will produce essentially the same output, but using different methods for data retrieval and results caching. As the administrator, you can set a title for the widget and the number of days to keep the results in the cache.

Both versions are simple and can be improved further (such as by selecting the post’s type or by formatting the output), but for this article they are enough.

Raw Widget

The “raw� widget version stores an object with the SQL query results in the transient cache. In this case, the SQL query would return all columns from the wp_posts table and some columns from the wp_users table, along with information about the authors. Every time the widget loads, each post from our results set would get stored in the non-persistent cache object in the standard posts group, which is the same one used to store posts for normal WordPress operations. Because of this, functions such as get_permalink() can use the cached object to generate a URL to post. Information about the authors from the wp_users table is used to generate the URL for the archive of authors’ posts.

This widget is located in the method_raw.php file in the d4p_sa_method_raw class. The function get_data() is the most important part of the widget. It attempts to get data from the transient cache (on line 52). If that fails, get_data_real() is called to run the SQL query and return the data. This data is now saved into the transient cache (line 56). After we have the data, we store each post from the set into the non-persistent cache. The render function is simple; it displays the results as an unordered list.

Rendered Widget

The previous method works well, but it could have one problem. What if your permalink depends on categories (or other taxonomies) or you are running a query for a post type in a hierarchy? If that is the case, then generating a permalink for each post would require additional SQL queries. For example, to display 20 posts, you might need another 20 or more SQL queries. To fix the problem, we’ll change how we get the data and what is stored in the transient cache.

The second widget is located in the method_rendered.php file in the d4p_sa_method_rendered class. Within, the names of class methods are the same, so you can easily see now the difference between the two widgets. In this case, the transient cache is used in the render() method. We’re checking for cached data, and if that fails we use get_data() to get the data set and generate a rendered list of results. Now, we are caching the rendered HTML output! No matter how many extra SQL queries are needed to generate the HTML (for permalinks or whatever else you might need in the widget), they are run only once, and the complete HTML is cached. Until the cache expires, we are always displaying HTML rendered without the need for any additional SQL queries or processing.

Download the Widget

You can download this D4P Smashing Authors plugin, which contains both widgets.

Conclusion

As you can see, implementing one or both caching methods is easy and could significantly improve the performance of your plugin. If a user of your plugin decides to use a specialized caching plugin, all the better, but make sure that your code is optimized.

(al)


© Milan Petrović for Smashing Magazine, 2012.


Do-It-Yourself Caching Methods With WordPress


  

There are different ways to make your website faster: specialized plugins to cache entire rendered HTML pages, plugins to cache all SQL queries and data objects, plugins to minimize JavaScript and CSS files and even some server-side solutions.

But even if you use such plugins, using internal caching methods for objects and database results is a good development practice, so that your plugin doesn’t depend on which cache plugins the end user has. Your plugin needs to be fast on its own, not depending on other plugins to do the dirty work. And if you think you need to write your own cache handling code, you are wrong. WordPress comes with everything you need to quickly implement varying degrees of data caching. Just identify the parts of your code to benefit from optimization, and choose a type of caching.

WordPress implements two different caching methods:

  1. Non-persistent
    The data remains in the cache during the loading of the page. (WordPress uses this to cache most database query results.)
  2. Persistent
    This depends on the database to work, and cached data can auto-expire after some time. (WordPress uses this to cache RSS feeds, update checks, etc.)

Non-Persistent Cache

When you use functions such as get_posts() or get_post_meta(), WordPress first checks to see whether the data you require is cached. If it is, then you will get data from the cache; if not, then a database query is run to get the data. Once the data is retrieved, it is also cached. A non-persistent cache is recommended for database results that might be reused during the creation of a page.

The code for WordPress’ internal non-persistent cache is located in the cache.php file in the wp-includes directory, and it is handled by the WP_Object_Cache class. We need to use two basic functions: wp_cache_set() and wp_cache_get(), along with the additional functions wp_cache_add(), wp_cache_replace(), wp_cache_flush() and wp_cache_delete(). Cached storage is organized into groups, and each entry needs its own unique key. To avoid mixing with WordPress’ default data, using your own unique group names is best.

Example

For this example, we will a create function named d4p_get_all_post_meta(), which will retrieve all meta data associated with a post. This first version doesn’t involve caching.

function d4p_get_all_post_meta($post_id) {
    global $wpdb;

    $data = array();
    $raw = $wpdb->get_results( "SELECT meta_key, meta_value FROM $wpdb->postmeta WHERE post_id = $post_id", ARRAY_A );

    foreach ( $raw as $row ) {
        $data[$row['meta_key']][] = $row['meta_value'];
    }

    return $data;
}

Every time you call this function for the same post ID, an SQL query will be executed. Here is the modified function that uses WordPress’ non-persistent cache:

function d4p_get_all_post_meta($post_id) {
    global $wpdb;

    if ( ! $data = wp_cache_get( $post_id, 'd4p_post_meta' ) ) {
        $data = array();
        $raw = $wpdb->get_results( "SELECT meta_key, meta_value FROM $wpdb->postmeta WHERE post_id = $post_id", ARRAY_A );

        foreach ( $raw as $row ) {
            $data[$row['meta_key']][] = $row['meta_value'];
        }

        wp_cache_add( $post_id, $data, 'd4p_post_meta' );
    }

    return $data;
}

Here, we are using a cache group named d4p_post_meta, and post_id is the key. With this function, we first check to see whether we need any data from the cache (line 4). If not, we run the normal code to get the data and then add it to the cache in line 13. So, if you call this function more than once, only the first one will run SQL queries; all other calls will get data from the cache. We are using the wp_cache_add function here, so if the key-group combination already exists in the store, it will not be replaced. Compare this with wp_cache_set, which will always overwrite an existing value without checking.

As you can see, we’ve made just a small change to the existing code but potentially saved a lot of repeated database calls during the page’s loading.

Important Notes

  1. Non-persistent cache is available only during the loading of the current page; once the next page loads, it will be blank once again.
  2. The storage size is limited by the total available memory for PHP on the server. Do not store large data sets, or you might end up with an “Out of memory� message.
  3. Using this type of cache makes sense only for operations repeated more than once in the creation of a page.
  4. It works with WordPress since version 2.0.

Database-Driven Temporarily Persistent Cache

This type of cache relies on a feature built into WordPress called the Transient API. Transients are stored in the database (similar to most WordPress settings, in the wp_options table). Transients need two records in the database: one to store the expiration time and one to store the data. When cached data is requested, WordPress checks the timestamp and does one of two things. If the expiration time has passed, WordPress removes the data and returns false as a result. If the data has not expired, another query is run to retrieve it. The good thing about this method is that the cache persists even after the page has loaded, and it can be used for other pages for as long as the transient’s expiration time has not passed.

If your database queries are complex and/or produce results that might not change often, then storing them in the transient cache is a good idea. This is an excellent solution for most widgets, menus and other page elements.

Example

Let’s say we wanted an SQL query to retrieve 20 posts from the previous month, along with some basic author data such as name, email address and URL. But we want posts from only the top 10 authors (sorted by their total number of posts in that month). The results will be displayed in a widget.

When tested on my local machine, this SQL query took 0.1710 seconds to run. If we had 1000 page views per day, this one query would take 171 seconds every 24 hours, or 5130 seconds per month. Relatively speaking, that is not much time, but we could do much better by using the transient cache to store these results with an expiration time of 30 days. Because the results of this query will not change during the month, the transient cache is a great way to optimize resources.

Returning to my local machine, the improved SQL query to get data from the transient cache is now only 0.0006 seconds, or 18 seconds per month. The advantage of this method is obvious in this case: we’ve saved 85 minutes each month with this one widget. Not bad at all. There are cases in which you could save much, much more (such as with very complex menus). More complex SQL queries or operations would further optimize resources.

Let’s look at the actual code, both before and after implementing the transient cache. Below is the normal function to get the data. In this example, the SQL query is empty (because it is long and would take too much space here), but the entire widget is linked to at the end of this article.

function d4p_get_query_results() {
    global $wpdb;

    $data = $wpdb->get_results(' // SQL query // ');

    return $data;
}

And here is the function using the transient cache, with a few extra lines to check whether the data is cached.

function d4p_get_query_results() {
    global $wpdb;

    $data = get_transient('my_transient_key');

    if ($data === false) {
        $data = $wpdb->get_results(' // SQL query // ');
        set_transient('my_transient_key', $data, 3600 * 24);
    }

    return $data;
}

The function get_transient (or get_site_transient for a network) needs a name for the transient record key. If the key is not found or the record has expired, then the function will return false. To add a new transient cache record, you will need the record key, the object with the data and the expiration time (in seconds), and you will need to use the set_transient function (or set_site_transient for a network).

If your data changes, you can remove it from the cache. You will need the record key and the delete_transient function (or delete_site_transient for a network). In this example, if the post in the cache is deleted or changed in some way, you could delete the cache record with this:

delete_transient('my_transient_key');

Important Notes

  1. The theoretical maximum size of data you can store in this way is 4 GB. But usually you would keep much smaller amounts of data in transient (up to couple of MB).
  2. Use this method only for data (or operations) that do not change often, and set the expiration time to match the cycle of data changes.
  3. In effect, you are using it to render results that are generated through a series of database queries and storing the resulting HTML in the cache.
  4. The name of the transient record may not be longer than 45 characters, or 40 characters for “site transients� (used with multi-sites to store data at the network level).
  5. It works with WordPress since version 3.0.

Widget Example: Using Both Types Of Cache

Based on our SQL query, we can create a widget that relies on both caching methods. These are two approaches to the problem, and the two widgets will produce essentially the same output, but using different methods for data retrieval and results caching. As the administrator, you can set a title for the widget and the number of days to keep the results in the cache.

Both versions are simple and can be improved further (such as by selecting the post’s type or by formatting the output), but for this article they are enough.

Raw Widget

The “raw� widget version stores an object with the SQL query results in the transient cache. In this case, the SQL query would return all columns from the wp_posts table and some columns from the wp_users table, along with information about the authors. Every time the widget loads, each post from our results set would get stored in the non-persistent cache object in the standard posts group, which is the same one used to store posts for normal WordPress operations. Because of this, functions such as get_permalink() can use the cached object to generate a URL to post. Information about the authors from the wp_users table is used to generate the URL for the archive of authors’ posts.

This widget is located in the method_raw.php file in the d4p_sa_method_raw class. The function get_data() is the most important part of the widget. It attempts to get data from the transient cache (on line 52). If that fails, get_data_real() is called to run the SQL query and return the data. This data is now saved into the transient cache (line 56). After we have the data, we store each post from the set into the non-persistent cache. The render function is simple; it displays the results as an unordered list.

Rendered Widget

The previous method works well, but it could have one problem. What if your permalink depends on categories (or other taxonomies) or you are running a query for a post type in a hierarchy? If that is the case, then generating a permalink for each post would require additional SQL queries. For example, to display 20 posts, you might need another 20 or more SQL queries. To fix the problem, we’ll change how we get the data and what is stored in the transient cache.

The second widget is located in the method_rendered.php file in the d4p_sa_method_rendered class. Within, the names of class methods are the same, so you can easily see now the difference between the two widgets. In this case, the transient cache is used in the render() method. We’re checking for cached data, and if that fails we use get_data() to get the data set and generate a rendered list of results. Now, we are caching the rendered HTML output! No matter how many extra SQL queries are needed to generate the HTML (for permalinks or whatever else you might need in the widget), they are run only once, and the complete HTML is cached. Until the cache expires, we are always displaying HTML rendered without the need for any additional SQL queries or processing.

Download the Widget

You can download this D4P Smashing Authors plugin, which contains both widgets.

Conclusion

As you can see, implementing one or both caching methods is easy and could significantly improve the performance of your plugin. If a user of your plugin decides to use a specialized caching plugin, all the better, but make sure that your code is optimized.

(al)


© Milan Petrović for Smashing Magazine, 2012.


  •   
  • Copyright © 1996-2010 BlogmyQuery - BMQ. All rights reserved.
    iDream theme by Templates Next | Powered by WordPress