Unit Tests as a Negative
There’s a constant debate between hacker types and PHB(Pointy Haired Bosses) types over what exatly it means to ‘design’ software. While many things in software that we consider ‘design’ are helpful in one way or another, they don’t suffice for a design like you might find in other engineering disciplines.
For example, if I gave you a ‘design’ for a bridge, a blue-print for a bridge, it would then be a completely mechanical effort after that to build the bridge. The design is a peice of knowledge that captures all the decisions that must be made to build a bridge, leaving only the physical work to be done. (I realize here I’m simplifying bridge work but stay with me…)
In software there’s no such thing, and we can prove it by contradiction. If you were to give me something that could be turned into ‘software’ with only mechanical effort, i.e., some sort of ‘design’, similar to designs in other fields, what would you have actually given me? You would have given me the CODE for that software. Compiling IS the mechanical work of software, as our field is entirely knoweldge based. There are no materials to put together once the ‘design’ is complete, and the idea of removing all decisions means you’ve specified your software so much you might as well have coded it.
Programming languages are, after all, our best attempt at describing a language that allows non-ambiguous description and determination of a system.
In other words, as the hackers have always said, “The Design is the Code”. This point of view is very attractive, but I want to add a nuance to it that might show room for comprimise between hackers and PHBs. Software starts out at a high level – if you’re doing waterfall, you start out by building requirements. If you are doing agile, you start out by gathering user stories/cases. Most of us probably start out doing a little bit of both – we need to start at a very high level description of the system we’d like to build.
This would be like being asked by a city to build a bridge over some river. You still need to scout for a location, secure funding, go through designs given to you by architects, etc. The use case phase is similar. As we drill down, we can turn use cases into smaller and smaller sequences of behavior the customer wants, or more and more detailed requirements on different parts of the system. We do this, ideally, until we get to the point that it’s more effective to use code to describe what the system should do than to use high level abstractions like sequences and stories.
But our requirements, our user stories, they are not just providing a means to drill down into what our system is supposed to do – they are levying TESTS on our system. The highest level we can call verification testing, but ultimately, for every use case, one should imagine that there ought to be an automated way to test that use case to ensure the system we are building fulfills that case.
Like plaster being poured into a mould, our software is ‘poured in’ to these implicit tests. The mould defines where our system stops. Similar to photography – when we create a photograph, we not only have the picture, but the negative. The negative defines the complete opposite of the picture, and if combined with the picture would simply look like a meaningless gray. It is through the difference between the negative and the picture that form takes place. Likewise, it is not just in code, but also in our means of testing that code, that our true design forms.
Specifications, requirements and use cases all are simply high level views of ‘test-driven-development’. A test is just the negative of the software that fulfills it, and together, the test and the software, do you have a true design. If we focused more on continually refining our requirements and use cases into actual test automated test cases, at the lowest level, then we can take advantage of TDD from the begining in.
After all, for anyone who’s done TDD, what’s the first thing you do when you start out with a blank slate? You decide what it is you want your new object to do, and then you write a test for it. A specification can be seen as a test (unfortunately they do not exist in that form very much today). A specification can be seen as a negative of the software that it produces.
For every object in your software, there should be a negative, a thing that describes the exact opposite, partnered to that object. If your software provides a function which you plug in 3 and get out 6, then you should have a negative that plugs in a 3 to some nameless thing and expects out a 6. These are two ways of describing the same thing, but as art like photography and sculpture shows, you need both to move forward.
2 Comments »