If you do any sort of test driven development, you've likely heard of the following steps
- Red: In this case means you write a test first and see the
red
(meaning failing test). - Green: This is where you actually write a tiny bit of production code to see the red test from above go green and pass.
- Refactor: Now that you have a passing test (or suite of tests) you can presumably safely refactor and apply any common code cleanup practices to the code.
The importance of the Red step.
I don't want to go in to each of these steps in detail today, but I did want to drill into the Red
step by giving you a short little example that happened to me today.
If you are interested in a little more detail on the subject check out:
- http://www.jamesshore.com/Blog/Red-Green-Refactor.html
- http://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html
If you're new to any sort of TDD/BDD/anything-DD you may not quite get how important this first step is, but hopefully the rest of this post helps...
Make sure it fails
The Red step is one of the most important, but one that a new practitioner may skip out of laziness. It takes discipline to write the failing test, run it and verify that it fails. It is so easy to write the test code AND the production code together, run it and see it pass that skipping the red step is something even seasoned veterans in the field can fall prey to.
However, if you don't see the test fail, how do you know it will ever fail?
I can tell you on numerous occasions in the past where I have written both production code and test code together, run it see it pass, just in case - comment out the production code and STILL see the test pass. Wait what?
If you are not careful you may have created a bad test and if you run this Red step first and you don't see it turn Red, you likely have a problem.
It could be a problem with the test itself, or possibly something you put in the test that is triggering something deeper in the system. It doesn't matter what the problem is, you first need to get the test to turn red before you write any production code to make it turn green.
Make sure it fails FOR THE RIGHT REASON
While it's easy to see a red bar and move on, it's also good to review the exact reason it failed. Did you get a FileNotFoundException
exception when you were expecting a NullReferenceException
? Or did you get an integer value of 10
when you were thinking at that moment it would have failed because it returned a string?
If you're writing proper tests, your red step will include a true failure case that not only just fails, but fails for the reason you would expect it to fail - at least until you go to write the production code that satisfies the tests intent.
Now a little example.
In the example below I was behaving myself and I DID run the red step first. I am using plain-ish JavaScript (I say ish here because I'm using ES6 with babel compiler).
It's much easier to make the type of mistake I'm going to highlight below with plain JavaScript than if you were using a statically typed language. You can try something like TypeScript for flow as these compilers provide a static type checker over your JavaScript. Compilers are a great first test...
Alas, I'm not doing that at the moment.
So here is what I did...
First I wrote a test:
import FeedData from '../app/feed.js';
describe("feed cache", function() {
var feedCache;
before(function() {
feedCache = FeedData.loadCache();
});
it("Feed Cache should have two items", function() {
expect(feedCache.length).to.equal(2);
});
});
When I tried to run it I got the error saying that TypeError: FeedData.loadCache is not a function
. This was great and made total sense because I haven't written this loadCache()
function yet.
Next I opened up my FeedData.js
file and added the loadCache()
function.
export default class FeedData {
loadCache() {
}
}
I left the implementation blank for now and re-ran my tests. Same error TypeError: FeedData.loadCache is not a function
as above.
That was odd, because I know I added the function but apparently it didn't think I had... some scratching... looking... hmm... Ahh ha - I had imported from feed.js
not feedData.js
.
It's subtle, but in my app feedData
and feed
are different things. So I moved the function to the correct ES6 class and re-ran the tests. I was certain this time that it may fail but at least fail for the right reason (not a missing function).
ARGGG....
Again I got the same error TypeError: FeedData.loadCache is not a function
. Ok, that was weird. Now I'm wondering if I have a caching problem in my browser, but before I try to debug Chrome's caching (kidding there, if I ever have to go that far I'm really having a rough day) I better have another look at my code.
It didn't take long in this case to realize where my issue was. FeedData
is an ES6 class and I'm calling what I thought was a static function on the class from within the test, however it wasn't declared as static in the implementation.
Adding the static
keyword below turned this function into what my test was originally expecting.
export default class FeedData {
static loadCache() {
}
}
Now, all this work just to get the first part of my red
test. What a journey it's been for something as silly as declaring a function.
It was a good reminder just how important the red step in red, green, refactor is.
Happy Testing!