Test Driven Development with Alfresco - Part 1 Introduction to TDD

This is the first in what I hope to be a series of blog posts on Test Driven Development (TDD) with Alfresco.  Before I can jump into the topic I want to make sure we are all on the same page with TDD.  I have heard a lot of definitions of TDD (primarily from interview candidates that haven't actually done TDD) and I know there is a lot of confusion as to what TDD is.

Unit Testing

Before I dive into TDD I want to talk a little bit about unit testing.  There are different types of testing, unit testing, integration testing, and system testing (there are others but all of them are out of scope of this blog except unit testing anyway :)).  Unit testing is the testing of a unit in isolation.  A unit of code in Java is generally a class.  The idea is that you are only testing the unit not the unit's dependencies (we will explore test doubles in a future post, for now just know there are ways to isolate your unit from its dependencies).

With unit testing there are two sets of source code, test and production.  Tests are the unit (integration, system, etc) tests we write and the production code is the code that the tests are validating.  Tests are not executed in a production context and for that matter they shouldn't even be included in our deployment packages.  The purpose of unit tests is to validate the correctness of the code; aka to test the code.

TDD

TDD as the name implies is literally an approach where testing drives your development.  There are three iterative steps to TDD:
  1. Write a failing test.
  2. Make it pass in the simplest way possible.
  3. Refactor

I am going to walk though each of these steps in more detail starting with writing a failing test.  Each of these steps is small as a matter of fact each iteration should take less than a minute.

Note: TDD is a specific type of test first development that involves working on one test at a time.

Writing a Failing Test

This is the first step in TDD, you write a failing test.  A test is considered failing if you run the test and it fails or if the test fails to compile.  Generally the first failing test I write is creating an instance of my class, this will fail to compile because I haven't created my class yet.

This leads me to another point; the order of the steps is important; if you change them then you aren't doing TDD.  You should never write any production code without first writing a failing unit test.  To help understand the purpose of this think about the first thing you do when you are fixing a bug.  Hopefully the first thing you is try to reproduce the issue.  You do this because you don't know if you have fixed the issue unless you can see it "get" fixed.  TDD builds on that same concept.  You are first testing your test by seeing it fail.  Then you know your code works because you made it pass.

Make it pass

OK so now you have a failing unit test.  The next step is to make it pass.  The trick here is not just to make it pass but to make it pass using as little code as possible and without causing any other test you have already written to fail.  My favorite example is assume you just wrote the first test for a new method.  This method returns an integer based on a string value sent it.  The first test is that if the string is null the method should return 0.  Now what is the easiest way to make this pass?  Most people would think the following code:


1
2
3
4
public int myMethod(String myParam) {
  if (myParam == null)
    return 0;
  return -1;
 }

But if you think about it this isn't the simplest way, line 2 isn't necessary to pass my test; sure I will probably have to write it later but if I write it now my tests aren't driving my production code.  So this is the simplest way to get the test to pass (line 4 is contrived to get it to compile):


1
2
3
public int myMethod(String myParam) {
  return 0;
}

OK I know what you are probably thinking, because it is what I thought too.  This is stupid.  Why not write the line I know I need to write!  What you will find is that sometimes the line you think you need, may not be required.  I have, more times that I can count, thought the code was going to go one way but ended up with something much simpler than I thought all because I let the tests drive the development.

Refactor

I hope you know what I mean by refactoring.  Refactoring is to change the structure of the code without changing the functionality.  As a method gets too long you extract parts of into their own appropriately named methods.  Rename variables as you realize that the original name doesn't accurately describe what the variable does.  Extract functionality into its own class (this may also require extracting tests into their own test class as well).  Delete unnecessary code and tests (which by the way you will find tests become redundant over the course of development don't hesitate in deleting them).

Keep in mind you shouldn't only refactor your production code but refactor your tests as well.

Why Use TDD?

There is a lot of debate over this point.  There is a growing group of people who are jumping on the TDD has failed band wagon.  They make some good points but they often miss the point of TDD.  They state that near 100% code coverage is unreasonable, but I have personally seen large Alfresco projects with very near 100% code coverage.  Yeah there are some classes that are hard to test like UI classes; but you will find the vast majority of code can be tested and even benefits from testing.

So why do I follow TDD?  Have you ever opened up a class that either you wrote or someone else wrote and went, wow this is really ugly?  You look at the code and you can barely understand it.  It has loads of comments, half of which are wrong.  I know have done this, and often it was my own code from months ago.  This code currently works or at least it is believed to work cause it passed QA, and now you have to make a change to it.  This can be scary.  You know the best thing to do would be to first clean up the code but then you could very well break it.  If only there was an easy way to know if you broke the code.  Well if the code was written with unit tests there is.  TDD isn't the only way to get unit tests but it certainly guarantees you will have them!

That is only half of the story though.  Tests give you confidence in the code but sometimes testing code can be hard.  If the code wasn't written to be tested then you will either have to change it to test it or do some clunky manipulation of the code under test which often is suspect and may not really be testing the code.  When you follow TDD you end up writing testable code because the test comes first.  If you are diligent about keeping your tests clean and concise you will find something surprising; your tests become the most accurate specification of what your code actually does.  If you want to know how to use a class or how it works look at the test; it will tell you.

Another important point is that following TDD will force you to refactor often.  You will find your code is considerably cleaner; since you can refactor at will it will also end up being better designed.

Where to Learn More

There are a lot of resources out there on TDD.  I highly recommend watching Robert Martin's videos on TDD and advanced TDD (I recommend watching all his videos to be honest :)) on https://cleancoders.com.  The videos aren't free but they are well worth the cost.  I also recommend Test Driven Development by Kent Beck.  There are loads of free resources as well.

Comments

Post a Comment

Popular posts from this blog

Bootstrapping Rules to Existing Folders in Alfresco

Test Driven Development with Alfresco - Part 3 Test Doubles