Friday, April 24, 2009

Test Driven Development, how to?

In software there are a lot of methodologies but really few reduce greatly the time of development or simplify it.

Most programmers probably know, but even if you don't know, is good to notice: great bulk of time in development of a software is bug fixing and maintainance. They can take from 55% in most lucky projects, to around 95%. The development time in bug fixing relies mostly on how much you invest in them also. So, having 95% of time means that this software product focuses on bug fixing greatly. Consider, for example, a real-time device programming or an air traffic control software. The platform needs to be stable in years or having minimal downtime.

In desktop programming around 75% of time is bug fixing. Why does software spend between two thirds to three quarters doing bug fixing instead adding features? Developers are humans and they can keep only some ideas in parallel, but they have to focus to memory management, performance, writing useful codes, interaction between components, specifications, etc. This will overwhelm really easy even the best developer.

Test Driven Development (TDD) is a software methodology created to reduce the time of staying in bug fixing. The best qualities of it are:

- detect bugs as early as possible
- have at least for your batteries of tests in place of their inputs, to provide the expected output
- make the code to feedback developer before a QA or in a much worse way, a user to notice.

What is the best way to do TDD?
- because it is test driven, you should write tests first, before your code. Or if the code exists, make tests to reflect the interface (without knowing the implementation is better).
- makes your tests pass with minimal code
- refactors your code to be as good as you want but doesn't break the tests


NaroCAD uses nUnit framework to do unit tests which are small programs that define a battery of tests.

Here is a test that checks in NaroCAD if one document has a right working Undo function:


using System;
using NUnit.Framework;
using TreeData.AttributeInterpreter;
using TreeData.NaroData;

namespace NaroTestSuite.TreeData
{
[TestFixture]
public class UndoRedoTests
{
[Test]
public void UndoAppliedEmpty()
{
DefaultInterpreters.Setup();
Document doc = new Document();
Assert.AreEqual(0, doc.Root.Interpreters.Count);
doc.Transact();
doc.Root.Update<IntegerInterpreter>().Value = 2;
Assert.AreEqual(1, doc.Root.Interpreters.Count);
doc.Commit();
Assert.AreEqual(1, doc.Root.Interpreters.Count,);
doc.Undo();
Assert.AreEqual(0, doc.Root.Interpreters.Count,);
}
}
}
How to easily write a test in NaroCAD codebase?
Check the feature you want to make working.

Define your expectancies (or assertions). Here are the normal expectations for a class that store a document in a doc.Root tree data.
- a newly created document is an empty one and an empty document will have no attributes (or interpreters)
- after I will try to save the status of the document (after a "transact") the document should not change the attribute count

- after I will add an integer attribute the attribute count should increase with 1
- after I do an Undo, the document will decrease to original document count, so it will be zero.

Also, making tests first will make a very important thing for developer, it will give to it concrete cases how the tested classes are used and this will mean a real feedback. Also, using a tested class and if the specified have the tests passed will mean that the problem is from another part of application or how the developer relationate with the class. This will remove a lot of assumptions about what breaks and why.

A great thing also is that tests are often much smaller than the entire application. So debugging it will reduce to debugging of the latest usage of the document, is not the entire NaroCAD that may not work with Undo function.

How about regression testing?
Regression Testing (RT) is almost the same as TDD but when application crashes (for instance to an user), there is a test written "to not happen again". RT has some downsides:
- the developer (or QA) who writes the test can get the application architecture. This may mean that we can create a test to pass our code, instead our code to pass our tests
- in development time, there are no tests to check if a component is working (excluding the developer will debug the program again and again)
Should you use RT? Yes, definetly! RT appears in ways that application you have never expected to crash or to run, does so, and where you don't want it. Also, the user bugs are great to be tracked. A flawed component will most probably crash again. There is a QA 80/20 theory which says: 80% of errors are in 20% of code. TDD will say that for your cases your code will work correctly. But still there are your tests, which may not reflect the real life cases.

As in our example: who creates a single document to store only one integer in it?

No comments: