Software Development Magazine - Project Management, Programming, Software Testing |
Scrum Expert - Articles, tools, videos, news and other resources on Agile, Scrum and Kanban |
Improving Application Quality Using Test-Driven Development (TDD) - Page 4
Craig Murphy, www.craigmurphy.com
Smells
Now that we have three tests in place, a certain amount of duplicated code has appeared. Most obviously, there is theclass 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 theimplementation class. Duplicated code is something XPers call a 'bad smell',and it is something that should be refactored out. Conveniently, most xUnitframeworks 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 useit 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 usethis 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 ofthe 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 [2] 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 [3] 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 totheir extreme (small steps and do the simplest thing), your initial work withTDD should take similar small steps. As your confidence increases, you will findthat larger steps are possible. However, taking steps that are too large willtake 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 myweb-site[4] 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 andre-test it with just a few mouse clicks is a great confidence boost. All toooften traditional non-automated testing techniques involve the originaldeveloper re-writing some code, re-testing the bit that changed with littleregard for the periphery code that really should be tested too. Maintaining anexhaustive set of tests that can be run again and again means those obscure bugsthat usually slip through the net are caught whilst the original developer isfocusing 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[7] and RonJefferies site[8] as good places to start looking for a TDD framework for your environment.
Lastly, I will leave you with the thoughts of Rutherford[5] and Marick[6]. Testing is an emotive subject, whilst this articlehas 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 exampleof 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 livedocumentation you will have for your code-base: think about it, paper-baseddocumentation is rarely in sync with the code-base, TDD's repeatability ensures the [Test]s are.
Resources:
[1] http://c2.com/cgi/wiki?ExponentialCostCurve
[2] http://groups.yahoo.com/group/testdrivendevelopment
[3] http://www.aisto.com/roeder/dotnet
[4] http://www.craigmurphy.com
[5] http://cgi.bramwell.plus.com/krblog/2005/02/test_driven_is.html
[6] http://www.testing.com/cgi-bin/blog/2003/08/21#agile-testing-project-1
[7] http://www.adaptionsoft.com/tddapg_xunit.html
[8] http://xprogramming.com/software.htm
Slide-deck accompanying this article:
http://www.craigmurphy.com/bug/tdd/13Jul2004/TDDIntro.zip
Examining the Cost of Change, Scott Ambler:
http://www.agilemodeling.com/essays/costOfChange.htm
Books:
Test-Driven Development: By Example, Kent Beck,
Addison-Wesley, 2003, ISBN 0-321-14653-0
Test-Driven Development: A Practical Guide, Dave Astels,
Prentice-Hall/Pearson Education, 2003, ISBN 0-13-101649-0
(reviewed here: http://www.craigmurphy.com/bug/tdd/review.htm)
Refactoring: Improving the Design of Existing Code, Martin Fowler,
Addison-Wesley, 1999, ISBN 0-201-48567-2
Click here to view the complete list of archived articles
This article was originally published in the Spring 2005 issue of Methods & Tools
Methods & Tools Testmatick.com Software Testing Magazine The Scrum Expert |