I thought I'd share my new journey into TDD for Bricscad, of course this applies equally to AutoCAD, I just choose to use Bricscad as that's what I use.
Anyway, if you haven't read Scott Macfarlane's AU doc on testing from 2012/13 you should, it's as good a reference as any to get going.
My aim with doing this example is not just to provide another angle but also proof of concept for myself to see if I'm getting it right and to open up discussion on best practices and ideas.
Anyway, here we go.
First off we know that we are going to have to do a lot more work with the cad platform testing, we will need to use Gallio or similar. The way we are going to construct this example we are going to minimise how much we 'touch' the cad api and by doing this our testing for the cad implementation will be more of an integration testing of boiler plate than unit testing what our app is doing.
It may sound impossible but we can leave this until a lot later, maybe even last but I think we still need to keep in mind how most cad apis work and plan for this in our code.
With that in mind I've created a very simple abstraction of a Drawing class that will later wrap our boiler plate and can be tested in isolation from our application code. The code snippet below is heavily commented so take a look through and it should make sense.
We could have started anywhere with this, we could have started with our application objects but we need to store them in our 'Drawing' so this was as good a place as any to start.
Following the rules of TDD we need to:
- write the test first, run it, it should fail.
- write just enough implementation to make the test pass
- re-factor and run the tests to make sure they still pass
- next test....
Part 1 - Adding an object, test return type.
I've written all the code to make this test pass but if you deleted all the code below the tests you could start again.
To set this up just create a simple class library and use nuget and add the NUnit package to your project references.
If you have NUnit installed you can run it side by side with VS, if you have Resharper I think it integrates it into VS for you as well.
Google NUnit to get it all set up, as this is isolated from the cad system at the moment it is no different to the examples on the web.
using System;
using NUnit.Framework;
namespace TestableAppTests
{
/// <summary>
/// The code below runs some simple unit tests to test our Drawing object can have
/// objects added to it.
/// We create an IDrawing interface, this is an interface for the wrapper of the real
/// Bricscad drawing database, we could call it Database but we will keep things simple
/// and 'different' enough that we can't confuse our db with the cad one.
/// </summary>
[TestFixture]
public class UnitTest1
{
/// <summary>
/// Let's write the first test to see if we can add an object to the drawing.
/// </summary>
[Test]
public void AddObjectToDrawingReturnsLong() // we want a long returned, see below.
{
var drawing
= new MyDrawingHelper
(); // implements IDrawing interface.
// now test the AddObject method we are about to write, as we don't have
// a concrete class for objects yet we can see we need another interface
// for drawing objects. The implementation of the drawing object is not
// important yet, not for testing this MyDrawing class anyway.
//
// We need to either return some sort of result object or other device
// to tell us we have succeeded, an object id is good as we can store
// them in simple lists etc for retrieving them from the drawing object
// at a later time when needed (and follows typical cad return types as well).
var drawingObject
= new DrawingObjectHelper
(); long objectId = drawing.AddObject(drawingObject);
Assert.AreEqual(1234, objectId);
}
}
/// <summary>
/// Here is our IDrawing interface, the first thing I want to do is add things to it,
/// it's a good a place as any to start.
/// Ideally we would like to write the proper implementation details that work with
/// the drawing database proper but the goal here is to create the api for our Drawing class.
/// By creating tests first and only creating the ones we want we can defer the details until later
/// and we can continue to develop our application in isolation knowing we have 'somewhere'
/// to store our objects.
/// </summary>
public interface IDrawing
{
/// <summary>
/// Here is the method signature for our interface, already we can see we need another
/// class to represent the objects we want to add to the Drawing, we will create an interface
/// for this class as well so we can do our testing.
/// We are not interested in what or how the object works yet, that's not the purpose of these
/// particular tests, we just want to make sure we can add objects and catch any errors.
/// </summary>
/// <param name="drawingObject"></param>
/// <returns></returns>
long AddObject(IDrawingObject drawingObject);
}
/// <summary>
/// Our drawing implementation. This is basically a 'mock' and could just as easily been
/// created using the Moq library but to make things clearer for now we'll do it the hard
/// way by creating a helper class with dummy implementation. For simple objects this suffices
/// but you will soon see where using mocks will save a bit of time and code.
/// </summary>
public class MyDrawingHelper : IDrawing
{
public long AddObject(IDrawingObject drawingObject)
{
// hard code a know answer here, this is the work of the real
// database. It's easy to see we need to do some error handling from this
// which will be the next test.
long id = 1234;
return id;
}
}
/// <summary>
/// Our Drawing Object interface, we use this with all objects we ant to add
/// to the drawing.
/// </summary>
public interface IDrawingObject
{
// nothing to do here yet, will no doubt end up another set of tests for our real objects.
}
/// <summary>
/// The helper class for our drawing object to be passed in as an argument
/// Again, don't worry about this class, it's only a mock up of what we need later,
/// the point is our Drawing api will take any class that implements
/// the IDrawingObject interface.
/// </summary>
public class DrawingObjectHelper : IDrawingObject
{
}
}
The next part will be writing tests to handle errors when adding objects, our api will start to take shape.
If we had of started writing a Drawing class first we would no doubt have come up with half a dozen methods and properties without batting an eye, by writing the test first we only write what we need when and as we need it. By continually running the tests we can catch any bugs as we go and as we are re-factoring as well we end up with a nice clean code file with 100% test coverage. Pretty cool, and when it comes time to implement the real database we have complete documentation of how we need to do it and the tests are already written.