Thursday 2 August 2007

Adventures in test-driven development Part 1, first mistake

Now that I'm starting to write my first test, I discover that I have already thought well about my future design. I made some key decisions as well. For example, I decided to decouple from the Graphics class. I also decided not to inherit from the PrintDocument class (a direct consequence of my first decision). Instead, I decided to provide an interface, let's call it ICanvas, and redirect all calls to Graphics somehow. This would allow me to avoid testability issues with Graphics (even if I mock it with TypeMock, internally it depends on other objects, so I have to dig in and specifically mock a couple of calls), and also it would allow me to substitute it for other outputs later, so that in theory I'll be able to print to PDF, Word, HTML etc., and the developers could add other outputs.

So far, everything looks so wise. Until I try to write my first test. It's supposed to be pretty straightforward: just print the word "test". However, when I try to write it, I discover that I even don't know my API! I don't have my ICanvas interface, much less its implementation, I don't have a Print method, and I don't know how to glue these things together.

So, although I already knew that I shouldn't design upfront, somehow I got into this trap again. I've been lucky that I haven't written much code before realizing that.

Now I'm going to do it by the book. Just find the simplest way to print a word and do just that. The really simplest way, it turns out, is to inherit from the PrintDocument class and override the OnPrintPage method. Then use the Graphics object to print stuff. For the moment, I have to forget all the elegant objects I invented, and follow the ugly (but quick) path. I even hardcode the word "test" in my code!

But before I do, I should write the test. Mocking Graphics isn't the simplest thing in the world, but with a little bit of Reflector I produce the following piece of code:

Imports TypeMock

<TestFixture>Public Class Tests


<Test, VerifyMocks>Sub PrintSimpleText()



Dim graphicsMock = MockManager.Mock(Of Drawing.Graphics)(Constructor.Mocked)

graphicsMock.ExpectCall("DrawString").Args("test", Check.IsAny, Check.IsAny, Check.IsAny, Check.IsAny)

Dim SystemFontsMock = TypeMock.MockManager.Mock(GetType(System.Drawing.SystemFonts))

SystemFontsMock.ExpectGetAlways("DefaultFont", New System.Drawing.Font("Verdana", 8))

Dim TestReport = New Report

TestReport.PrintController = New Drawing.Printing.PreviewPrintController

TestReport.Print()


End Sub

End Class

A few comments here for those who's not familiar with TypeMock. It creates a Mock object that "waits" till a real object of this type is created, and then "hooks" on it and lets us watch/catch/stub/fake method and property calls to this object. This contrasts to the other frameworks and also manual mocking, where you have to manually create an object which replaces the "real" object. Notice that we are mocking a Graphics object which is created somewhere deep in the System.Drawing.Printing namespace. Since the Microsoft's design is (as always) utterly untestable, we can't feed our own mock object, so our only option is trick the code with the help of TypeMock. So, first we create a mock for Graphics, then we tell it that we expect a call to the DrawString method, and that the first argument should be "test", and the rest can be anything. Same with the SystemFonts object, but we also tell it that it should return a specific value.

Now, my production code would be:

Public Class Report

Inherits PrintDocument

Protected Overrides Sub OnPrintPage(ByVal e As System.Drawing.Printing.PrintPageEventArgs)

e.Graphics.DrawString("test", New Drawing.Font("Verdana", 8.0, Drawing.FontStyle.Regular), Drawing.Brushes.Black, 0, 0)

End Sub

End Class

I've got a green, I'm happy for today, and I'm particularly ecstatic about having the word "test" hardcoded in my production code.

What I'm not happy about is that ever since I upgraded to TypeMock version4, my Zanebug GUI won't run with the TypeMock runner. So, I had to switch back to NUnit. I don't know about their engines, but I feel quite uncomfortable with the NUnit GUI. OK, it's personal.

What's next? The next useful feature seems to be the ability to print any text we want, not just the word "test". This is going to be the topic of the next post.

2 comments:

Anonymous said...

Good Luck with the testing.
You could post a request in the TypeMock forums about the Zanebug GUI - I am sure that they will help.

Tip: use the [VerifyMocks] attribute and then leave out the MockManager.Init and MockManager.Verify

ulu said...

Thanks for the tip, Eli!

As for Zanebug, I guess I should gather some more information. I've got a record in my Fusion log, saying that something was looking for TypeMock, Version=0.29797.26996.26478, but then I've got all sorts of things there. In VS 2005 I used TestDriven.Net, and TypeMock worked fine. But with Orcas, I downloaded the Express edition, so I don't have the Add-in luxury.