Recently I was pulled onboard a conversion project for a client. The project’s purpose was to convert an existing web application written in Classic ASP to the new ASP MVC framework. We decided on MVC for the unit testability, but in recent meetings it was apparent no one on the team besides myself and my colleague were armed with Test Driven Development experience. With a tight deadline looming and the high visibility of the project, we couldn’t afford to deal with 30 different development styles holding each other up (let alone the resulting mess that would’ve made us rich off maintenance fees alone). We’d already agreed to TDD, but no one was able to grasp the concept quick enough through their own research to be productive. So I put together an email detailing the process and importance of TDD. The email went over so well, a colleague suggested I make it a blog post, so here it is.
Team,
Over the past couple of meetings I’ve gotten the impression from the questions being asked that some of you plan on jumping directly into Core/Infrastructure/Web code instead of starting where you should, in the Unit Tests.
I understand TDD (test driven development) is new to most of us and might even seem like a waste of time, but its importance and purpose really start to make sense the more you get into it. I hope to make TDD more understandable in this email by demonstrating how a use case turns into a series of unit tests and finally into a unit of functionality.
The Case
So as everyone’s heard in our meetings on the scheduler’s mechanics, one of the most important pieces of business logic for the scheduler is determining if the Project is able to be scheduled. The business rule is as follows.
In order for a project to be able to be scheduled, it has to have been approved by a stakeholder.
We use statuses to track a project’s position within its lifecycle so this translates to
In order for a project to be scheduled, its status has to be (at least for now) “approved”.
Ok so I have the case, now what? Well a few things.
1) The Project domain object (found in Core/Project/Project.cs) is a POCO (plain-old-clr-object) with behaviors such as Creating and Getting stuff related to the instantiated project.
The reason this is important is because some come from a background where domain objects are written as DTOs (data transfer objects) aka Value Objects with no behavior whatsoever, and this does come into play when writing a test. Should the functionality reside within the POCO or within some sort of a service/manager/utility class if one exists?
2) There is a Util package within the Infrastructure project.
NB. I’ve seen .NET purists insist on packaging these classes as Helpers, but their functions and purposes are synonymous.
First lines of code written
1: [TestFixture]
2: public class ScheduleCampaignTests
3: {
4: [Test]
5: public void ShouldReturnFalseWhenCheckIsPerfomedOnScheduleability()
6: {
7: Project p = new Project(new Company(), "", "");
8: var result = Check.ProjectStatus.IsProjectAbleToBeScheduled(p);
9:
10: Assert.IsFalse(result);
11: }
12: }
I have to stress again, I wrote code nowhere else but here. This is important because if you’re doing it right, you’re code won’t compile after you write a test. So what’s the next step? Make it compile! How do we do that? By creating a nested static class inside of the Check static class called ProjectStatus and create the method as follows (while I’m at it, the method name seems too wordy so I’m going to change IsProjectAbleToBeScheduled() to CanProjectBeScheduled()
From Infrastructure/Util/Check.cs
1: public static class Check
2: {
3: public static class ProjectStatus
4: {
5: public static bool CanProjectByScheduled(Project project)
6: {
7: return true;
8: }
9: }
10: }
Wait, why isn’t the Project parameter highlighted like my other classes? Oh yea I need some using statements. Wait, where’s the built-in refactoring shortcut that adds the namespace using statement for me? Oh I need to add the reference to the project… @#!$!@ doing that would create a circular dependency. Ordinarily, I’d slam my laptop shut, order a pizza, pop in my Scarface DVD and revisit my plans of going to law school, but we got a tight deadline
. So now what? Well my only option at this point is to extend the functionality of the Project POCO in the Core. So I revisit the test code and alter as follows
ScheduleCampaignTest.cs
1: [Test]
2: public void ShouldReturnFalseWhenCheckIsPeformedOnScheduleability()
3: {
4: Project p = new Project(new Company(), "", "");
5: var result = p.CanProjectBeScheduled();
6: Assert.IsFalse(result);
7: }
Building this will fail as well because CanProjectBeScheduled() doesn’t exist. So create it!
Project.cs
1: public class Project
2: {
3: // properties
4:
5: public Project() { }
6:
7: public Project(Company c, string a, string b)
8: {
9: ...
10: }
11:
12: public virtual bool CanProjectBeScheduled()
13: {
14: return true;
15: }
16: }
I guess I didn’t go down far enough on my error list because I’m getting an error now saying I can’t use the default constructor for the Company object (it’s for use with our ORM package). So we’ll need to use the overridden Constructor for our tests. However, intellisense tells me the overridden Company constructer expects an interface, and I’m starting to get a sinking feeling I’m going to have to introduce mocking in this email
. Well, lets see if I can get away with nulling everything in the Company’s constructor. Hopefully I won’t get any exceptions during the test execution. Here’s the new code:
ScheduleCampaignTests.cs
1: [Test]
2: public void ShouldReturnFalseWhenCheckIsPeformedOnScheduleability()
3: {
4: Project p = new Project(new Company(null, null, null, null), "", "");
5: var result = p.CanProjectBeScheduled();
6:
7: Assert.IsFalse(result);
8: }
Ok so I get a good build…now I run the test and………..PHEW!!! It failed for the right reason! The testdriven.net tool’s output in Visual Studio (click the link to get the add-in if you don’t already have it) says that the test fails because it expected a return value of “false” but true was returned instead. I set an assertion expecting the result to be false, but instead my method returned true since I built it that way to get it to compile. (I also knew that I was expecting a false value in my initial test so I purposefully set it to return the opposite to see my test fail for the right reason).
Now let’s make it work. Here’s the new code for the check in project.cs
1: public virtual bool CanProjectBeScheduled()
2: {
3: switch (this.Status)
4: {
5: ...
6: default:
7: return false;
8: }
9: }
Ok it builds now let’s run the test………Test passes! 
( NB. anybody with a problem with switch statements can meet in me the parking lot and we’ll discuss it!
)
But this victory is short lived. I now realize I’m only testing if a newly instantiated project object defaults to a status that can’t be scheduled (the object’s constructor sets the status to draft). How do I know if this will behave when other components like our services get their hands on a project and changes its status? Write tests! For brevity, replace the obvious for the remaining tests and the assertions.
1: [Test]
2: public void ShouldReturn(boolean)ForSchedulingCheckWhenProjectsAre(ProjectStatus)()
3: {
4: p.Status = ProjectStatus.(Project status);
5: var result = p.CanProjectBeScheduled();
6: Assert.Is(BooleanExpectation)(result);
7: }
8:
I do this for every possible status recognized in the solution.
I also rename my original test from
ShouldReturnFalseWhenCheckIsPeformedOnScheduleability()
to
ShouldReturnFalseWhenCheckIsPeformedOnNewlyCreatedProject()
I feel the new name better expresses the intent of the test.
NB. In your research you may find examples with test names with underscores separating words like:
Should_Return_False_When_etc…
or you might come across samples where the test area is prefixed to the Pascal cased test name like:
Scheduler_ShouldReturnFalseWhen…
or a combination of the two, or how its done in this project. Since we walked into this project with test-arealess (not a word) Pascal cased test names, for the sake of consistency we’ll continue along those lines.
So are we done yet? Not yet. These tests are only valid provided the ProjectStatus enumeration doesn’t change. The only way to know if this area of code may need attention if a change to the enum is made is to create a test that will tell us so.
Here’s my handling of that:
1: [Test]
2: public void ShouldReturnSixProjectStatusTypes()
3: {
4: var values = System.Enum.GetValues(typeof(ProjectStatus));
5:
6: Assert.AreEqual(6, values.Length);
7: }
8:
Currently, there are 6 statuses the app is aware of. If we add a new status to suit some unrelated functionality, this test tells us by failing. Then we repeat the process of creating a test, making it fail, making it pass, refactoring, running the test, and making it pass if it failed after refactoring.
Because of the simplicity of this case, I didn’t address an important step in Test Driven Development which is Refactoring. In short, refactoring is the process of modifying code (hopefully for the better) without changing its intended functionality. (A more thorough definition is available here). After you’ve gotten your test to pass, revisit the code and see if there’s a way you can make it cleaner or more performant. If there is, refactor in that direction then run ALL tests to ensure nothing unrelated broke on account of your refactorings.
OK. So I see how to write unit tests from your snippets, how to declare classes as fixtures and methods as tests, and I’m even beginning to understand the immediate benefits of TDD, but what are the long term benefits?
I’m done writing!
Google or Bingit up!
I hope this clears some stuff up. When I come across a test that requires mocks and expectations, I’ll write about that for you guys.
Hit me up me with questions yo!