For 15 years I've practiced Behavior Driven Development, focusing on what to build rather than how to build it. It made me a better developer but it also made me indifferent to most new technologies. Then AI coding agents came along and everything changed.
I’ve been writing code for well over 20 years. About 15 years ago I found Behavior Driven Development and I was hooked. I’m convinced that it’s a superior development method and I’ve been practicing it ever since.
What this means in practice, at least for me and many of the great co-workers I’ve had the pleasure of working with, is that I never start a new development task by writing production code. In fact, one of the challenges is to not even think about how to implement a given feature. Instead I start by writing a feature test. A high level specification of the happy path for the feature that describes context and preconditions, how a user interacts with the system and what happens then. It can look something like this:
Feature('Blog admin', () => {
Scenario('writing a new blog post', () => {
Given('a logged in user', () => {
// Code
});
When('navigating to the new post page', () => {
// Code
});
And('entering a headline and a body text', () => {
// Code
});
And('clicking the publish button', () => {
// Code
});
Then('the new post should be visible on the blog', () => {
// Code
});
});
});What the feature test does is that it forces me to think about what I’m actually building. This often leads to questions. I find that it’s rare that I as a developer actually have all of the answers with regards to what I’m building and this type of test forces me, and often other parts of my team to clarify that. Sometimes by talking to stakeholders but most of the time by having a discussion within the development team.
Once the feature test is written and proven to fail the next phase continues. I start to allow myself to think a bit more about how the functionality will actually work and I describe that using a more succinct “specification”. They tend to look like this:
describe('new blog post view', () => {
it('produces a validation error if the headline is missing', () => {
// Code
});
it('produces a validation error if the body is missing', () => {
// Code
});
});An important aspect of both the feature tests and the specifications is that they should know as little about the inner workings of the application as possible. Preferably it should be possible to switch frameworks and ideally even programming languages in the production code without having to make significant changes to the tests. This ensures that a) I’m driving the production code based on user needs and b) that I have a test suite that is refactoring friendly.
Once the feature test and a few specifications are written the actual implementation phase commences with the goal of making both the specifications and the feature test pass. During this period I tend to find permutations and edge cases that need handling which leads to even more specifications. Should parts of the production code become complex I also add some unit tests where I allow myself to test specific implementation details.
After the feature is completely implemented I have working code which can easily be adapted to new requirements without the risk of unexpected consequences thanks to the tests. I also have a human readable specification of the system in the form of the test output.
In theory the test output is something that I can share with less technical people, such as asking a Product Owner to verify it. However, in practice I’ve found that that rarely happens. Instead it provides invaluable documentation for myself and my developer colleagues that we ourselves can use when talking to stakeholders.
One somewhat sad part about practicing this development methodology is that I’ve rarely been excited about new technologies since I started practicing it. There have been a lot of shiny new things in the world of software development the last 15 years, often in the form of new frontend frameworks, but when you focus on the actual deliverables rather than the technical implementation of it the allure fades quickly. Oftentimes such new technologies aren’t really built with the type of testing that I like to practice in mind, instead focusing on testing individual components or automating a full fledged browser which is way too slow for my taste.
Enter AI coding agents
Now however, with the emergence of AI coding agents, I’m really excited. Finally something has happened in the tech space that I believe will make a significant impact on my daily work.
I’m not talking about Copilot’s autocomplete here but the likes of Claude Code and Copilot agents. These tools are a perfect match for the development methodology that I so strongly believe in.
These days I don’t start off by writing a feature test. Instead I start the development of a new feature by writing the high level documentation of it, as if it already existed. I then delve deeper into details that I think are important, still in the same document and still from a user's perspective. Should there be any important technical details, which is often the case when interacting with a third party software, I describe those as well. I don’t however spend any time on describing the inner workings of the application, just as I didn’t when writing a feature test.
I then hand this documentation over to an AI agent along with an AGENTS.md file that describe development practices I find important. After that there may be some back and forth between me and the agent for a few minutes before the implementation phase begins during which I’m free to either have a cup of coffee or start thinking about the next feature that needs to be built.
There have been a lot of attempts at making "end-user development" work throughout history. Most of the time these attempts have failed with tools that produced unmaintainable messes that some poor developer would later have to debug, often ending up with a lot of sunk cost and a rewrite from scratch as the result. I still believe that the jury is out when it comes to end-user development using AI and I have a hard time seeing that AI will allow CFOs to write their own integration layer between the billing system, the PIM and the HR system in the near future. But I truly believe that it will free me from having to write that integration "by hand".
While this may seem scary to many developers I absolutely love it! Sure, I like writing code and I suffer from the "not written here bias" as much as anyone else. But the benefits outweigh the drawbacks to me. I get to focus on what I like the most: understanding the business needs and shipping a sustainable solution as fast as possible. The fact that it happens way quicker than if I had written all of the tests and the production code myself allows me to get the kick of seeing working code much more often than I did before.
PS. This is the first blog post that I’ve written in years. In fact I allowed my old hosting provider to shut down my old blog. Prior to that I took a very unstructured backup of it using a crawler. Now the blog is back, including all of its original content. I accomplished that during a couple of evenings while skiing during the days, all through Claude Code who wrote me a custom built CMS, deployed it and imported the old content. All according to my specifications but without me having to manually implement every detail in the OAUTH spec to allow me to log in using my Gmail account.
PS 2. While the title says “the Most Fun I've Had Coding in 15 Years” it’s not entirely true. It should really read “with the exception of Elasticsearch”. I friggin love Elasticsearch