Robert Cramer said:
So I'm looking at this test-driven development (TDD) paradigm, and I like
the idea of having automated tests. That idea is not new.
But according to the TDD approach, we are to write our unit tests FIRST -
even before writing the code that the test is ultimately going to
test/verify.
This is the purist approach and it has both merits and detractors. Most of
the detractors are paradigm shifts rather than actual impediments.
I do see some merits of TDD - and doing the testing first - but what I
think is a practically guaranteed artifact of this approach is that many
if not most unit tests will become obsolete [and therefore have to be
rewritten] with any non trivial refactoring effort (which is also part of
the whole TDD paradigm).
If you do a code first, design second approach, this is a risk. If you take
time to brainstorm your application and figure out use cases, flows, etc.,
there is very little danger. Organization is the key, but then again, you
should not be building an application that you do not understand.
Unfortunately, this is VERY common in the industry, as managers try to limit
design time, throw out specs that are worthless, etc.
BTW, refactoring, for the most part, does not break tests, as the majority
of it is either a) refactoring out duplicate code or b) refining current
code. If you are changing interfaces, etc., you are not really refactoring,
you are altering. Let's take an example:
if(x=1) {}
elseif (x=2) {}
elseif (x=3) {}
elseif (x=4) {}
else {}
Each of the conditions has to be walked, which is inefficient. A common
refactor would be to change to a switch (Select ... Case) in VB.
switch(x)
{
case 1: {}
case 2: {}
case 3: {}
case 4: {}
default: {}
}
This creates a state machine. That is a refactor. But, note, that there is
nothing happening here that changes my public interface, so the test still
works.
In fact, since most refactoring is trying to improve, not alter, code, NOT
having tests is a major detriment, as your tests, when written properly,
protect you against new bugs in old code.
Does refactoring ever break tests? Certainly. But, more often than not, it
is because your refactoring revealed a flaw in your test. Either you were
running the test for the wrong behavior or you have set up an overly
optimistic test, or similar.
So, if I understand correctly, we are to write unit tests, then write our
code, then refactor the code almost immediately - and bam - just like
that, many of the unit test will also have to be rewritten or refactored
because the code being tested has been refactored [perhaps refactored out
of existance as we remove duplication, encapsulate stuff that changes and
recompose it with its original pre-refactoring class, etc].
Am I missing something?
Yes. You are missing the core of TDD.
1. Design - Determine what you are building
2. Decide - Agree on functionality to be built
3. Divide - Break work down into bite sized chunks
4. Write Tests - write a test on the expected behavior
5. Write code to pass the test - red to green
6. Refactor - remove any "code smells" (repeat code being the most common)
If you start with step 4, you will probably end up rewriting your tests as
you really have no clue what you are building. In that case, return to step
one. I can even add a pre-step, which is brainstorm.
If you want to get a good idea of how to start design for customer facing
apps (and I include internally facing apps here), you can watch the free
user experience videos at
http://sessions.visitmix.com (need to have
SIlverlight installed). Some really nice sessions on design are UX04, UX05,
UX06 and UX07 (in that order), which are videos from Adaptive Path. The
middle of each is boring, as people are doing exercises, so fast forward to
the last 10-15 minutes when they start that.
Another thing to consider is unit tests are just that. They test a single
routine. You can expand this a bit, but if you are system testing, your
tests are too broad.
Let's take an example, which you might be thinking fits your "problem" with
TDD and why it does not.
You have a test that tests a User object. In your application, you will pull
the information from the database and diplay on a web page. Something like:
ASP.NET page
Welcome <asp:Label id="nameLabel" runat="server"/>
Code behind
nameLabel.Text = user.UserName;
You have a user object that is filled by some facade method. A quick stub
would be something like:
public static User GetUserFromId(string userID)
{
//Stuff to test id and pull user object from database here
return user;
}
So, you set up the following test (using Visual Studio unit test format):
[TestMethod]
public void TestGetUserFromIdSuccess()
{
string userID = "G5F765";
string expectedName = "Greg Beamer";
User actual = UserFactory.GetUserFromId(userID);
Assert.AreEqual(expectedName, actual.Name, "Name is different");
}
Suppose, however, that the test data no longer contains the same name for
that user. It fails. But, you are not supposed to be testing the database
retrieval at the business layer. You should be injecting the dependency, so
it always returns the right name. This may sound like "cheating", but you
are only testing the code in THAT routine.
Now, if you change the entire business layer and how you retrieve data, you
will break the application. But that is more likely to happen if you have
not designed it up front, at least in most instances.
Your thoughts and opinions are appreciated - specifically on the idea that
if I decide to write my unit tests first (per mainstream TDD), then many
of those tests won't be around [or will, themselves, need to be
refactored] by the time version 1of the system goes live.
I disagree. In fact, I have used TDD for years now and find that 90%+ of my
tests are still around. There are a few that have changed, but if you design
first and then code, you find that you eliminate a lot of useless rabbit
holes before you even start coding.
--
Gregory A. Beamer
MVP, MCP: +I, SE, SD, DBA
Subscribe to my blog
http://gregorybeamer.spaces.live.com/lists/feed.rss
or just read it:
http://gregorybeamer.spaces.live.com/
*************************************************
| Think outside the box!
|
*************************************************