Test a little, code a little - a practical introduction to TDD

Posted Wednesday, December 07, 2005 4:41 PM by Dan Bunea

Introduction

 

Test Driven Development is one of the most well known practices in and outside agile communities. However, the level of understanding is in my opinion low as the term seems to be surrounded by lots of misunderstanding and misuse.

Biggest confusions regarding TDD

Many people that do not know too many things about agile practices, or beginners tend to think that test driven development, means having written automated test for your code or even worse that test driven development is a method to test the code, rather then develop it.

1. Test Driven Development is for testers

A lot of confusion comes from the name: Test Driven Development, which to some developers means a method of writing automated tests for software, something that should be the QA team’s responsibility. Having the test word in the name tends to confuse a lot of developers, who presume as explain above that TDD should be ignored as it is a practice for the QA. This comes from a partial read of the title, imagining the rest. The title was intended to be quite clear: automated tests that drive the development of the code

2. I do TDD because I have automated tests that test my code

Beginners in agile practices, have the tendency to believe that having automated tests that cover parts of the whole code, means they are doing TDD. In fact it is right, only of those tests were developed before the code was developed and not after. If the test code did not influence the development of the code, then it is not TDD, it is just having automated tests, which is a good thing, anyway.

Having automated tests can say that is partially TDD even if the tests were not developed before the code, at a high or better said project level, as the biggest purpose of the tests in a project as a whole is detecting when something is broken by a change a developer makes, which influences the decisions he makes later in the design and in the code he writes, thus driving the way the software is written.

 Theory

Test Driven development is a software development technique, targeted for software developers, and not software testers, that uses automated tests that are written at first, to influence and drive the code that is being written. In the end the code passes the tests, which means that it has been driven towards passing a specific number of tests.

The technique has been widely described, so I will try to keep my introduction as simple as possible. The technique is also called red-green factor, test a little, code a little or test, fail, code , pass and consists from a list of steps that are followed in order to obtain the code we want. So it is a coding technique, one a little bit different, as it presumes the following steps:

  1. Test: Write a small test (one, not more)
  2. Compile: Make the code compile, and make the test fail
  3. Fail: running the test will show it failing (red)
  4. Code: write the code, just enough to make the test pass, not more
  5. Pass: run the test , it should pass. If it doesn’t code a little more (step 4). If it passes, you get the red (red-green factor)
  6. Refactor: refactor the code written so far (both test and code)
  7. Pass again. If it doesn’t go to step 4.
  8. Repeat: Go to step 1 and do it again, until you have the desired code, passing all the tests it should pass.

 

This would be the simplified list of steps that need to be done in test driven development. In fact, the list is a little bit longer as the first step is always think: about what you need and then the second is to think about making a simple test, then following the 1-8 steps and going back sufficient times to make the code good. This is an iterative process that presumes making a test (test a little) and building the code that passes it (code a little), and repeating this step as much as needed.

Practice

Suppose we have a order processing system, requested by the customer and I am asked to write the part where the clients are given a discount if the order total is over a certain limit. In Extreme Programming, this would be a programming task of a user story [].

At this time, I have absolutely no code written. First I think I should have an Order class and maybe also an OrderLine class. Let me write a test about that (step 1):

 

    [TestFixture]

    public class OrderTests

    {

        [Test]

        public void EmptyOrder()

        {

            Order order = new Order();

            Assert.AreEqual(0,order.Total);

        }

    }

 

 

I have no such class Order. So at step 2 I should make the code compile and them and at the same time make the test fail. So I write the following code:

 

    public class Order

    {

        public decimal Total

        {

            get{throw new NotImplementedException();}

        }

    }

 

I can compile the code now, so I should see if the code fails:

 

 

It seems steps 1-3 were simple, let’s move on and add the code that passes the test. You might be surprised:

 

    public class Order

    {

        public decimal Total

        {

            get{return 0;}

        }

    }

 

The fact that I intentionally wrote return 0, makes the code be bad but still pass the test. However this is very good way of understanding that the code needs to pass the tests, as they are influencing the development and that it is important not to write code I advance.

And now let’s if it passes as it should (step 5):

 


 

Wow, now we have implemented a passing test, we should see if any refactoring is needed. So far I don’t see any so since the code still passes the test, I can consider the first cycle completed. Back to the first step, adding a new test, for a few order lines.

 

    [TestFixture]

    public class OrderTests

    {

        [Test]

        public void EmptyOrder()

        {

            Order order = new Order();

            Assert.AreEqual(0,order.Total);

        }

 

        [Test]

        public void TwoOrderLinesTotal()

        {

            Order order = new Order();

 

            OrderLine ol1 = new OrderLine();

            ol1.Product = "Laptop";

            ol1.Quantity = 1.0;

            ol1.Price = 1000.0;

 

            OrderLine ol2 = new OrderLine();

            ol2.Product = "Monitor";

            ol2.Quantity = 2.0;

            ol2.Price = 200.0;

 

            order.AddOrderLine(ol1);

            order.AddOrderLine(ol2);

 

            Assert.AreEqual(1400.0, order.Total);

        }

    }

