In this post I’ll show how well formed outcome and decomposition play together in the software development.

As I’ve said before, the most important factor in the software development goals is being specific. As far as it is achieved, the simple and recursive process is applied.

First of all, when Customer comes with a Product idea, we make it specific by specifying business goals and context. Since any non-trivial Product is, well, non-trivial (otherwise why we, Developers, would be necessary?) we have to decompose it.

Thanks to 40 year history of software development industry, we don’t have to invent it. Just use the accumulated knowledge wealth. We decompose business requirements to the software requirements, consisting of

  • architecture
  • users
  • data model
  • user-level functions or use-cases
  • user interface (screens description if the Product to be GUI or web)
  • description of how it should integrate with other software and hardware (including OS, DB and web server, as necessary)
  • security and confidentiality requirements
  • license requirements, stating how the Product would be licensed by the Customer and, therefore, what third-party components are compatible with the target license
  • other requirements, as necessary

and make each of them a well-formed goal.

Since almost all software projects are non-trivial - that is they don’t fit entirely into a single developer’s head - we have to decompose further.

Projects are about functionality, usually. When they’re about data, we call them database engineering. When they’re about user interface, we call them (user interface) design. Neither of two are the topics of this article, so we focus on functions.

Non-trivial (see above for the definition) software functionality is again too complex to implement it straight ahead and we decompose again. Each function in the function list turns into the detailed function description:

  • short nickname
  • goal and description
  • invocation method (click, REST HTTP call, function call, unix shell, etc)
  • event flow
  • tests - to fulfill the “being specific” criteria of the well formed outcome
  • tickets - how we split the function development into work chunks.

Each test set should test every possible category of a wrong input and have at least one example of a correct input.

Usually we split the function into two or more tickets. When function is complex or tests are non-trivial, we assign a ticket to each test.

Here (and only here) one may start the development: the functionality is already well formed, the Developer had agreed with the Customer what consists the correct function implementation (use tests, Luke!).

But here lies a potential showstopper. In any non-trivial project (we don’t do trivial ones, right?) the interdependencies between internal parts are quite complex, so they don’t fit a developers head entirely. Therefore, ideally, all tests are to be executed after each change to the software code.

Doing tests manually quickly turns into a nightmare: make a 5-minute change and run 5-hour tests. Gladly, we have a computer to offload the testing to.

Yes, here we get automated tests. Straight out of a well formed outcome be specific criteria!

Even more, we shouldn’t bother from this moment with thinking out our software too wide. Please, get me correctly, I’m not saing you don’t have to think about, do design it at all. I mean that you can focus now exactly on the next test that has to be implemented. And, to ease it further, we may implement tests first and then just hit make test (bond to F9 in my emacs) to see what’s wrong with our implementation and where we should head.

Yes, the distance from automated tests to a test-first development is very short.

Bonuses you get:

  • recover a work focus instantly, doesn’t matter if you switched out of the project five minutes or five years ago or you haven’t worked on it at all
  • (manager’s bonus as a conclusion of the above) easier staffing
  • your software has now a tests skeleton that doesn’t depend on your implementation
  • (developer’s bonus as a conclusion of the above) easy refactoring
  • instant delivery: you need just to check out the last version that passed all the tests and ship it

.. and so on, and so on.

So, the above shows how two simple principles of well formed outcome and decomposition lead to the test-first development.

Note that we only scratched the surface of the agility. With the test-first, you easily may implement any rational software development process, from the waterfall to the one-day-iteration XP. The only thing you won’t be able is being chaotic again.

Also note that a surrending to the frequent intent to implement tests after the code, you start playing completely different game of adjusting your goal to your results. Like in soccer, insead of heading the ball to the gate, you put the gate where your ball is.(thanks to Yuri Rashkovskii for usefull analogy)

More recent links about tests, test-driven development and how it can go wrong:

  • Useful test criteria
  • Ideal test qualities
  • More about layered testing strategy
  • How test driven development can go wrong
  • Python dependency injection test method.