I wanted to teach myself to use the TDD approach and I had a project I had been wanting to work on for a while. It wasn't a large project so I thought it would be a good candidate for TDD. However, I feel like something has gone awry. Let me give an example:
At a high level my project is an add-in for Microsoft OneNote that will allow me to track and manage Projects more easily. Now, I also wanted to keep the business logic for this as decoupled from OneNote as possible in-case I decided to build my own custom storage and back end some day.
First I started with a basic plain words acceptance test to outline what I wanted my first feature to do. It looks something like this (dumbing it down for brevity):
- User clicks create project
- User types in title of project
- Verify that the project is created correctly
Skipping over the UI stuff and some intermediary planning I come to my first unit test:
[TestMethod]
public void CreateProject_BasicParameters_ProjectIsValid()
{
var testController = new Controller();
Project newProject = testController(A.Dummy<String>());
Assert.IsNotNull(newProject);
}
So far so good. Red, green, refactor, etc. Alright now it needs actually save stuff. Cutting out some steps here I wind up with this.
[TestMethod]
public void CreateProject_BasicParameters_ProjectMatchesExpected()
{
var fakeDataStore = A.Fake<IDataStore>();
var testController = new Controller(fakeDataStore);
String expectedTitle = fixture.Create<String>("Title");
Project newProject = testController(expectedTitle);
Assert.AreEqual(expectedTitle, newProject.Title);
}
I'm still feeling good at this point. I don't have a concrete data store yet, but I created the interface how I anticipated it would look.
I'm going to skip a few steps here because this post is getting long enough, but I followed similar processes and eventually I get to this test for my data store:
[TestMethod]
public void SaveNewProject_BasicParameters_RequestsNewPage()
{
/* snip init code */
testDataStore.SaveNewProject(A.Dummy<IProject>());
A.CallTo(() => oneNoteInterop.SavePage()).MustHaveHappened();
}
This was good until I tried to implement it:
public String SaveNewProject(IProject project)
{
Page projectPage = oneNoteInterop.CreatePage(...);
}
And THERE is the problem right where the "..." is. I realize now at THIS point that CreatePage requires a section ID. I didn't realize this back when I was thinking at the controller level because I was only concerned with testing the bits relevant to the controller. However, all the way down here I now realize I have to ask the user for a location to store the project. Now I have to add a location ID to the datastore, then add one to the project, then add one to the controller, and add it to ALL of the tests that are already written for all of those things. It has become tedious very quickly and I can't help but feel like I would have caught this quicker if I sketched out the design ahead of time rather than letting it be designed during the TDD process.
Can someone please explain to me if I've done something wrong in this process? Is there anyway this kind of refactoring can be avoided? Or is this common? If it is common are there any ways of making it more painless?
Thanks all!