Improving Application Quality Using Test-Driven Development (TDD)

Smells

Now that we have three tests in place, a certain amount of duplicated code has appeared. Most obviously, there is the class setup code:

MagazineIndex cbtMagazineIndex;
cbtMagazineIndex = new MagazineIndex();

We know that refactoring plays a major part in the TDD process, but so far we’ve only really looked at refactoring the implementation class. Duplicated code is something XPers call a ‘bad smell’, and it is something that should be refactored out. Conveniently, most xUnit frameworks implement a "setup" and "teardown" mechanism. NUnit uses the attributes [SetUp] and [TearDown].

The [SetUp] attribute allows us to specify a method that will be called once at the creation of the [TestFixture]. We can use it to create an instance of the MagazineIndex class.

The [TearDown] attribute allows us to specify a method that will be called when the [TestFixture] is destroyed. We could use this to clean up any objects/memory that are not garbage collected.

Think of [SetUp] and [TearDown] as "initialise" and "cleanup", or "create" and "destroy/free".

If we refactor our most recent test class, we can remove the duplication by making the class being tested a private member of the class TestMagazineIndex. The introduction of a SetUp method then allows us to construct a new instance of MagazineIndex.

namespace WinApp
{

    using System;
    using NUnit.Framework;

    [TestFixture]
    public class TestMagazine
    {

        private MagazineIndex cbtMagazineIndex;

        [SetUp]
        public void Setup()
        {

            cbtMagazineIndex = new MagazineIndex();

        }

        [TearDown]
        public void TearDown()
        {

            cbtMagazineIndex = null;

        }

        [Test]
        public void CheckNew()
        {

            cbtMagazineIndex = new MagazineIndex();
            Assert.IsNotNull(cbtMagazineIndex, "cbtMagazine is null!");

        }

        [Test]
        public void CheckAddMagazine()
        {

            cbtMagazineIndex = new MagazineIndex();
            cbtMagazineIndex.AddMagazine("Methods & Tools");
            Assert.IsTrue(cbtMagazineIndex.IsMember("Methods & Tools"), "Magazine was not added to collection!");

        }

        [Test]
        public void CheckAddTwoMagazines()
        {

            cbtMagazineIndex = new MagazineIndex();
            cbtMagazineIndex.AddMagazine("Methods & Tools");
            cbtMagazineIndex.AddMagazine("The Delphi Magazine");

            Assert.IsTrue(cbtMagazineIndex.IsMember("Methods & Tools"), "M&T was not added to collection!");
            Assert.IsTrue(cbtMagazineIndex.IsMember("The Delphi Magazine"), "TDM was not added to collection!");

        }

    }

}


Listing 9: Refactoring followed by re-running of tests

Of course, with such a sweeping change like that of Listing 9, we need to run the tests again to be sure that everything still works. Luckily the tests pass and we get a green bar.

One Assertion per Test?

The TDD Yahoo group contains a long running thread about the merits of how many assert statements a test may have. Whilst the hardened core of TDDers will argue that "one test should test one aspect", you may have noticed that I have included two assert statements in one of my tests. I believe with practice I’ll reach the point whereby I have a single assertion per test, but until I’ve taken TDD across an entire [large] project, I’ll have to continue using one or two assertions per test. So far I’ve enjoyed considerable success introducing TDD into the new classes that I’m writing for inclusion in existing applications. I’m also taking the time to write tests for some of the periphery classes as I go.

The TDD Yahoo group is the place to find Dave Astels and many other TDD gurus – if you’re serious about TDD it’s definitely a group to follow.

Naming Conventions

Many xUnit implementations enforce a naming convention. Tests, for example, must be prefixed with the word ‘Test’, e.g. public void TestCreateNew(). .NET’s attributes relieve us of that convention; we are able to essentially prefix classes and methods with the [TestFixture] and [Test] attributes. These attributes are embedded in the assembly (our .exe) which can then be interrogated by the NUnit GUI / Test Runner application.

Equally, we could examine the assembly using other tools. Lutz Roeder’s .NET Reflector will let you examine the ‘metadata’ inside a .NET assembly.

However, there is one small NUnit convention that we must follow. All NUnit [Test] methods must be declared as public void. If you attach the [Test] attribute to a non-public method, NUnit will simply ignore it.

Conclusions

As you may have gathered, refactoring and TDD are made for each other. During the refactoring process we find ourselves making sweeping changes to the internals of a system, we need a mechanism that ensures that any refactorings do not break the functionality, TDD provides us with that mechanism.

I noted a number of principles:

  • write tests first
  • make a test fail the first time it is executed
  • do the simplest thing that could possibly work
  • small steps
  • no code without tests

These principles are worth following. Granted, over the course of this article, I have deliberately taken these principles to their extreme (small steps and do the simplest thing), your initial work with TDD should take similar small steps. As your confidence increases, you will find that larger steps are possible. However, taking steps that are too large will take you back to square one: biting off more than you can chew is why we spend a lot of time scrapping code and re-working it.

I have used a very simple example in this article. If you would like to see a more detailed example please visit my web-site where you can find examples written using Visual Basic.NET and Delphi.

I hope this article has given you a flavour of the power of TDD – the ability to change a class’s implementation and re-test it with just a few mouse clicks is a great confidence boost. All too often traditional non-automated testing techniques involve the original developer re-writing some code, re-testing the bit that changed with little regard for the periphery code that really should be tested too. Maintaining an exhaustive set of tests that can be run again and again means those obscure bugs that usually slip through the net are caught whilst the original developer is focusing on the problem – not hours, days or sometimes weeks after the code was re-written/tweaked.

If you like the idea behind TDD but do not wish to use Visual Studio .net, I can recommend Dave Astels site and Ron Jefferies site as good places to start looking for a TDD framework for your environment.

Lastly, I will leave you with the thoughts of Rutherford and Marick. Testing is an emotive subject, whilst this article has used the phrase test-driven development, I agree with Rutherford and Marick, the code that makes up a [TestFixture] and a [Test] provides us with an example of how a class/method can be invoked and what we might expect it to return. Given that, it is fair to say that [TestFixture]s and [Test]s are the best live documentation you will have for your code-base: think about it, paper-based documentation is rarely in sync with the code-base, TDD’s repeatability ensures the [Test]s are.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“To iterate is human, to recurse divine” - L. Peter Deutsch