In my previous post, I described how pull systems work. I made the statement that work is only done in a stage in a pipeline when there is a request from the downstream stage. I.e. I only develop some code when QA has asked me for some artifacts to test. I also made the statement that if there is a request from a downstream stage, and no work needs to be done to fulfil that request, then the ‘conversation’ should ‘return’ immediately.
I contend that test-driven development (TDD) is a pull system. I make a request, and I then fulfill that request. I write a test as a specification of what I want to achieve, and I then write code to fulfill that request.
According to TDD best practice, I write code only when I have a failing test. This is indicative of a pull system. I write the code only when I need to. This need is represented by a failing test.
TDD ensures that you don’t waste time writing unneeded code. All code written must be justified by the ‘failing test’ rule. Otherwise it is considered superfluous, and does not contribute to satisfying the request from the downstream process.
If you write more code after all your tests have passed, you are not adding value and you are being wasteful.
Think of TDD according to this example: instead of producing an engine and then trying to fit it into a car (a set of requirements), rather start off with the car, and have the test ‘does my car go?’ You only need an engine to make a car go. If the car goes without an engine (highly unlikely), you don’t need the engine. Production of the engine can be justified (ie represent value) only when there is an explicit need for an engine. Code is only needed when you have a failing test.
You may refine your test to include economy and performance requirements etc: “My car needs a certain maximum speed. To achieve that, I need an engine which produces 100kW. Does my engine produce 100kW?” If your car needs an engine that produces a maximum of 100kW, it is wasteful to design an engine that produces 120kW. When designing the engine, you have explicit requirements you need to satisfy. Producing more than what is required is wasteful, and is not ‘pull’. If your tests aren’t failing, don’t write more code.
Similarly, if you are designing a new car that needs an engine that produces 100kW and you already have one sitting on the shelf, don’t design another one – if you add a test which passes without any new code, don’t write more code.
TDD simply provides a philosophy for specifying your requirements, and a mechanism for evaluating whether you have satisfied those requirements (i.e. does my engine satisfy all my requirements?). It provides a means for simulating requests that the code’s production clients will expect to be satisfied (e.g. drive-train and throttle interfaces). TDD makes those requirements explicit and measurable.
To conclude, TDD is a pull system. When practicing TDD correctly, code is only written when there is a failing test, signifying an explicit need for some work to take place. There should be direct traceability from each line of code to a TDD test, from each TDD test to a user story/MMF/BDD test etc, and so on up the line. The value stream then becomes explicit. Any code which can’t be traced back to a test (requirement) can then be considered waste and should be eliminated. (Obviously there will be some infrastructure code etc).
Some more notes on TDD as a pull system:
– A failing test is usually signified by ‘red’. This red serves as a visual trigger for action: we need to write some code. ‘Red’ is therefore a Kanban.
– The ideas discussed here don’t address the problem of how to arrive at TDD tests. In the car example, the tests are put forth as natural langauge: “Does my car go”?, “Does my engine make at least 100kW?” In reality, it is often difficult to translate user requirements into low-level unit tests required for TDD. Techniques like Doman Driven Design (DDD)’s ubiquitous language and Behaviour-Driven Design (BDD) can help towards this.
If we’re talking about engines, unless we can agree what ‘100kW’ means and how we measure it, we can never be sure that we’re actually satisfying our requirements, and we will fail only when we try put the engine in the car and drive away. At this point, it becomes very costly to try change the engine or the car.
– We are lucky that we can use these self-same tests for automated regression testing.