There is a misconception in the software development industry dating back decades that assumes the most valuable thing we programmers can do is type production code into an editor. That may have been true at some point. But, with the advent of high-level programming languages in the 1950s, our role began to change. By 1999, Martin Fowler put it this way:

“Any fool can write code that a computer can understand.
Good programmers write code that humans can understand.”

– Martin Fowler, Refactoring: Improving the Design of Existing Code

In other words, we’d moved from translating requirements into machine code, to translating requirements into machine-readable, structured, human language. And as such, our value proposition changed. It was no longer good enough that the code caused the machine to produce the correct answer. It was now expected that the code would make sense to other humans (including our future selves).

That same year, Kent Beck published a book of his own in which he stated: 

“Software features that can't be demonstrated by automated tests simply don't exist.”

– Kent Beck, Extreme Programming Explained: Embrace Change

That implies that tests are a mandatory part of the development flow. In fact, Beck goes on to describe throwing away exploratory code and starting over with tests. To my knowledge, that is the public beginning of the modern movement to write tests first, before writing code. He also described the long term value of tests this way: “When you have the tests, you can make more changes longer than you can without the tests.”

All these years later, experience has shown me that Beck was right. Code without tests only tells us part of what we need to know to maintain a software system. It tells us how the program functions today. But, it does not tell us what the program was intended to do. That’s where tests come in, along with another major shift in our value proposition.

By 2020, the most valuable thing we could do was to translate our understanding of system requirements into automated specifications (a.k.a. tests) written in structured, human language. The code itself was still important. But, we could restructure or even rewrite it as many times as necessary, as long as we kept the tests running.

And now, twenty-five years on from Refactoring and Extreme Programming Explained, and with the advent of AI, I would characterize our roles this way (with apologies to Mr. Fowler):

Any fool can write code that a computer can understand. Good programmers write automated specifications that prompt AI to generate the code that correctly passes those specifications.

In other words, we’ve gone from creating machine code, to creating code humans can understand, to creating tests that add to human understanding, to creating tests that add to the machine’s understanding. At this point, it's worth asking: is code even relevant anymore?

And yet, we continue to assume that the most valuable thing programmers can do is to type production code into an editor. How do I know? Because we keep asking if AI can write our tests.

The answer is no.

Automated tests are the documentation of our understanding of a system’s requirements. Copilot has no understanding of the requirements. It only knows the code. All it can do, therefore, is document how the code works today. And, in doing so, it will very likely generate implementation-specific tests that are brittle to changes in both design and functionality.

But, if we first seek to understand the requirements and then seek to document our understanding in automated specifications, we end up with behavior-centric, implementation-agnostic tests that communicate what the software is supposed to do, not just how it is currently doing it. And, in doing so, we will extend the life of our software because future developers (ourselves included) will share in our understanding of the system's requirements through our automated tests.