Let’s make it compile and fail, by adding the OrderLine class and AddOrderLine method:

    public class OrderLine

    {

        public string Product

        {

            get {throw new NotImplementedException();}

            set { }

        }

        public decimal Quantity

        {

            get { throw new NotImplementedException(); }

            set { }

        }

        public decimal Price

        {

            get { throw new NotImplementedException(); }

            set { }

        }

    }

 

And:

 

    public class Order

    {

        public decimal Total

        {

            get{return 0;}

        }

 

        public void AddOrderLine(OrderLine ol)

        {

        }

    }

 

Let’s see if it fails (we run all the tests):

 


 

Now it is time to code to make the test pass.

We add a little code, then if it compiles, run the tests. If it fails, then we need to code some more, and run again and so on until the code passes the tests. The code becomes:

 

    public class Order

    {

        private IList orderLines = new ArrayList();

 

        public decimal Total

        {

            get

            {

                decimal sum = (decimal)0;

                foreach(OrderLine ol in this.orderLines)

                {

                    sum += ol.Total;

                }

                return sum;

            }

        }

 

        public void AddOrderLine(OrderLine ol)

        {

            this.orderLines.Add(ol);

        }

    }

 

And OrderLine:

 

    public class OrderLine

    {

        private decimal quantity;

        private decimal price;

        private string product;

 

        public string Product

        {

            get { return product; }

            set { product = value; }

        }

        public decimal Quantity

        {

            get { return quantity; }

            set { quantity = value; }

        }

        public decimal Price

        {

            get { return price; }

            set { price = value; }

        }

 

        public decimal Total

        {

            get

            {

                return this.quantity * this.price;

            }

        }

    }

 



Wow, this is the second time in the last 10 minutes, now let’s look for some refactoring possibilities. The code seems ok, but maybe we can do something about the test, as it seems the order initialization code is duplicated. We move it into the SetUp method, as it is run before every method is run, then the OrderLine initialization code could be moved to another method:

 

    [TestFixture]

    public class OrderTests

    {

        Order order = null;

 

        [SetUp]

        public void SetUp()

        {

            order = new Order();

        }

 

        [Test]

        public void EmptyOrder()

        {

            Assert.AreEqual(0,order.Total);

        }

 

        [Test]

        public void TwoOrderLinesTotal()

        {

            OrderLine ol1 = CreateOrderLine("Laptop",1,1000);

            OrderLine ol2 = CreateOrderLine("Monitor", 2, 200);

 

            order.AddOrderLine(ol1);

            order.AddOrderLine(ol2);

 

            Assert.AreEqual(1400.0, order.Total);

        }

 

        private OrderLine CreateOrderLine(string product, decimal quantity, decimal price)

        {

            OrderLine ol1 = new OrderLine();

            ol1.Product = product;

            ol1.Quantity = quantity;

            ol1.Price = price;

            return ol1;

        }

    }

 

Now see if it still passes:

 


 

It does, so we have just finished the second cycle, and we have an almost good code, pretty much tested if it works ok. Now let’s add the part with the discount, using another “test a little, code a little” cycle. First we add the test:

 

    [TestFixture]

    public class OrderTests

    {

        Order order = null;

 

        [SetUp]

        public void SetUp()

        {

            order = new Order();

        }

 

        [Test]

        public void EmptyOrder()

        {

            Assert.AreEqual(0,order.Total);

        }

 

        [Test]

        public void TwoOrderLinesTotal()

        {

            OrderLine ol1 = CreateOrderLine("Laptop",1,1000);

            OrderLine ol2 = CreateOrderLine("Monitor", 2, 200);

 

            order.AddOrderLine(ol1);

            order.AddOrderLine(ol2);

 

            Assert.AreEqual(1400.0, order.Total);

        }

 

        [Test]

        public void Discount10PercentOver2000()

        {

            IRule discountRule = new DiscountRule(10, 2000);

 

            OrderLine ol1 = CreateOrderLine("Laptop", 1, 1000);

            OrderLine ol2 = CreateOrderLine("Monitor", 2, 200);

            OrderLine ol3 = CreateOrderLine("MimiMac", 2, 500);

 

            order.AddOrderLine(ol1);

            order.AddOrderLine(ol2);

 

            order.ApplyBusinessRule(discountRule);

 

            Assert.AreEqual(2400.0, order.Total);

            Assert.AreEqual(2160.0, order.TotalAfterDiscount);

 

        }

 

        private OrderLine CreateOrderLine(string product, decimal quantity, decimal price)

        {

            OrderLine ol1 = new OrderLine();

            ol1.Product = product;

            ol1.Quantity = quantity;

            ol1.Price = price;

            return ol1;

        }

    }

 

 

