You’re probably thinking that this topic has been done to death. You’re probably right, but only probably. I’ve read a lot of articles talking about how unit testing is great and wonderful, I’ve also read articles saying that only bad developers write unit tests and that they’re a waste of time. This article is my view. Obviously I’m in favour since I include a section on how I personally write the tests. Take it or leave it, no one is asking you to keep on reading.
I originally wrote this article for my company blog, since there were many people in the department who hadn’t come across unit tests and were struggling not with the theoretical concept, but the practicalities of how and why they should still be writing them in a time critical project.
A lot of my tme was spent doing code reviews for different people across different teams and one of the things that struck me is that as a departmental delivery team they weren’t practising one of the ideals of XP – test driven development. The article was never intended to be an “And I want to know why! Dammit!“article, instead wanted to explore what TDD means, how it should be affecting a developers thinking and what it gives. Then I’ll run off on a tangent and talk about testing the front-end of a web application.
Test Driven Development
The concept of TDD doesn’t have to be derived from the use of unit testing and JUnit. (Although these are most commonly used). What TDD means is that before you write a line of business code you’re writing test cases that will define how your business code will behave, i.e. what it will do given different inputs. All that then remains if for you to write just enough code(1) to make your tests pass.
Before I continue, let’s get check our terminology. TDD often uses unit testing (and jUnit in the Java world) but TDD isn’t just about testing discrete units. It’s more than possible to use jUnit to write higher level integreation tests that check the combined behaviour of two or more ‘units’. There’s nothing wrong with that, however I always council that you have these integration tests in addition to unit tests. So when I talk about Test Driven Development, I’m not necessarily talking about Unit Test Driven Development – although that is a big factor.
Benefits of Unit Testing
Something I say quite often when describing the benefits of automated tests is “Automated tests don’t save you time today. Instead they save you time tomorrow.” So what does that mean? Someone standing over me telling me to write my automated tests before I write my business code is obviously going to cost me time. Time spent writing tests is time spent not writing business code. There are two separate time-spans when TDD/automated tests gives us benefit. They are before the fact and after the fact.
Before the fact
Merely expressing in programmatic terms how I’m going to test my business code helps me to define what business code I need to write. Having to explicitly think about all the inputs and outputs of all my (non-private) business methods means that I’m activily thinking about what each method is trying to accomplish, and that will help me validate my design. Getting into that mindset of constantly thinking about how to test something means that your concentration focus is changing to thinking about is what I’m trying to test correct? And that’s a critical question for any software project.
This design validation, if I can phrase it like that, is the first benefit that TDD gives.
After the fact
The afterwards benefits come from having a suite of repeatable automated tests. Lets imagine that you’ve written the complete suite of tests, you’ve written the business code and all your tests pass. Job done, yes?
No. The job is not finished. Next comes the code review.
You submit your code for review and against all odds the reviewing pannel doesn’t think much of your implementation. Your code, your tasks and the project cannot be signed off until you fix your code to meet the comments raised by the review. If you originally did all your testing manually then you must now redesign the code, change the code and then go through every single manual test yourself to make sure it still works as expected. And of course, everytime one of your manual tests fail and you have to update the code you need to start from the beginning again and test everything all over again. Tell me, how is that quicker than hitting F11 (in Eclipse) and running your automated tests?
Now let’s go up a level. You haven’t written a unit test, you’ve written something that is more akin to an integration test. (There’s notthing wrong with that.) But now a different developer has changed a class that your code uses. Now your code doesn’t behave as expected. What’s is worth pointing out here is that without your integration test this problem probably wouldn’t be found until much later in the cycle. Possibly not even until the code went live!
But if you have your automated integration tests, when someone elses dodgy code is checked in immediately the build breaks and alerts you to the fact that someone has negativily impacted the system and development steps are required to fix it! This is perhaps one of the biggest time savers that comes with automated unit tests. It’s cheaper to fix bugs as close as possible in terms of time from their inception (5. Do you fix bugs before writing new code?) so having the bug highlighted at build time is obviously better than getting a bug report from a live system.
Are we in some infathomable quantum environment?
In the above few sections have I told anyone anything they didn’t already know? I don’t know. Maybe I have, maybe not. Hopefully I’ve clarified the why behind TDD for some, but if none of what I’ve just said is new can someone explain the follow to me.
- There is not enough time to write automated tests
- There is time to find and fix bugs in all environments that automated tests would have exposed
Of course the paradox I stuggle with is that often (not always, I admit) the time taken to write an automated test suite is less than the time to find and fix bug after bug after bug. It’s certainly cheaper especially when code has to be pulled from a live environment because it is unstable.
I do understand that some consideration has to be made to the current development and PM process that governs us all. Not everyone works in an environment where unit testing is encouraged and the build is considered sacred. That’s why I encourage mydevelopers to start writing useful automated tests (at any level) before they write any more business code. It’s good to make a start doing some of this, then the benefits can start to be demonstrated and considered useful to the PMs and architects why govern the processes. Demonstrating it to the powers that be is a much more effective tool for changing their conceptions and behaviours (and therefore the process) than any number of blogs and powerpoint slides.
How I do it
I appreciate that some developers might be new to TDD so in this section I’m just going to quickly share how I personally go about ‘doing’ TDD (using JUnit). It might prove useful you some of you, hopefully it won’t add any confusion.
Step 1: Define The Public Business Class
Firstly, I decide (probably using my class diagram) what non-private methods my class needs to have, and I write a skeleton for it. Example(2);
package com.foo.tdd;
public class MyBusinessClass {
public MyBusinessClass() {
throw new RuntimeException("CONSTRUCTOR INCOMPLETE");
}
public short doSomething() {
throw new RuntimeException("METHOD INCOMPLETE");
}
public int doSomethingClever(int someInput) {
throw new RuntimeException("METHOD INCOMPLETE");
}
public Object doSomethingEvenMoreClever(int someInput, String someString) {
throw new RuntimeException("METHOD INCOMPLETE");
}
}
As you can see, this is just a skeleton class, if you try and do anything to this class you’ll get a run-time
exception. But what it allows me to do is now write a series of unit tests based on this class that will
compile.
Step 2: Define the Unit Tests
The second step is to think about what I need to test in the business class and write the corresponding
skeleton. Example;
package com.pof.tdd;
import junit.framework.TestCase;
public class MyBusinessClassTest extends TestCase {
public void testDoSomethingClever_negativeInput() {
fail("INCOMPLETE TEST");
}
public void testDoSomethingClever_zeroInput() {
fail("INCOMPLETE TEST");
}
public void testDoSomethingClever_positiveInput() {
fail("INCOMPLETE TEST");
}
public void testDoSomethingClever_hugeNumber() {
fail("INCOMPLETE TEST");
}
//snip more of the same
}
These series of tests enable me to look at what (I think) I should be testing to make sure I cover all the
scenarios. I can also run this test suite, but obviously I’ll get a 100% failure rate. Remember, you might flicker between these first two steps since you might write a test that means you need additional, or fewer, non-private methods or maybe you need to tweak some of the business methods you’ve already defined.
Step 3: Describe the Unit Tests
The fourth step is to now go back through all the unit tests and replace all the <code>fail</code> method calls
with the actual code for the tests. Your unit test class will still compile okay because the non-private methods of the business class exist. Of course, you’ll still get a 100% failure rate from running the tests, but this time it’ll be because of the runtime exceptions the business class is throwing.
Step 4: Write the Business Code
Now you can write just enough code to make the tests pass – and this is an iterative action. Remember, you want to write the smallest most simple code you can (as long as it makes the tests pass). There is an interesting article here; Using Test Driven Development in a Computer Science Classroom: A First Experience, you can ignore a lot of it but under the section “On Your Mark, Get Set, Test!” there is a good example of writing the smallest most simple thing to make your tests pass. But as I said, this is just my approach to TDD. I’m sure there are others, but figured that I’d share it with you in case it’s helpful.
Write a Web Based Test
Testing the (web) front-end is a bit different, instead of thinking in units you need to start thinking in user scenarios. These will probably be end-to-end scenarios that describe a given story or known business process through your application.
There are a bunch of free tools you can use for testing the web front-end, the one I choose is jWebUnit. I choose this over HtmlUnit or HttpUnit simply because the code is easier to write. In general, a half-dozen lines of HttpUnit can be reduced into just one or two jWebUnit ones. In fact, I’m pretty sure that jWebUnit doesn’t provide any additional functionality over these other two frameworks.
Introducing jWebUnit
Well that’s the tool of choice; but what does it do? I always like to use examples, so let’s choose Amazon. Let’s assume I’ve just written the functionality for “search for CD by artist”, the manual steps I might go through to test the functionality are;
- Open a web browser at http://www.amazon.co.uk
- Click on the “Music” tab
- Enter “Rolf Harris” in the search input
- Click on the Go button
- Check that the phase “No results match your search for “Rolf Harris” in Music.” does not appear on the page
- Check that the phrase “The Best of Rolf Harris” does appear on the page
And that’s a very simple test. I can expand that test to cover some sub-set of all artists and make sure that the correct search results always appear. The alternative to this is to manually do that, one at a time for each artist my by test data. And everytime I changed the back-end of my code and I wanted to make sure that it still works, I’d have to do those manual steps all over again.
So here’s a cunning(3) plan. Who don’t I write an automated test, that will run everytime I change any code. I’d only have to write the test once – and maybe keep it up to date if some of the copy changes – but it would allow me to change the underlying code and keep utter confidence that those changes haven’t broken anything from the user’s perspective.
Example jWebUnit Test Case
Luckily, jWebUnit makes writing these kinds of things very simple. The following jWebUnit code does the manual test defined above.
public class AmazonSearchMusicForArtistTest
extends WebTestCase {
// snip attribute definitions
public void setUp() {
getTestContext().setBaseUrl("http://www.amazon.co.uk");
}
public void testSimpleCDSearchOnAuthor() {
final String searchTerm =
"Rolf Harris";
final String searchResult_notPresent =
"No results match your search for "
+searchTerm
+" in Music.";
final String searchResult_isPresent =
"The Best of Rolf Harris by Rolf Harris"
+(Audio CD - 2003)";
//set the start URL from the base URL set in the setUp method
beginAt("/");
//check that we are on the right page by checking the title,
//then go to the Music page
assertTitleEquals(WELCOME_PAGE_TITLE);
clickLinkWithImage(MUSIC_TAB_IMAGE_HREF);
assertTitleEquals(MUSIC_PAGE_TITLE);
//enter the search terms into the form and submit it
setFormElement(SEARCH_INPUT_FORM_ELEMENT, searchTerm);
submit(SEARCH_SUBMIT_BUTTON_NAME);
//check that the correct text appears on the page
assertTextNotPresent(searchResult_notPresent);
assertTextPresent(searchResult_isPresent);
}
}
As you can see, the actual code that does the test is tiny, and in reality it didn’t take that much more time to write the test than it did to perform the test manually. What it has given me though is an automated repeatable test. So when you want to refactor your code, or someone tells you to change the code, you don’t have to expend any energy retesting the front end. These are the exact same benefits you get from writing ‘normal’ unit tests, the differences are that this is testing a web unit rather than a more traditional unit. It’s sort of part integration, part user and part scenario test.
Is this actually really useful? To highlight what a time saver let me give you this real-world and personal example. A few weeks ago I had done a code review for someone. I felt that their implementation was flawed, it was expensive and wasn’t conclusive to extensibility so I asked him (or her, I won’t tell you who it was) that they should refactor it. The first comment that person made to me was, “But if I do that I’ll have to retest all the user scenarios!” Well, yep. You will, because I won’t pass the code review until the changes are made. The chap (or chapess) wasn’t very happy about having to do that, but imagine how really unhappy they would have been if their changed code still didn’t pass the review and they had to do it a third time? If there had been a suite of automated tests for all these user scenarios then changing the back-end code becomes a snap because (once the automated tests are written) you an validate the new implementation for free!
Super free extra advantage
When someone changes code, a web service might bomb out. The first few pages of the application might still be okay – thus satisfying a cursory imspection or smoke test, but by having a suite of these indepth and end-to-end scenario tests you’d uncover the back-end problem very easily. And what’s more you’d uncover the underlying problem at build time (these tests are run as part of the build) and you’d discover it with no manual intervention needed.
Caveat: Maybe Some Additional Work Required
In some cases you might have to put in some additional work when coding the JSPs to allow you to write meaningful jWebUnit test cases. For example, you might have to add an “id” or “name” attribute to some tags such as images, form elements or links just to enable you to call the
dosomethingtoThing(String id);
method.
Disclaimer
I’ve written all the code in this article in notepad so some compiler errors might have crept in, I’m pretty sure that it’s all okay really though.
And finally
<p>This article is made up of three things. My opinion, my experience from ‘today’, and my experience at previous companies working in Agile/XP. I don’t think these ideas are contencious, but they are different and can be difficult to swallow. TDD is not just a new way of doing stuff, it’s a new way of thinking about how you do stuff. And once you get into the practise of thinking in this way you do start to wonder how you did stuff any other way. The value becomes more and more apparent with experience.
1 The idea being that, statistically, the less code you write the fewer bugs you’ll introduce
2 To save space in the blog, I’ve ommitted the javadocs
3 More cunning than a cunning plan thought up by a cunning fox on a particularly cunning day
Posted by Tom