Earlier, I wrote a post inviting you to try your hand at test-first development. This post is the first in a series of how I did it. In the previous post, I had all the tests written, but here I’m starting from scratch and writing the tests as I go. Well, I’m not starting from scratch in that the classes are already set up. If you want to see what the classes look like, download the workbook from the previous post or the one at the bottom of this post.
First, create the property in CTodo that will parse the string. There’s nothing in it, but we’ll get to that shortly.
1 2 3 |
Public Property Let Raw(ByVal sRaw As String) End Property |
Write a test. This test will determine if the todo item is complete. Per the spec, the first thing in the string is an “x” if it’s complete
1 2 3 4 5 6 7 8 9 10 11 |
Sub TEST_Complete() Dim clsTodo As CTodo Set clsTodo = New CTodo clsTodo.Raw = "x (A) Call Mom @Phone +Family due:2016-05-30" Debug.Assert clsTodo.Complete Debug.Print "TEST_Complete" End Sub |
Now write the simplest code to make the test pass. I probably could have written simpler code than this, but don’t get too hung up on that. Just write simple code and don’t try to solve the next test – only this test.
1 2 3 4 5 6 7 8 9 |
Public Property Let Raw(ByVal sRaw As String) Dim vaSplit As Variant vaSplit = Split(sRaw, Space(1)) Me.Complete = vaSplit(0) = "x" End Property |
When I split the string on a space, the Complete property is set to whether the first element is “x”. The test runs successfully. Next, write a test for incomplete todos.
1 2 3 4 5 6 7 8 9 10 11 |
Sub TEST_NotComplete() Dim clsTodo As CTodo Set clsTodo = New CTodo clsTodo.Raw = "(A) Call Mom @Phone +Family due:2016-05-30" Debug.Assert Not clsTodo.Complete Debug.Print "TEST_NotComplete" End Sub |
Oh goodness, that test already runs successfully. There’s no “x”, so Complete is set to False. Next, write a test for a completed todo with a priority. Per the spec, the first element after the optional “x” is a capital letter in parentheses.
1 2 3 4 5 6 7 8 9 10 11 12 |
Sub TEST_CompletePriority() Dim clsTodo As CTodo Set clsTodo = New CTodo clsTodo.Raw = "x (A) Call Mom @Phone +Family due:2016-05-30" Debug.Assert clsTodo.Complete Debug.Assert clsTodo.Priority = "A" Debug.Print "TEST_CompletePriority" End Sub |
This test fails on Debug.Assert clsTodo.Priority = "A"
, so it’s time to write the simplest code to make it pass.
1 2 3 4 5 6 7 8 9 10 11 |
Public Property Let Raw(ByVal sRaw As String) Dim vaSplit As Variant vaSplit = Split(sRaw, Space(1)) Me.Complete = vaSplit(0) = "x" Me.Priority = Mid$(vaSplit(1), 2, 1) End Property |
The Priority property is set to the second character of the second element. The test passes. Did we break anything? Let’s see.
1 2 3 4 5 6 7 |
Sub TEST_All() TEST_Complete TEST_NotComplete TEST_CompletePriority End Sub |
Nope, everything passes so far. Time for the next test. Check the priority for an incomplete todo.
1 2 3 4 5 6 7 8 9 10 11 12 |
Sub TEST_NotCompletePriority() Dim clsTodo As CTodo Set clsTodo = New CTodo clsTodo.Raw = "(A) Call Mom @Phone +Family due:2016-05-30" Debug.Assert Not clsTodo.Complete Debug.Assert clsTodo.Priority = "A" Debug.Print "TEST_NotCompletePriority" End Sub |
It fails, so let’s write some code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Public Property Let Raw(ByVal sRaw As String) Dim vaSplit As Variant vaSplit = Split(sRaw, Space(1)) Me.Complete = vaSplit(0) = "x" If vaSplit(0) = "x" Then Me.Priority = Mid$(vaSplit(1), 2, 1) Else Me.Priority = Mid$(vaSplit(0), 2, 1) End If End Property |
If my fist element is an “x”, get the second element, otherwise get the first element. Pretty simple and the test passes. Every test I write, I add to the TEST_All()
procedure to make sure I don’t break any prior tests. The next part of the spec is an optional completion date. Let’s start with a completed todo with no priority and a completion date.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Sub TEST_CompleteNoPriorityCompletionDate() Dim clsTodo As CTodo Set clsTodo = New CTodo clsTodo.Raw = "x 2016-05-20 Call Mom @Phone +Family due:2016-05-30" Debug.Assert clsTodo.Complete Debug.Assert clsTodo.Priority = vbNullString Debug.Assert clsTodo.CompleteDate = DateSerial(2016, 5, 20) Debug.Print "TEST_CompleteNoPriorityCompletionDate" End Sub |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
Public Property Let Raw(ByVal sRaw As String) Dim vaSplit As Variant vaSplit = Split(sRaw, Space(1)) Me.Complete = vaSplit(0) = "x" If vaSplit(0) = "x" Then If vaSplit(1) Like "([A-Z])" Then Me.Priority = Mid$(vaSplit(1), 2, 1) Else Me.CompleteDate = DateValue(vaSplit(1)) End If Else If vaSplit(0) Like "([A-Z])" Then Me.Priority = Mid$(vaSplit(0), 2, 1) Me.CompleteDate = DateValue(vaSplit(1)) Else Me.CompleteDate = DateValue(vaSplit(0)) End If End If End Property |
My new test passes, but I get an error in one of my old ones. Plus this code is getting pretty ugly. When your code is ugly or repetitive, it’s time to refactor. Instead of a bunch of nested If’s, I’ll just move a pointer down the line.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
Public Property Let Raw(ByVal sRaw As String) Dim vaSplit As Variant Dim lNext As Long vaSplit = Split(sRaw, Space(1)) Me.Complete = vaSplit(0) = "x" If vaSplit(0) = "x" Then lNext = lNext + 1 End If If vaSplit(lNext) Like "([A-Z])" Then Me.Priority = Mid$(vaSplit(lNext), 2, 1) lNext = lNext + 1 End If If IsDate(vaSplit(lNext)) Then Me.CompleteDate = DateValue(vaSplit(lNext)) lNext = lNext + 1 End If End Property |
I use lNext
to keep track of where I am in the array. If the first element is an “x”, I advance the pointer. Then I check vaSplit(lNext)
rather than a specific element number. All my tests pass.
In the next installment, I keep writing tests, writing code, and refactoring.
The below workbook has all the tests and the completed Raw property. It also has a userform, but it’s not complete.
You can download TodoTxt.zip
Series:
Posting code? Use <pre> tags for VBA and <code> tags for inline.