What Does “Run the Tests” Mean? – How Shared Symbols Can Speed Up Team Communication & Reduce Errors

On your project, what does “run the tests” mean? What about “add a feature?” “Fix a bug?”

When passed through the brain of someone on your team, these phrases bloom into specific things — a command that you run in the Terminal on a developer’s laptop, a process that you follow to add new functionality to your app, a shared investigative strategy that you use to repair a broken part of the same.

The question is, do these phrases inspire the same actions from everyone?

What Are Symbols?

A symbol is anything that carries meaning — a word like “stop,” a phrase like “run the tests,” a color like red, an emoji like 🛑, or a gesture like 👏. Just about anything can be a symbol if it effectively communicates an idea.

Say we both understand that clapping hands means “good job.” When I clap my hands after your sprint demo, I’ve communicated the idea that you did a good job. Communication happens instantaneously but breaks down into a few distinct steps:

  1. I decide to communicate “good job.”
  2. I reach into my mental library of symbols and find one (👏) that we both understood to mean “good job.”
  3. I send that symbol your way.
  4. You recognize the 👏 as a thing in your mental library of symbols, look up the associated idea, and find that it means “good job.”
  5. You get a little dopamine hit from the recognition.

Your team, project, and application are full of symbols like this. Neglected, they lead to miscommunication, broken promises, and FUDA. But with a little cultivation, you can transform your symbols from a liability into an asset.

Why Shared Symbols Matter

Everyone is constantly stocking and restocking their mental symbol libraries. The more we have in common, the easier it will be to communicate effectively.

If we all use “run the tests” to mean “run the unit tests, the integration tests, the visual tests, and the acceptance tests on your local dev machine after migrating the test database,” then we can talk about our development process quickly. But if “run the tests” means “run just the unit tests on your local development machine” to some of us, we have to slow down and be more specific every time we want to communicate one of those ideas. And we’re definitely going to forget at some point and miscommunicate.

Shared symbol definitions are even more important when communicating across disciplinary boundaries. When a financial expert says, “We always round revenue before calculating margin,” it’s critical that the developer knows exactly what they mean. If the financial expert regularly uses nearest-even-integer rounding but the developer uses nearest-integer rounding, the code that the developer writes won’t do what the finance department needs it to do.

Similar miscommunications could happen for pretty much every other symbol in that sentence. Does “always” mean “literally every time,” or does it mean “every time, except for products we sell in Sri Lanka”? Does “revenue” mean “the money that we made by selling all of these products last year” or “the money that we think we’ll make if we sell X of these products this year”?

Two men talking at a whiteboard

Cultivating Shared Symbols on a Development Team

The holy grail of a shared symbol library is a symbol that means exactly the same thing to everyone who uses it. This almost never happens in everyday human communication, but on a software team, we can come really, really close when it comes to developer tasks.

Every team that I’ve worked on has had some notion of development tasks, from routine things like “migrate the databases” and “run the tests” to one-off things like “run a data consistency check.” Normally, these are fairly ad hoc, existing only in a developer’s terminal or as a hodgepodge of lengthy symbols that we share while pairing. JavaScript developers might know that “run the tests” means yarn test on this project, while on another project, it might mean npm test. For some projects, it might be an even longer string like NODE_ENV=test yarn build && yarn test:unit.

Holding each of those definitions for a single symbol in your head is an unnecessary mental burden. It slows down onboarding, and it leads to subtle differences between developer experiences that cause miscommunication and wasted work.

When a new developer joins my team, I don’t want them wasting brain cycles remembering the exact syntax for whatever testing framework we happen to be using that month. I want them to remember: “run the tests.” To that end, I try to wrap up every developer task into a simple, runnable script with a name that matches the symbol that we already use.

Automation

When communicating, we rarely say, “Go ahead and open a terminal and run NODE_ENV=TEST yarn build && yarn test:unit.” Instead, we say, “Run the tests.” Whenever I see that kind of translation happening regularly, I write a script. I name it using the phrase the team uses already. Instead of wasting brain cache to remember what “run the tests” means, developers can just run-the-tests.

It’s a bit like the principle of progressive disclosure behind cucumber-style testing. Write your tests in plain English so anyone can understand them. Doing so allows interested parties to understand what your test does without knowing how it works. They can then discover (at their leisure) how those tests are implemented.

In the same way, use your team’s communication to drive your automation. Listen for the phrase that the team uses to refer to a task, then automate that task with a script called “that-phrase-…”.

One-Off Tasks

We do the same thing when running one-off developer tasks. For each one, we write a script. We name that script carefully, using a phrase that the team already knows.

One-off tasks are seldom only run once. At the very least, you probably run the commands a few times while iterating to get the syntax just right. By scripting and naming one-offs, we ensure that we always do them correctly. We give ourselves permission to focus intently for a moment and then completely forget how they work. Rather than holding a lookup table in our heads, we can focus on one kind of resource at a time.

Recently, we needed to tag a bunch of our resources in the AWS cloud. We knew that we’d have to run this process in both our development and production accounts. Remembering the arcane tagging syntax for each AWS resource seemed like a huge waste of brain space. So instead, we wrote a ton of little scripts.

The next time any developer on the team needs to tag new resources, they can run tag-all-rest-apis or tag-all-kinesis-streams. When they need to know how tagging works, they can look it up instantly.

By explicitly defining and scripting the symbols that we use to talk about our work, we free up brain space to focus on the more interesting aspects of the project and make it easier to onboard new developers.