Lets illustrate this on sample situation. The goal is to implement an application. The specification mention multiple text boxes and buttons along with many use cases in which in which sequence of inputs and button presses are mentioned to get desired outputs.
Lets see how Josh, novice programmer right out of college, full of energy and passion, would tackle this problem:
- He creates the UI, which is obvious considering the whole spec is written for UI.
- He goes through the use cases and, knowing he fully understands them, starts implementing the code.
- As he is reaching completion, he figures out he should try a use case if it works. And to his surprise, it doesn't.
- So he implements the code as to fix the use case and repeats 3.
- As he reaches what he believes to be end of the implementation, he decides to do some previously done use cases, and they all work. If any of them doesn't, then he goes to 3.
- As he commits the code, he doesn't do any other changes, because then he would have to go through all the use cases, which would take him quite some time, considering they all require entering many values. And he doesn't have that time right now.
And now, lets see how Sarah, self-styled software craftsman with many years of experience in design of software, would do it:
- She realizes that UI is just a detail and that it is not important, instead she creates a library that would contain the logic of the calculations described by the spec.
- She then picks simplest use case and implements a code so that it simulates the behavior required by spec. Button clicks as method calls and text boxes as properties.
- She then implements the code so that the above created code succeeds.
- Satisfied with the result, she picks next use case and goes back to 2.
- As she is reaching last of the use cases, she often runs the code that simulates the behavior defined by spec. And because this takes less than a second, she can do it after every small change, making sure she didn't break anything in the process of implementing new use case.
- As last step of implementation, she creates the UI and wires it up to the library she created and runs the application once to make sure the wireup is correct.
- Before she commits the code, she analyzes the code from point of someone who is seeing the code for the first time, changing it so it is much easier to understand. She can do this, because after every change, the simulation code will tell her if she didn't break something.
We can all see what the output of the two developers will be. Josh's code will most probably be broken (eg. not following the specification), hard to understand and anyone inheriting his code will curse Josh's very existence. Sarah's code on the other hand will be working according specification, be easy to understand and anyone taking it over would know what the code is supposed to do even if original specification is lost to time.
But that is not what this article is about. It should be obvious that the workflow in both of those cases is extremely similar. Even though Josh tried to implement the logic in one go, he soon drifted towards cycle of testing one use case and implementing the code so that single use case works, just like Sarah. Except in Josh's case, he has to start the application and enter the values manually every time he wants to test the use case. So while Sarah did spend some time writing more code, she spent less time than Josh, who is entering the values manually every time. It then comes as no surprise that they both follow the same workflow:
- Define a test case.
- Implement code so the test case can pass.
- Ensure test case passes.
- If it passes, go to 1. If not go to 2.
The incompetent developers simply define their test case as "Enter values and click buttons" instead of automating it in code, thus making the Ensure test case step extremely laborious and time consuming. Thus making it hard to repeat reliably and often.
There are only two cases I can think of where this workflow is not used. Developer is either able to write the whole implementation the first time and then execute the manual test cases after the fact. In this case, I call him liar. Second case is situation where developer doesn't bother repeating the previously finished use cases, in which case the code will be broken, because those use cases will not work as specified. As such I call him fully incompetent.