Tuesday, April 08, 2008

The Importance of Writing the Test First

I had a skype call today with Rob Conery and Scott Bellware (twitter) today to talk mainly about how to improve the “TDD message” of his latest ASP.NET screencasts.

One of the points of feedback Rob got on his screencast is that he had stubbed the code he wanted to test before actually writing the test.  While there was no “meat” of the code he wrote, there was still code.  And that code had some sort of design baked in already.

The topic in question was about Products.  Rob had a fictitious requirement that the discount should be displayed next to the price.  So he created something like this as a stub:

public class Product
{
	public float DiscountPercent { get; set; }
	public decimal ListPrice { get; set; } 
	public decimal AdjustedPrice 
	{ 
		get { return 0.0m; }
	}

	public decimal DiscountAmount
	{
		get { return 0.0m; }
	}
}

The intent was to test that a discount was properly applied to the product.  Of course, the test fails initially, and then you can easily go make it pass, but the first activity performed was stubbing out code, and that is design.

Instead, a failing test should show with absolute opactity that there is even a need for those properties.  The fact that you’ve written a test specification that says:

A Product with a discount percent of 10 and a list price of $100 should yield a discount amount of $10.

This becomes executable, and you know immediately that you have to implement those properties. 

“But wait?” I can hear you say, “…didn’t you just come to the same end result?”  Well, yes, here I did.  But what if while writing this code you realized that you really need to calculate this amount when you are calculating an order total, then you might introduce an order object.  Or you might have a completely different need.  The point here is that you shouldn’t just assume that you’ll need those 4 properties, or this 1 method, because you’ll likely be wrong.

This also brings out the point of having good test naming.  The test name should be so descriptive, it’s like a rule for the application.  Any new developer can come onto a project and decipher the meaning & intent of broken tests.  That is a very valuable thing to have.

If you recall the original fictitious requirement, it was “the discount should be displayed next to the price” you can see that it is a UI requirement.  It doesn’t necessarily dictate the model that you must use.

In fact, you might even come to the conclusion to use a data transfer object as a presentation model and then this piece of information could literally come from anywhere.

TDD is about design.  You’ve heard that a million times.  The obvious corollary benefit is the enforced existence of automated tests for every possible specification.  Even if you intend to write the tests immediately afterwards, you may have missed out on some critical aspects of design, such as loose coupling, or eliminating waste.  You’re also tempted to just not write tests at all.

If you skip on the test-first nature of TDD, then you’re really just unit testing, and you’re missing out on the real design benefits of TDD.

I also want to point out that I’m not picking on Rob.  He’s had way too much of that.   I think he’s doing a great job and (if done correctly) he can definitely reach a larger audience than I can, seeing that he works for Microsoft.  I also think it takes guts to submit your work to the world and, in the face of some pretty harsh criticisms, stand up and say… “Hey, why don’t you get on skype and we can talk about how much I suck!” — Keep it up, Rob!

So here’s a challenge:  those of you who think that Rob did a “disservice” to the community, I’d like to see your screencast on the subject.  Obviously there is a gaping void instead of widespread adoption, so we as a community could definitely use the material.

Tags:
Tuesday, April 08, 2008 9:50:52 PM (Central Standard Time, UTC-06:00)
Hey Ben - thanks for the write up :). A small correction to the post here - my stub code was this:
public decimal AdjustedPrice
{
get { throw new NotImplementedException("write a test"); }
}

public decimal DiscountAmount
{
get { throw new NotImplementedException("write a test"); }
}

I knew this was going to start a bit of a riot - didn't think I'd be asked to apologize though :).
Wednesday, April 09, 2008 7:50:55 AM (Central Standard Time, UTC-06:00)
Haha,

Yeah I was trying to recall exactly what it was... *definitely* better to throw than to return zero for a stub without tests yet!
Wednesday, April 09, 2008 9:35:41 AM (Central Standard Time, UTC-06:00)
Ridiculous that people get so bent out of shape over someone trying to learn from the community we are a part of. Isn't that the point? My opinion is that if someone hears "My name is Rob and I work for Microsoft" and automatically takes every word that comes out of his mouth as the gospel then they are bound to fail anyhow. That is laziness that they don't want to have to do any of their own research, they want someone to come out and tell them how to do it and then complain when it's wrong. Get a grip people. Public perception is that Microsoft thinks they know everything and are the experts, I personally have not experienced that attitude with Rob or any of the other prominent faces in this corner of the company. In fact, I have seen more "here is what I have learned" or "I'm learning and these are my thoughts" (as is the case here) from these guys. I can't recall any instance to the contrary.
Wednesday, April 09, 2008 12:53:24 PM (Central Standard Time, UTC-06:00)
Might be a dumb question, but in a static language like C# how can you write tests and compile them before stubbing a non working implementation of the code you want to test?
I mean, it will not compile, and you will not have the intellisense.
In Ruby you can do it because the test will fail for the missing method, but with C# you will detetct the error at compile time, not at test time.
Wednesday, April 09, 2008 4:16:10 PM (Central Standard Time, UTC-06:00)
IMHO, that is the point - the code fails to compile and you put in enough "meat" to make things compile. Next you try to solve the failing test. At least, that is the way I do it.
Carlton
Friday, April 11, 2008 12:12:19 PM (Central Standard Time, UTC-06:00)
Ben, while *we* have heard that TDD is all about design 1000 times, the majority of devs haven't. Keep repeating the "good news" every chance you get.

++Alan
Friday, April 11, 2008 12:27:28 PM (Central Standard Time, UTC-06:00)
@Simone

The point is, write the test how you want it to look and it will of course not compile. Here you're not bound by any predisposed idea of how the system should behave.

If you think you need a class call FooXYZExtractor, I'm going to ask you to show me a test that shows that you really need that class.

Compilation is just the next step in the process. With R# (visual studio also does this primitively) you can generate the members directly inside the file just to satisfy the compiler. Run the tests, add some more code to make it pass, refactor. The code will eventually start to settle, and that's when I break it out into it's own file.
Friday, April 11, 2008 5:29:45 PM (Central Standard Time, UTC-06:00)
@Ben:
yep, makes sense: if you start writing classes before tests you are designing your app before the tests...
Pardon the dumb question, but I'm trying to get a "real" grip on Agile practices. I agree in theory, but I never had a chance to apply it to a real project, also because I never someone that previously used Agile.
That's why I'm also trying to start to apply the various pieces of the Agile methodology one by one (CI, Unit Test, and then move to TDD and so on...). That's one of the reasons why I'd like (if I find the time to do it) to contribute to CodeCampServer.
Anyway, thank you for the answer... R# is really an help here.
Saturday, April 12, 2008 8:09:32 AM (Central Standard Time, UTC-06:00)
@Simone

Definitely! We'd love the help on codecampserver.
Name
E-mail
(will show your gravatar icon)
Home page

Comment (HTML not allowed)  

Enter the code shown (prevents robots):

Live Comment Preview