Intent Driven Development

Intent Driven Development
A personal development philosophy that treats tests as statements of intent and a contract with the future.
As someone who has been coding for more than forty years, I’ve seen a lot of frameworks, a lot of stylistic choices and a lot of concepts come and go. Along the way I learned the basics of Test-Driven Development (TDD) and after trying it a couple of times, I can safely say: I’m not a fan.
This led me down some interesting moments where I started learning iteratively what works best for me, and the only way that I can describe it is as “Intent Driven Development.”
TDD treats tests as a forcing function; IDD treats tests as a declaration of intent.
Understanding Intent Driven Development
The basis of understanding IDD comes from a re-framing of what unit tests mean to the coder and to the project. I’m a coder that I would refer to as an “additive” coder: By that, I mean I start with an idea, build on it, refactor it, and let the shape of the solution emerge over time. Usually sometime (let’s be honest) closer to the end of the development cycle I sit down and think about the test suite.
For my first pass, I consider tests that cover all logical branches in the program. This is standard stuff, making sure that if/then trees work the way I expect them to, etc. But instead of writing “expected ‘x’ got ‘y’”, I take the time to write something closer to “this routine takes ‘x’ from that place over there, and is supposed to transform it to ‘y’ for that downstream process.”
By putting in some of the intent that leads to the test, I can start shaping what the program “means” instead of merely what the program “does.” And in so building the test suite, I usually find holes in my understanding of the problem, and failed spots in my code. The two code bases start forming iteratively and intertwined. The code makes the tests better, the tests make the code better.
This process continues until I achieve the objectives of the project, effectively a (accountant’s term) “tick and bob” of the requirements list. That achieved, I go through and either explicitly put comments like:
// THIS [test]: is the key to the whole module; don't lose it
Or
// Note: all comments are sacrosanct. They are written in stone.
These serve as goal posts for future me, and other developers. But they also serve a critical function, explored below.
Version 1.0
Version 1.0 represents the moment that a promise is made. When any application is promoted to “version 1.0” that should represent a point beyond which the code never changes, by which I mean, the combination of the functional code set and the testing code should never be violated without following these rules:
Rule 1: no code can be released that violates “test suite 1.0”. Doing so is breaking the implicit contract between your software and the outside world. You can refactor. You can improve. You can speed up. But you cannot change what goes out.
Rule 2: no changes to the test suite code set may originate internally. Any changes to the tests must come from outside pressure, i.e. a requirement change, a regulatory change or a new assumption. This change in behavior represents a change in the contract between the world and your software.
Rule 3: no changes can be made to the test suite code without attribution to an actor. If there’s a change, it was caused by “someone” or “something”. And even if it was caused by a “something”, “someone” must propagate those requirements to you.
Rule 4: once the “new reality” caused by a change in the test suite achieves the new balance described in the road to version 1.0 – basically a balance between the new tests and the new code, then a new “deal” is made and identified by:
Version 1.1
(Although to be fair, if the underlying logic has changed so dramatically that nothing makes sense anymore to 1.0, well, then it might 2.0.)
At this point, this process stops being a personal workflow and starts behaving like a system. The moment you treat your test suite as a contract – as something that can’t be changed casually, can’t be changed anonymously and can’t be changed without external cause or pressure – you’ve created a structure that scales beyond a single developer. Teams can disagree about implementation. Architects can refactor aggressively. Performance can be optimized, platforms can shift. But the intent remains anchored. The test suite becomes the stable surface area where the meaning of the software is preserved, even as everything underneath may evolve.
Playing Fair with Semantic Versioning
Incorporating semantic versioning into this system, a semantic version is built from:
- Major
- Minor
- Patch
- Build
Build: build should be a link back to your system that will identify a specific function that built the program, and falls outside of the discussion on Intent Driven Development.
Patch: increment patch numbers when improvements are made to internal code, but no changes were made that impacted any tests in the test suite.
Minor: minor changes increment when changes are made that may impact the test suite, but not in architectural ways or large ways. Promoting an integer to a bigint, or expanding the width of a string would count as a minor increment. Minor releases would usually encompass a lot of small changes that are likely user-facing.
Major: a major release occurs when the contract – the test suite – dramatically changes. This may be entirely architectural, it may be an entirely different structure for the problem, and it may come about because of a new understanding of the problem space, perhaps even caused by your application. Major changes introduce new functionality, likely break old contracts or data promises, and usually have a lot of effort in them.
Conclusion
Your test suite should represent the minimum acceptance of the problem space for the project. Note that this is a “minimum viable product” and certainly does not represent a “user accepted product”. It represents the line in the sand. That line in the sand is how you build software that survives change.
And that… is how you form long-term relationships as a developer.