We ensure everything is compilable and then that the test fails, then we struggle to make it pass, fixing the code, then we realize something very ugly: the test should work but it doesn’t.

 



 

We have a bug in the test. Now we see that in the test we forgot to add the order line #3 at the order (totals 1400), so we add it, then rerun the test. It seems the relationship between tests and code makes the bugs easy to find in both parts of the code:

 

Well, it seems that we have a few simple tests that helped us build a piece of code. Now these tests helped us so far, and by adding them to a test suite on the project, and running them regularly, can really tell us when they have been broken and where.

Hopefully, the above lines will clear a little the "fog" around what TDD means and how it can be applied practically.

 

Comments

# re: Test a little, code a little - a practical introduction to TDD

Wednesday, December 07, 2005 7:12 AM by Martijn Veken

Thanks Dan for this introduction. I know you want to keep it simple to demonstrate a piece of the TDD concept, but doing it in real life is lot harder than this.

If you want to build large applications using this technique there are lots of things you have to realize before you can actualy do it. People need solid understanding about conceived architecture, patterns and standards before you can do anything like this. You will build you're (test)code just based on requirements and if not everybody is absolutely clear about this your application will become messy. Not everybody on your team is an experienced developer.

Another problem is the fit of it in more traditional projects. Not everybody will be confinced upfront that this is the way to go, so you will probably have some traditional designs before you will start your coding. Developing your application with TDD will be virtually impossible because the tests are not driving the code at this moment. We will need some adapted version of TDD to make this work. I know I'm cursing in a church by saying this, but Microsoft offered a more practical approach that would help you to implement some TDD-ish things in your development. The person who wrote that probably still has some trouble sitting down.

I'm curious how many developers are actually doing 100% TDD in a real life project at this moment, how large their application is and how succesfull they are. Is this really something that can be done main stream or just something for the happy few?

# re: Test a little, code a little - a practical introduction to TDD

Wednesday, December 07, 2005 2:02 PM by Dan Bunea

Well, we have developed projects where everything or almost everything is fully tested and was developed with TDD. The projects are small and medium, one is started in july with 3 people and still working. The more we develop, the more we use TDD and fully automated tests. The best examples for us are by comparison, between projects done in the past and the ones done using XP. There is a massive difference (quality especially, far less bugs, and great possibilities to change, extend etc), and this difference has really convinced the developers, and management even the clients about the real benefits of TDD.

The good news might be the fact, that is is only one of a series of articles I plan to write about testing, but I had to start with the concept first. I will write about testing business components, about tests done with the database, about tests with the user interface (I have written already about using MVP pattern: http://bloggingabout.net/blogs/danbunea/archive/2005/11/28/10388.aspx) and testing web applications with selenium (http://selenium.thoughtworks.com) or even customer tests done with Fit (http://fit.c2.com).

I don't really think there's a limit in how or what kind of projects can be done in agile manner.

# re: Test a little, code a little - a practical introduction to TDD

Wednesday, December 07, 2005 11:57 PM by Martijn Veken

Thanks, I'll be looking forward to your new articles!

# re: Test a little, code a little - a practical introduction to TDD

Thursday, December 08, 2005 3:32 AM by Dennis van der Stelt

>I don't really think there's a limit in how
>or what kind of projects can be done in agile
>manner.

I agree. I once heard you cannot do XP with larger teams. So I accepted it as a fact. Later, I heard that "larger teams" are 50 people and up. Way, WAY bigger then 90% of the teams I was on most. Last I heard Mike Cohn with his scrumm, working on projects with several hundred people.

So I guess size doesn't matter, even in Agile! :)

# An introduction into the TDD Mantra

Wednesday, December 14, 2005 7:07 AM by Dennis van der Stelt


A while ago I gave a presentation, called “An introduction into the TDD Mantra”. The occasion...

# An introduction into the TDD Mantra

Wednesday, December 14, 2005 7:11 AM by Dennis van der Stelt


A while ago I gave a presentation, called “An introduction into the TDD Mantra”. The occasion...

# An introduction into the TDD Mantra

Wednesday, December 14, 2005 7:12 AM by Dennis van der Stelt


A while ago I gave a presentation, called “An introduction into the TDD Mantra”. The occasion...

# An introduction into the TDD Mantra

Wednesday, December 14, 2005 11:45 AM by Dennis van der Stelt


A while ago I gave a presentation, called “An introduction into the TDD Mantra”. The occasion...

# An introduction into the TDD Mantra

Wednesday, December 14, 2005 11:46 AM by Dennis van der Stelt


A while ago I gave a presentation, called “An introduction into the TDD Mantra”. The occasion...

Leave a Comment

(required) 
(required) 
(optional)
(required)