Saturday, 12 January 2008

Inka Layout, take three

The next release if Inka is being delayed, again and again. I decided to rewrite the layout engine. I already wrote about that ( see here, for example).

In short, first I tried Need Driven development: implement it step by step, mocking out the next step before implementing the current. This became a disaster: too much upfront design for me. Without seeing the whole picture, I was never sure that it works, much less that it works the way it should.

My second attempt was much better. At least it began better. I implemented a quick and dirty solution first, then added a few more integration tests and made my solution a bit more complex, then began to refactor. The refactoring stage took forever though. I still haven't finished it in the last release, so I was too shy to publish the code. With only integration tests in place, I wasn't seeing the correct direction of refactoring, so I was doing it blindly, and it took ages to figure out why this particular test broke when I did this simple refactoring.

Now it's the third attempt, and I've already abandoned version 3a. But I don't want to talk about it yet, until I see the results. I should say that the trunk version is the same as used in the last release, since I decided to put the new experimental version in a separate branch.

Wednesday, 12 December 2007

TDDing a HelloWorld application

I'm going to write a very simple WinForms application using TDD, and I'm going to refactor it using TDD as well.

As I'm just learning good OOP design, I often don't have any idea as to how to refactor my (already working) code, and how to properly implement separation of concerns. I also don't know how much I have to cover with tests: I tried too little and was lost in refactoring, I tried too much and couldn't change my design without breaking a lot of tests, so I didn't know if everything was still working or not. So, I'm going to apply my new idea of Test Driven Refactoring and hope that it would bring me somewhere.

Here's the story. We have a form with a textbox and a button. I enter "Martin" in the text box, press a button, and a message box saying "Hello Martin" should appear.

Should I apply MVP or MVC here? Probably, but I won't. Folks say, let the tests drive your design, so I'm letting them. But first, I want to make it work as quickly as I can, so I write a simple test using NUnitForms (see below), and I quickly get the following code:


Private Sub HelloButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles HelloButton.Click

MessageBox.Show("Hello " & Me.NameTextBox.Text, "Hello")

End Sub


I quickly see that my form is doing too much. It's been created for the purpose of showing the UI elements and reacting to the user input. However, in this code it decides how to greet the user. It decides how to construct the message, and that the messagebox should be shown.


How do I refactor this code? First, let's see the test:


<Test()> Sub PressingTheButtonShowsAMessage()

Dim form = CreateForm()

form.Show()

Dim boxTester As New TextBoxTester("NameTextBox", form)

boxTester.Enter("Martin")

Dim buttonTester As New ButtonTester("HelloButton", form)

ExpectModal("Hello", AddressOf HelloHandler)

buttonTester.Click()

End Sub



Public Sub HelloHandler()

Dim messageBoxTester = New MessageBoxTester("Hello")

Assert.AreEqual("Hello Martin", messageBoxTester.Text, "Invalid message text")

messageBoxTester.ClickOk()

End Sub


Function CreateForm() As Windows.Forms.Form

Return New TestDrivenRefactoring.Form1

End Function


I see that the test's setup phase is too big. In fact, the whole Test Driven Refactoring (TDR for short) process is built around the idea that too much test setup is a code smell. Specifically, we should refactor so that our unit test's setup takes just as much information as required. For example, in this case, all we need to test the message box is the text of the message. So, we should refactor our code into two components: the first, A, passes some object, X, to the second, B, and the second displays a message box. The information is somehow "packed" into X, and our test should be able to create X directly and pass it to B to test it independently of A.

What is X? It should be created using a single string, and it should provide the same string to B. It could be just a string, but another TDR principle states that such intermediate objects should be flexible, so it's best to make it a custom object. Why not an interface? Could be as well an interface, but we have a simple data transfer object here, with no behaviour, so a custom class should be fine. Let's call it PersonData, and let's rename B to MessageBoxService.

Here's our test for the new class:


<Test()> Sub CallingTheShowMessageMethodShowsAMessage()

ExpectModal("Hello", AddressOf HelloHandler)

Dim messenger As New MessageBoxService

Dim message As New Message With {.Text = "Hello Martin"}

messenger.ShowMessage(message)

End Sub


We see that the setup portion contains just enough: the first line is an expectation which is sort of an assert, creating the two objects doesn't count, and the only nontrivial thing is setting the Text property of the Message object. Now we clearly have a messaging component that encapsulates showing a message box and has no other concerns.

But what about the rest of the application? We can refactor our it straightforwardly:

Private Sub HelloButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles HelloButton.Click

Dim messenger As New MessageBoxService

Dim message As New Message With {.Text = "Hello " & Me.NameTextBox.Text}

messenger.ShowMessage(Message)

End Sub


Our first, functional test still passes. But now we are unable to test the behaviour independently of the MessageBoxService object. It is more or less fine that we have a hardcoded Message class -- it's just a simple class with no behaviour. But hardcoding the MessageBoxService class is a real problem: at some point we'll want to switch all our message boxes to the new Vista-style dialogs, or provide a custom form, whatever. We'll have to search-replace thru all of our code to change that.

But at this very moment the problem is that we can't test the remaining piece. So, we have to inject a dependency. Now, we don't want hold a reference to the MessageBoxService inside our form class, since we are not sure we'll need it after the refactoring is finished. So, we use a ServiceLocator pattern.

First we introduce the IMessageService interface:

Public Interface IMessageService

Sub ShowMessage(ByVal message As Message)

End Interface


Next, we do something that is considered one of the worst practices: we introduce a global variable:

Module Globals

Public Services As System.ComponentModel.Design.ServiceContainer

End Module


Now we can obtain a reference to our MessageBoxService as simple as

Dim messenger = Services.GetService(GetType(IMessageService))

But of course we should set it to a concrete service somewhere in the setup code, which has nothing to do with our form:

Services.AddService(GetType(IMessageService), New MessageBoxService)

Similarly, in our test we set it to a mock service, and expect a call to its ShowMessage method.

(to be continued)

Tuesday, 13 November 2007

MUMPS language sample

Just discovered this piece of code:
QUE(RTN,DESC,START,ASK) ;
; Queues a job to Taskman with optional user interaction
; Inputs
; RTN : The full routine info to queue
; DESC : The Description for the task
; START : The start date/time (in FM format) for this job
; : If null, uses today's date/time
; ASK : Prompt the user to queue the job?
; : ASK = 0 (don't) or 1 (ask) If null, defaults to 1
; Outputs
; 0 if not tasked, or the Taskman task # if tasked
;
N %,QUE,TIME,I,X,Y,DTOUT,STOP,%DT
N ZTSK,ZTRTN,ZTDESC,ZTIO,ZTDTH
S QUE=0
S RTN=$G(RTN)
S DESC=$G(DESC)
I RTN=""!(DESC="") Q 0
S START=$G(START)
S ASK=$G(ASK)
I ASK="" S ASK=1
I 'ASK S %=1
S STOP=0
I START="" D ;
...
(see this link for the full code)

I was going to add some sarcastic comments but found myself at loss of words..