In the previous post I discussed the tools I will be using in this series; Visual Studio 2013 Preview as my development environment, NUnit as my unit testing framework and JustCode as my test runner. I also introduced a requirement and wrote my first test to capture that basics of that requirement. In this post I’ll demonstrate one of the most important concepts about TDD by writing just enough code to make the first test pass. I’ll then add more tests to introduce more functionality to our library.
In the last post I presented you with a single requirement:
“Create a library method that takes in a sentence and a single character as parameters. The method should return a number that indicates how many times the character appears in the sentence.”
From there I wrote a test to verify the “happy path” for this first requirement:
1: using System;
2: using System.Linq;
3: using NUnit.Framework;
5: namespace ThirtyDaysOfTDD.UnitTests
8: public class StringUtilsTest
11: public void ShouldBeAbleToCountNumberOfLettersInSimpleSentence()
13: var sentenceToScan = "TDD is awesome!";
14: var characterToScanFor = "e";
15: var expectedResult = 2;
16: var stringUtils = new StringUtils();
18: int result = stringUtils.FindNumberOfOccurences(sentenceToScan, characterToScanFor);
20: Assert.AreEqual(expectedResult, result);
(get sample code)
The test is ready, so the very first thing I want to do is try to run my test. This is a key part of the TDD workflow; write a test and then run that test immediately to see it fail. Most of the time it will (and should fail). The truth is a failing test, especially a failing that that has just been written for functionality that should not be in your application gives us a lot of information.
If I write a test for a feature that should not be in my code and it fails, it tells me a couple things. First of all it can help me verify (verify, NOT guarantee) that I’ve written the correct test. If the test were to pass immediately then there is a good chance I’m not testing the correct piece of functionality, as that functionality should not exist and the test should not pass. This may seem like a silly reason now as our requirement, test and code are very simple. But once you start working on larger and more complex systems it can sometimes be difficult to write a test that exposes the lack of a specific feature, especially if you are unfamiliar with the codebase or functionality of the application you’re working on.
The corollary to the first reason to run the test and see it fail is to ensure that I’m not duplicating someone else's work. In some cases, especially on larger projects, it possible that the same piece of functionality can somehow be scheduled twice. In this case a passing test will indicate that I’m about to duplicate someone else’s work. I’ve seen many developers in this situation who did not practice TDD forge ahead assuming they needed to write some code to implement a feature only to create a mess by having the same steps duplicated, usually in completely different ways and sometimes in the exact same methods as the original implementation. This can lead to a mess of unreadable, unreliable and un-maintainable code that nobody wants to touch in a hurry.
Before I can run my test I need to compile my solution and right away I can see my first problem (Figure 1):
Figure 1 – Compilation error
By definition if your solution does not compile your test has failed. In this case root of the failure is that the StringUtils class which I try to instantiate on line 16 in the example above does not exist.
Now we come to a bit of a cross roads in TDD; where do we create our class under test, in this case the StringUtils class. Our choices are to create it in the same project as our unit test (some developers actually like to create them in the same file) or do we create a new Class Library project (where our StringUtils class will eventually end of anyway) and create it there? There are benefits and drawbacks to each approach.
Strictly speaking, a major concept of TDD is the idea of not implementing functionality until it’s needed. This helps keep your code clean and free of unnecessary “clutter code” that can make an application more complex and difficult to understand that it needs to be. The idea is that even though you are developing a unit of functionality for the application, the application itself doesn’t need it yet. You don’t actually add the functionality to the application until you need it. When you need the class in your application (ideally in order to satisfy another test) you simply move it to the Visual Studio project you want it to be a part of.
Conceptually I agree with this. But I also understand the argument, that for the sake of pragmatism, you can go ahead and create the new class in the Visual Studio project that it will end up in anyway. A benefit of this is that you are building the class where it lives and don’t have to worry about moving it later. Depending on your project methodology and how you “slice” features you may not only be responsible for creating the current piece of functionality, but also the user interface or upstream process that relies on it. If that’s the situation you can make a case that you know you’re going to have to move it sooner as opposed to later, so why not just put it there in the first place. Yet another benefit is that sometimes when code is developed in the test project its existence if forgotten and at some point in the future another developer ends up re-developing the functionality. This can be mitigated with good test and code naming standards and good project management, but it is still a risk.
The point is that there are good cases for both and you should do what works best for your particular project. In this case I’m going to create the class in my unit test project as it is not only conceptually the cleaner way to do it, but in this case it also happens to be the easiest and more pragmatic solution.
I need to add a new “cs” file to my project. If I’m using JustCode I can use the file template functionality by right clicking on the ThrirtyDaysOfTDD.UnitTests project and selecting JustCode –> Expand File Template option (or by using the Ctrl + Alt + Ins keyboard shortcut). Doing this presents me with JustCodes Available File Templates list (Figure 2)
Figure 2 – Available File Templates
As you can see there are a variety of templates available. You can also add your own templates, which I will address in a future post. For now though I want to select the C# Class option. I’ll replace the suggested class name with the name of my class, StringUtils as shown in Figure 3:
Figure 3 – Create new “Class” dialog
I click the OK button and JustCode creates a simple class for me:
1: namespace ThirtyDaysOfTDD.UnitTests
3: public class StringUtils
(get sample code)
Creating the class was the simplest thing that might make my test pass, or at least get me past the current problem. When I attempt to recompile again I see I still have an issue as shown in Figure 4:
Figure 4 – New error
Based on this error the next thing I need to add is a method definition for FindNumberOfOccurneces in my StringUtils class. If I’m using JustCode I can put my cursor on the call to the method in the test (line 18 in the listing above) and press Ctrl + ~ which his brings up the JustCode Visual Aid Menu as seen in Figure 5:
Figure 5 – The JustCode Visual Aid menu
As you can see here, JustCode is offering to help us out by creating the FindNumberOfOccurences method for me:
3: namespace ThirtyDaysOfTDD.UnitTests
5: public class StringUtils
7: public int FindNumberOfOccurences(string sentenceToScan, string characterToScanFor)
9: // TODO: Implement this method
10: throw new NotImplementedException();
Compiling the application again succeeds, so it’s time to actually run the first test. I will be using the JustCode test runner for this series. If you don’t have JustCode you can download a 30 day trial here. If you do not want to use the JustCode test runner and have installed NUnit you will have access to the external NUnit test runner. Instructions for using that NUnit test runner can be found here.
Using the hotkey combination of Ctrl + Alt + K brings up the JustCode Unit test Window as shown in Figure 6.
Figure 6 – Unit Test Window
To run a specific test or group of tests I can select the test or group (in this case the ThirtyDaysOfTDD.UnitTests namespace or the StringUtilsTests type) and click the single arrow icon on the far left of the window (shown in Figure 7)
Figure 7 - Buttons to run a single group of test, all tests or debug tests
If I want to run all the tests I can click the double arrow to the right of the single arrow. If I want to debug a group of tests I can click the debug icon to the right of the double arrow.
For this example I’m going to run all the tests by clicking the double arrow (I can also use the keyboard short cut Ctrl + T, R) and JustCode will run my tests, showing the results in the Results tab of the Unit Test Window (Figure 8)
Figure 8 – Unit test run results
On the left side of the window I can see a listing of my unit tests. In this case I only have the one test, so that’s all that’s shown. I can also see some metrics about my last test run. In this case one of one tests ran and one failed. The big red X next to my test method name indicates that the test failed. The number to the right of the method name is time the test took to run, in this case three milliseconds.
The left side of the window gives us some information about why my test failed. In this case we can see that a System.NotImplementedException was thrown and according to the stack trace it happened on line ten of the StringUtils class. Here is the current listing of the StringUtils class:
As you can see, JustCode created the method for us, but we did nothing to implement it. Hence the exception being thrown on line ten. We need to write the simplest implementation that will make the test pass. Let’s review the test case again:
I want to pass in the sentence “TDD is awesome!” and the character “e” and my result should be two.
Based on this, the simplest implementation that will make this test pass is the following:
9: return 2;
Rerunning the test shows that this implementation, in spite of its simplicity, stratifies the current test (Figure 9)
Figure 9 – The test passes! Ship it!
Alright, set aside the absurdity of this example for a second. Clearly we are not ready to ship this feature; returning a hard-coded value of two is not what we were thinking when we created the requirement. But, if the test case we had was the only test case the requirement needed to support, we could say this feature is done. More importantly, we would not want to develop any more on this feature. As developers it’s sometimes hard to know when to stop. Sometimes we can’t help ourselves from tinkering with code by adding features and functionality that aren’t in the specification because “They don’t know it, but they’ll need it.”
Well, what if they really don’t need it? It’s not uncommon for developers to get into the mindset that we know better than the users. But while we may be experts at software development, we are likely not at that level of expertise in other areas like approving loans, evaluating medical records or interpreting sales data. The business user is the expert in the business. Just like we wouldn’t like it if someone from accounting or sales starting telling us how to write our code, we shouldn’t be telling them what they need to do their jobs. The business knows what the business wants and needs, and if they don’t then they will figure it out and come back and ask for it. And we should wait for them to ask and then give them exactly what they ask for. Trying to anticipate needs creates a lot of “features” and code in the system that is never used and adds no value. In consulting we referred to this as “Gold Plating” which itself was simply a term meant to describe “a bunch of work the client didn’t ask us to do, so we don’t get paid for.”
The morale of the story; let the business decided what they need. Provide them exactly what they ask for. No more, no less. If your concerned about the business user making unreasonable requests, discuss it with them, but have an open mind and remember that ultimately the application your building is intended to serve their needs. If that doesn’t work, tie each feature they request to a dollar amount. It’s amazing how equating writing coding with spending money can help to control scope.
For our example it’s safe to say that the current test case is insufficient. To enhance the quality of our code, we’ll add another test case:
“I want to pass in the sentence ‘Once is unique, twice is a coincidence, three times is a pattern.’ and the character ‘n’ an my result should be 5”
The next step is to go ahead and write a new test. I’ll call this test ShouldBeAbleToCountNumberOfLettersInAComplexSentence and add it to the StringUtilsTest class as shown in this listing (starting on line 23):
18: int result = stringUtils.FindNumberOfOccurences(sentenceToScan, characterToScanFor);
24: public void ShouldBeAbleToCountNumberOfLettersInAComplexSentence()
26: var sentenceToScan = "Once is unique, twice is a coincidence, three times is a pattern.";
27: var characterToScanFor = "n";
28: var expectedResult = 5;
29: var stringUtils = new StringUtils();
31: int result = stringUtils.FindNumberOfOccurences(sentenceToScan, characterToScanFor);
33: Assert.AreEqual(expectedResult, result);
(get code sample)
Now that our new test is written, we run it and see if fail (figure 10)
Figure 10 – The new test failed
The last time we had a failing test it was because the method under test didn’t have an implementation yet. This time our method has an implementation. But we are not getting the results we want, as shown in the details pane of the Unit Test Window (Expected: 5 But was: 2). The easiest way to make the test pass last time was to simply return two. This time I think the easiest thing to do is to actually implement an algorithm to count the occurrences of a character in a string. I’m going to implement this algorithm in the FindNumberOfOccurences method of the StringUtils class:
9: var stringToCheckAsCharacterArray = sentenceToScan.ToCharArray();
10: var characterToCheckFor = Char.Parse(characterToScanFor);
12: var numberOfOccurenes = 0;
14: for (var charIdx = 0; charIdx < stringToCheckAsCharacterArray.GetUpperBound(0); charIdx++)
16: if (stringToCheckAsCharacterArray[charIdx] == characterToCheckFor)
22: return numberOfOccurenes;
This may not be the most optimized or even best way to solve this problem. But right now that doesn’t matter; it’s the simplest and I’m looking for the simplest thing that makes my tests pass. If I run my tests again I can see that this algorithm has satisfied this need:
Figure 11 – The tests pass
In a future we will revisit this sample to demonstrate a few more advanced concepts of TDD and Refactoring. But right now I know that no matter what I do to this code in the future as long as these test pass, my code still satisfies these two test cases.
While the last two posts have covered a simple TDD case, they have covered a lot of ground. In this test I demonstrated how to take a new, failing test and write just enough code to make that test pass. I also explained why as a practitioner of TDD you want to let the requirements drive your test and let your tests drive your code to avoid extraneous features and code. In the next post I’ll be discussing the SOLID principals and how they play a big part in TDD.
Continue the TDD journey:
Copyright © 2017, Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
Progress, Telerik, and certain product names used herein are trademarks or registered trademarks of Progress Software Corporation and/or one of its subsidiaries or affiliates in the U.S. and/or other countries. See Trademarks or appropriate markings.