As appealing as Test Driven Development (TDD) appears, in the workplace, I have yet to be able to see it successfully applied consistently. According to the book The 7 Habits of Highly Effective People, effective habits are defined by the intersection of 3 components; knowledge, skill and desire. Given this, the hypnosis is, if all concepts of habits are explored in a TDD perspective, one would be able to effectively perform TDD.
Knowledge – What to do, why to
Test Driven Development is a development process which starts by creating failing tests first, unlike the traditional approach which is to develop then test or skip the test.
The standard workflow of TDD is:
- Create a failing test
- Code until the test passes
- Refactor the code
When tests are the first aspect of a feature, tests also become a tool of design rather than just verification.
Tests allow the developer to gain empirical feedback. As the developer deploys work, the developer is able to assert their assumptions and measure the progress of the task by executing tests and learning from the results.
Once a test has passed, refactoring code is possible since it is possible to ensure that the refactoring has not changed the behaviour of the code.
Skills – how does this work?
For the purpose of this document, I have chosen Zend Framework and PHPUnit to demonstrate how testing works. The simple project is to create a person who can greet others. By that, I mean say “hello world!”
Following the standard work-flow. Step one is to create a failing test.
…/tests/application/Models/TestPerson.php
<?php namespace Models; class PersonTest extends ModelTestCase { public function testGreet() { $person = new Modelsperson(); $result = $person->greet(); $expectedResult = 'Hello World' $this->assertEquals($result, $expectedResult); } }
Although it was simple, design choices have been made. I have decided on the name of the class, the function name and its inputs and outputs. There is a clear contract on how this behaviour should operate. Step one is now complete and as we run the PHPUnit test for this feature we expect errors!
Fatal error: Call to undefined method ModelsPerson::greet() in /tests/application/Models/PersonTest.php on line 10
Next, begin developing the feature so that the test passes. The design has been done and all that is left is to lay the bricks. As the task is developed, use the test to continuously assert that the work is complete. Typically, the following output is what you should see:
Tests: 1, Assertions: 0, Errors: 1.
Starting with a blank canvas:
…/application/Models/Person.php
I begin to develop until finally step two is complete with the following result from PHPUnit:
…/application/Models/Person.php
<?php namespace Models; class Person { public function greet() { $greeting = 'Hello World!'; return $greeting; } }
The final step enables the developer an opportunity to refactor code without fear of introducing bugs. I have made a trivial refactor to reduce the line numbers.
…/application/Models/Person.php
<?php namespace Models; class Person { public function greet() { return 'Hello World!'; } }
Again, we need to verify that nothing has broken by running the tests again and ensure that we are greeted with:
Desire – As a developer, I want to test because…
There are a number of reasons why we want to to be guided by tests when programming. Tests allow us to assert our assumptions against reality. Tests increases our confidence levels in the code behaving as we expected and as it grows through regression testing. When tests are written first, they disrupt the fundamental understanding of testing which is not only to prevent bugs but to also use it as a design tool. However if you do not already have a burning desire to write tests first, it all comes down to acknowledging that all we have to lose is bugs.
it all comes down to acknowledging that all we have to lose is bugs.
^
And time.
I should clarify, TDD is great but there is a cost in time that shouldn’t be ignored. At a certain point the time saved in fixing bugs may outweigh the time invested in writing tests but that’s not always true and it’s a fairly difficult judgement to make.
There is also a cost in maintaining tests and it’s hard to write tests that aren’t brittle. In your example, the Person class is responsible for generating a greeting. Your test means that you can’t change the greeting to anything except “Hello World” without changing the test.
If doing TDD means increasing code size by 4x and making the code more brittle that’s not a good thing. Writing good tests is hard.