TDD: The Antidote to Schedule Pressure Toxicity

IT MUST BE DELIVERED YESTERDAY!!!!!!  WHY IS IT TAKING SO LONG!!!!?????

It would be nice to live in a world where a developer could take an adequate amount of time to fully develop and test a feature before it is needed.  If you work in such an environment, consider yourself fortunate.  More often, I see a lot of developers working in an environment where there never seems enough time to be enough time to finish a feature.  Harassed and pressured by this schedule, developers take shortcuts in order to finish a feature sooner.  Testing?  Eh, who has time for that?  Welcome to the death of software quality.

Skull

TDD is one antidote to this schedule pressure.  No, it will not fix everything.  But it is a good consideration to help keep software quality high in spite of this schedule pressure.

Antidote

The whole problem with the traditional style of software development – developing software and then waiting until the end to test (and I include manual testing in this category, which is not repeatable… at least not like an automated test is) – is that it builds risk in your project.  Every line of code you write that is not tested has at least three problems:

  1. The potential to have bugs
  2. The code could be really difficult to test
  3. There may not be time to test it later

Instead of building up this risk with every line of code you write, you can greatly diminish it by writing the test up front.  This is the idea of TDD.  Let’s look more at this software development practice.

What is TDD?

TDD – Test-Driven Development – is the practice of focusing on tests before code, one test at a time.

Test

Here are the ‘official’ steps:

  1. Write a test
    1. It should fail, because the code to make it pass has not yet been written.
    2. Failing to compile could be seen as a failure.
  2. Write only enough code to make that test pass.
    1. Use code coverage to verify you only wrote the necessary amount of code.
    2. The test should pass after your code is complete.
    3. Think simple here.  Sometimes you can make a really stupid code change to make the test pass.  The point is, the smaller the change you can make in order to pass the test, the simpler your final solution will be.
  3. Refactor your code – both test and source code.
    1. Very important to keep code quality high.
    2. Test code is just as important as source code!  A fragile or difficult test environment makes it unlikely to be used.

The concept of TDD is actually quite simple.  In practice, it can be very challenging and frustrating (to those starting to use it).  You will probably feel as though you are going very slow.  It will likely feel like a waste of time.  The tests will probably feel very small and unnecessary.

Over time, you will find three things:

  1. Oh wow, I don’t have to go back and test very much at the end.  It’s already done!  This is an awesome feeling.  Especially when you do system testing and after very few bugs, it just works.Happy
  2. It actually ends up being faster due to not having all the integration issues at the end of your process.
  3. Writing the tests first actually helps you design the software better.  It will be more testable, which in turn usually makes it higher quality.

Estimating the time to develop a feature is hard.  Estimating the debug or integration time due to poorly designed software is even worse.  Using TDD minimizes the time spent debugging and integrating at the expense of making the initial development take longer.  Remember us talking about untested code building risk?  Every single line building risk?  This is how test-driven development takes away that risk.  We are constantly testing.  TDD makes the feedback loop very, very small.  We can very quickly determine if our latest code caused a problem.

A Quick Example

This example is not meant to be fully-fledged.  I will show a complete TDD example in a future post.  However, I want to demonstrate the concept here just to give a better idea of it.

Let’s setup our environment first (this will be a Python example and will use my Python 3.6 environment Docker container.  Create the container in that post then tag it as python-env:3.6).  Create a directory of your choosing (I named mine tdd-example) then put the following files in it:

docker-compose.yml (runs the python-env:3.6 container on the code in the existing directory)

setup.cfg (configuration for nosetests – tells it to generate code coverage)

Create a directory called “cover”.

Create a file called test_tdd_example.py that we will put our code in (see the following paragraphs).

Suppose we want to write a function that determines if a number is even.  If it is even, the function returns true.  If the number is odd, it returns false.  This is a very trivial problem to solve for many, but we are not going to solve it by writing the function first!

Following the practice of TDD, we will first write a unit test:

Now run the Docker container: docker-compose run –rm nosetests

You will see the following:

TDD Example

We have successfully written a “failing” unit test, because the function doesn’t exist yet.

In that same test_tdd_example.py file, put the following function above the TestExample class:

Run the tests (docker-compose run –rm nosetests):

Check your code coverage (open the cover/index.html file in that folder):

Code Coverage

I can see most of you now: ARE YOU MAD?  THAT’S WRONG, THAT’S WRONG WRONG WRONG!!!!  THAT DOESN’T IMPLEMENT IS EVEN!!!!

And you would be 100% correct.  The point is that we are test-driven, not source code driven.  In order to make our function more complex, we need to add another test case to make it fail (within the Test class):

Now run the tests again, and you will notice it fails.  Since we first added a failing test, now we can implement smarter code.

Now if you run the tests, they will pass.  And we are done with our requirements.

Conclusion

The point of this exercise is that if you can keep your changes stupid-simple like that, you are going to end up with a stupid-simple solution at the end.  Granted, your real-world projects aren’t going to be this trivial, but the same concept applies.  If you can break your problems into that small of detail, you are going to end up with a simpler solution.

In addition, if you make this as part of your regular practice, you don’t have to worry about not having time to test your software at the end.  ‘But this will make me slower!’  I hear you.  Yes, it will make the development process slower (potentially) than just writing the source code.  However, you don’t have to worry about time pressures at the end of the project keeping you from sufficiently testing your code.  In addition, the tests as you go along keep your design simpler and more testable.

So if management is breathing down your neck, just trust this process.  Don’t even tell them you are doing this.  They don’t need to know!  It is not their job to write the code – that is your job.  And as a true software professional, you need to give your code the care it needs.  The code is your responsibility!  Not your boss’s!  (Now, if the whole team doesn’t follow this process, you have other problems… but that’s another problem.)

So… give it a whirl.  TDD changed the entire way I think about developing code.  Generally one of the first questions I ask before developing is ‘how will I test this?’  I consider myself a decent programmer, not because I am great at programming, but because I use test tools to make my code more reliable.  I follow a process that makes me better… I’m not innately a better programmer.

Idea

I hope TDD can make as big an impact on your development career as it has mine!  Stay tuned for a future post where I got into a much more detailed TDD example.

One comment:

Leave a Reply

Your email address will not be published. Required fields are marked *