Psst, hey I got a little programmer secret….
Ready?
I think most programmers will agree that testing your code, at least in theory, is a good idea. After all, we’ve seen the public consequences of software code that hasn’t been tested enough.
The recent “heartbleed bug” discovered in an SSL (Secure Sockets Layer) protocol, was a very serious software defect. Many e-commerce and banking websites used this particular piece of software to help encrypt and secure any data getting transferred across the wire. This would most likely include important personal information about you and your credit/debit card information.
The bug had a certain defect that would potentially allow a hacker to “listen in” on this kind of valuable data. Ouch. If more testing had been done around where this particular software defect was located, I’m sure this bug could have been easily avoided.
And while I agree that testing my code is absolutely a good idea and crucial to the success of a project … to this day, I personally still struggle with being disciplined enough to test. And I’ve conversed with other software developers with the same sentiment about testing code…good idea in theory, hard to practice.
So let’s start with the definition of test-driven development and how it why it came about.
Definition of Test-Driven Development
Here’s the definition of TDD on Wikipedia: Test-driven development (TDD) is a software development process that relies on the repetition of a very short development cycle: first the developer writes an (initially failing) automated test case that defines a desired improvement or new function, then produces the minimum amount of code to pass that test, and finally refactors the new code to acceptable standards. Kent Beck, who is credited with having developed or ‘rediscovered'[1] the technique, stated in 2003 that TDD encourages simple designs and inspires confidence.[2]
What Problem TDD Addresses
Test Driven Development (TDD) is quite a change from traditional development. Before TDD came about, here’s the way I usually went coding an application.
- Pound away on keyboard till program is complete
- Start testing
- Fix the tsunami of bugs that came out of testing
The problem with this approach is step 3. I’m not exaggerating when I say ‘tsunami’. And no matter how many years of experience you’ve been in the programming game, you still end up with just as many bugs as you did as a beginner programmer.
Maybe the KIND of bugs have changed … you no longer introduce novice bugs into your code … after some programming experience under your belt, you avoid obvious bugs, but the funny thing is you begin introducing more SUBTLE and more complex bugs.
One of the novice kind of bugs I used to introduce into programs were ones related to data input. If you’re not careful, user inputted data can really wreak havoc on your application.
One of my former managers, bless her heart, used to beta test my user interface screens. Without fail, she would slam her fingers down on the keyboard and enter data, as fast as she could, into my data entry fields. Without fail, my program would eventually crash.
The fix? Simple, set maximum lengths on fields, use input masks to ensure the end user would fill in data properly (ie. ***-***-**** for things like phone numbers, and dropdown controls that would force end users to SELECT things instead of TYPE things like counties and states).
The lesson learned?
Don’t assume your end users will do what you think they will do with your application!! Those are the easy kinds of bug fixes. The hardest kinds of bugs are subtle.
They are NEAR IMPOSSIBLE to reproduce. Logic errors and data errors, and a whole army of other kinds of errors that will bring your application to its knees.
Without TDD, I would have to fix those kinds of hard to find bugs the HARD WAY. By running the application and hoping to get to the right screen and right situation to reproduce a bug. Because without successfully reproducing a bug, there’s no way to fix it!
Humans are great at intuitive, creative, out of the box thinking. Not so great at repetitive tasks that require SPEED.
Finding and reproducing bugs the way I just described, would be a SLOW and TEDIOUS task. And furthermore, humans make mistakes and miss things.
Yet, before the Test-Driven Development movement, this was the only way that a software team could even HOPE to make a dent at FINDING, let alone FIXING software defects. This was the crux of the problem before the TDD movement.
Software got bigger and more complex, but the way to find and fix software defects took even longer to finish. When TDD was introduced, it must have caused quite a stir within the software developer ranks.
TDD really sounded alien to me, when I first heard about it. You write a test FIRST, THEN you write your code? It just sounded counterintuitive to me.
How do you write a test for code that doesn’t even exist yet? What’s the point?
TDD advocates writing a test first, because it MAKES YOU THINK ABOUT THE DESIGN. It helps you focus on the end result, which for TDD, is coming up with the final software deliverable.
As a software developer, the traditional way I developed software was like this:
- Get the business requirements for my project
- Write the code to implement the business requirements
- Test
And test some more. And fix bugs. And test some more. And fix more bugs. And test some more. And yet find a whole new passel of more bugs. Tick tock. Tick tock.
Whatever original schedule was set for the project, inevitably was hopelessly optimistic. In my years of professional software development, the actual task of WRITING CODE is the EASY part.
What always seemed to make my projects go sideways was getting the business requirements wrong AND/OR testing taking more time than originally scheduled, because more than expected software bugs and defects cropped up after the CODING phase of software development.
TDD makes testing the MOST IMPORTANT element of software development.
In TDD, the UNIT TEST is the most important part of a software project, in many more ways than one. A UNIT TEST is verifiable proof that a particular section of your source code, works as designed.
There are as many ways to write a unit test, as there programming languages, but they all share some common traits.
- Arrange
- Act
- Assert
In a unit test, you typically divide it up into three major sections. The “arrange” part is where you “set up” your test. You bring in any objects and code you need to actually perform your test.
The “act” is where you actually run the core component in your code you are testing. The “assert” is where you verify whether your core component is essentially doing what it should or not.
public void BankAccountDepostIsSuccessful() {
// arrange
var user = new User();
user.userid = "johnkim";
user.password = "password";
var bankAccount = new BankAccount();
bankAccount.user = user;
var currentBalance = bankAccount.balance;
var amountToDepost = "10.00";
// act
var newBalance = bankAccount.deposit(amountToDeposit);
// assert
assert.areEqual(newBalance, currentBalance + amountToDeposit);
}
Here’s a typical type of unit test (I’ve written it in C#, but all unit tests in other languages will look very similar). You typically give the unit test a descriptive name as possible. I basically want to test that depositing some money into a bank account object, will yield a correct new balance.
In the “arrange” section, I pull in two objects I’ll need to perform the test. A “user” object, which represents the user of the bank account I want to test against. And the actual bank account object itself, which contains the code which performs the actual deposit.
In the “act” section, I want to perform the actual deposit action. This is the essential core functionality of the unit test. We are testing that the deposit() method of the bankAccount object, can successfully deposit the amount of money we are specifying.
The “assert” is the actual test. We want to “assert” (think verify), that when we deposit the specified amount of $10.00, that the new balance is equal to the old balance + $10.
If we follow the true tenet of TDD, we write the test FIRST even before we create the BankAccount and User objects! The terminology to follow this practice is referred to as “red-red-green”.
Most unit test frameworks visually tell you when a test succeeds (shows green color icon) or fails (shows red color icon). When you write a new unit test, the context is you’re writing the test for some new code you wish to implement. So writing the unit test first means the code is guaranteed to fail, because the code you are testing for, doesn’t exist!
So the unit test framework will show RED. Then you start writing just enough code so that you have the actual code/objects you need to test the code. The unit test framework will show RED again. Then you go back and make the test PASS by coding whatever else you need to make that “assert” section of your unit test pass … in our case, it’s making the deposit() method of the bank account object return the correct new balance, after depositing some money.
This is the GREEN phase of test-driven development. This is the essence of TEST-DRIVEN DEVELOPMENT. It forces the developer to think about the end result… in this case, making a bank deposit function correctly.
It forces you to think about the objects and code you’ll need. In our case, it forces you to think about the actual functionality of that BankAccount object, which here is the deposit() method.
In essence, TDD forces you to think about the DESIGN of your application. Pretty useful! Yet many developers I know still resist TDD.
Why Developers Dislike TDD
I sometimes wonder why TDD came about, relatively late in the history of software development. Software development has been around, in earnest, since at least the 1950s. Yet TDD wasn’t formally introduced until the early 2000s, a full 50+ years since the birth of software programming!
You would think that TDD would have been introduced much early in the lifecycle of professional software programming, so why so late?? I’ve got a couple theories.
One, I’m a lazy guy. Testing means just one more thing to do to get a project out the door.
Second, I think many coders (myself included) would rather CODE than TEST. We’re codemonkeys! Coding is our bread and butter! We don’t need no stinkin’ tests!
Lastly, I wonder if it’s an EGO thing. TDD assumes that any code you write will inevitably contain bugs and defects. As a software developer, I like to think my code always comes out pristine and perfect. But we know it doesn’t. Even the best coders out there make mistakes in their code. After all, we’re only human.
TDD is such a fundamental shift in the way I, and many other software developers work, that it hard to change.
People don’t like CHANGE. It’s human nature. We’re resistant to it. We like our comfort zones and woe to the person who tells us we need to get out of our comfort zone to get to a better place.
[bctt tweet=”Test-driven development requires a fundamental shift in the way software developers work. ” username=”profocustech”]As much as I dislike change, I understand why TDD is important. I’ve seen the sad consequences when you DON’T test your code.
Hard Lessons Learned When You Don’t Test
When you don’t test your code, you become AFRAID of it. You’re SCARED of changing it, for fear of BREAKING it, or dependencies to your code. I’ve worked at many organizations that don’t practice TDD, or even testing your code in general.
Codebases at those types of organizations end up being SCARY.
No test coverage for that code means you PRAY that new code you add to it, doesn’t break the whole system. WITH test coverage, any new code you add to a project means you’re not worrying about breaking existing code. The more tests you add to a project, the more CONFIDENCE you gain, when you need to change it.
I can’t overemphasize how LIBERATING this feeling is. When you have the test coverage for a particular codebase, you’re free to refactor and improve it to your heart’s content, knowing that your tests will signal to you when something fails because of your changes. It’s easy to go back and fix those tests, so you can continue refactoring and improving your code.
Why You need to Mentally Prepare Yourself for TDD
But this kind of benefit doesn’t come for FREE. It takes a radical shift in thinking, commitment and discipline to write a test BEFORE you write code. The lazy part of me is whispering in my ear to forget about the testing and just write my code.
Conclusion
So that’s the little secret of writing software. But the pain of dealing with the consequences of NOT testing your code will eventually motivate MORE developers to try out TDD. Because what we hate worse than TESTING our code is the extra time and effort we’ll need to exert FIXING our code after it’s been released out in the wild!
This is an updated version of a previously published post.