A Love Letter to Automated Testing — As a Tool for Quality

I think automated testing, particularly automated unit testing, is a misunderstood creature. Every time I run into a “Do we write tests or do we not?” discussion in the wild, the focus is around the confidence tests give one about the functionality of the code. People in favor of tests generally argue about how you gain higher confidence about how well the code functions, and people counter that with their existing approaches like QA or the fact that their management accepts bugs as part of the process.

For me, that confidence boost isn’t the point; it’s a nice result, a by-product of the real advantage. It’s like scallions on my baked potato, or the sauteed onions on my steak, or the whipped cream on my sundae. Yes, I relish those things and they give me the extra hit of dopamine that makes me feel I have to have them…but I don’t skip the potato because I only have sour cream, and I definitely don’t refuse ice cream because there’s no toppings. The garnish is just the obvious benefit to the dish, the same way the “Confidence it will work” is the crouton on the testing salad.

Valuable tests are more than logic validators. They enforce sane engineering practices, expose complexity, put a fan behind code smells, and provide documentation that will be up-to-date as long as people are running the test suite. In short, automated tests are a way to avoid poor Quality code.

Tests Help Avoid Poor Quality Code

Quick qualifier here -- working code is good code, but we all know Quality code when we're in it. Yes, I'm stealing applying concepts from Pirsig again.

Poor Quality code is difficult to test. Sometimes so difficult it’s not worth it, like an abandoned copper mine that still has plenty of ore… but it’d take an investment many times over market price to extract it safely. For me, I can use the ease of writing a test to tell me a lot about the architecture, patterns, and configuration of the codebase. It’s the quickest way to identify challenges I’m going to face.

  • “Oh. This method is new-ing up a dependency in-line. That’s…that’s going to be hard to mock.”
  • “So part of the constructor on this service class is… calling out to a 3rd party API for part of it’s configuration?”
  • “Wait. The controller is making a call directly to DatabaseA, so it can use the return to make a call to a service class that talks to DatabaseB?”

All things I’ve run into trying to write what I thought would be quick tests around legacy code, moving roughly from easiest to fix to most difficult. If you never write tests, and the app worked correctly right off the bat, and you never need to change the functionality, none of these things are problems, per se. But when was the last time you had an application go into prod without a problem? How many stakeholders have you met whose requirements are written in stone?

When I worked at a home improvement retailer, one of my jobs was to cut wood to size for customers. I was taught by a retired carpenter-turned-retailer to measure on the saw where the piece of wood would be once it was cut, then clamp scrap wood there. This gave me a guide to know when the 2×4 was cut correctly without too much conscience effort, and also prevented me from cutting too much.

For me, writing a test is a lot like clamping that scrap into place. The test is a fixed goal to hit, and you know without question and without thinking about it while you’re coding if you’ve hit it or not. If tests are failing (and that includes refusing to compile or build), the code isn’t right. Could I cut 2x4s by measuring each 18 inch length out individually? Yes, but it’s harder than just cutting till I can’t reach the scrap anymore. Could I write that class to cover all the use cases without a test for each one? Probably, but it’s definitely going to be harder.

I write tests because they force me to think about the problem, to break it down to testable pieces, and figure out how to keep it testable. I’m not trying to combine the “what” and “how” in the same thought. Tests also force me to implement that code in a way that is testable –and testable code is (typically) easy to change, easy to diagnose, easy to plug into different use cases.

Documentation

The beauty of arrange/act/assert, especially in an xUnit style test with a minimum of shared setup, is each test is explicit about how the system under test behaves under different conditions. If you’re confirming that a specific result happens based on configuration, you have to put that value in the test. If the data context has to return a specific value, you have to specify it in the test where it’s visible to anyone.

Which means that a year from now, when you need to update a switch statement — you already know what all the values correspond to, without looking up the requirements doc from two projects ago. You won’t need to spend as much time explaining the code to someone — the tests lay it all out, in all the variability. The dependencies are documented, the expected behavior of dependencies is laid out.

Recently (and this has happened more than a few times) I went to the tests as the first stop in a bug squash. I quickly realized that none of the tests covered the scenario where the main dependency throws a null reference exception; as a result, the code was just logging the exception and returning an inappropriate value. I was able to write a test that replicated the situation, and then put a bug fix in without ever actually debugging the app.

Having up-to-date tests is like having up-to-date documentation. You don’t need to debug the application to figure out what it’s doing under the hood, you already know by following the story told by the tests.

And When Coupled With Test Driving…

So all of that above is primarily based on experiences I’ve had trying to wrap legacy code, and it boils down to “Tests help me understand the code so I can improve it safely.”

But…what if you were able to avoid the whole “this needs redesigned before we can add the feature” part? What if I told you there was a way to build that same quality into your code, right from the start?

This is the obligatory TDD plug. I don’t want to harp on it — I love TDD, and even I can’t stand most of the TDD missionaries out there — but again, I view the tests the same way I view configuring the IDE, using git aliases, customizing my PowerShell profile, using Resharper. It’s a tool that allows me to work with the code in a way that drives Quality. Writing a test for an empty service class is going to keep the problem I’m trying to solve very small. And if I’m trying to solve a small problem by “using the the least amount of code to make the test pass,” I’m far less likely to over-engineer a situation. This keeps my code lightweight, flexible, and simple.

As the problems become more complicated, in the “Only update the database if these 3 conditions are true and also it’s Tuesday” vein, so does my code…but incrementally, and in a way that doesn’t break previous passing tests. I’m already avoiding regressions and we’ve never deployed this code. My code is only as complex as it needs to be (if I stay disciplined), and the fewer moving parts the fewer things that can spawn bugs.

Tests, whether before or after writing your prod code, are going to drive Quality. I just prefer to be efficient and find out I’m making a mess before I commit any changes.

Wrapping It All Up

I’ve worked in shops with no automated tests. I’ve worked in TDD shops. I’ve worked in shops that half-assed testing. I’ve learned you do not need tests to write and change working code, but that tests make the job infinitely easier. And when delivering software isn’t an absolute struggle, I write far better code.

Code that can’t be tested without a lot of work is smelly. Writing tests in that case is like opening the refridgerator door — without opening that door you never smell the fact that last week’s leftovers are ready for the trash. Tests, if nothing else, tell the story of how your code is supposed to function — far better than writing a README or walking someone through the entire application.

These two items are the things I’ve come to appreciate about automated tests far more than the pat “I know the code works because tests.”

The Almighty Card Board

If you’re a professional developer, and your team isn’t using a ticket system, you’re probably familiar with card boards. You may even be a Trello wizard–I personally have spent more time in Microsoft’s Team Foundation (now called Azure DevOps), but it’s the same idea. A way to track what needs to be done and how long it’s taking.

Since this is a (relatively) simple one-off project, I just threw together a Trello board. Here’s the 800-mile-view:

A view of a Trello cardboard with Backlog, Current Effort, In Progress, and Done lists

My backlog is the total sum of what I want to get done/cover in my post series. I have code items (labeled in orange), infrastructure items (labeled in green), and posts to write (labeled in yellow). I have them roughly grouped in order by things I want to do for a specific post.

Current Effort is what might be called a sprint–it’s the group of cards I think are obtainable to get done in the next week or two. To continue my trip analogy this is just the next leg of the trip–maybe how much ground I want to cover before stopping for dinner. It would be the point where I would feel comfortable putting in a pull request, a point where if need be I could say “I have to put this aside for three months” and be able to pick it up as a complete module of the larger project. The acceptance criteria for each “effort” is to have one or more blog posts scheduled.

In Prog is just that–in progress. Stuff that I’m still working on, since I anticipate doing little bits throughout the week.

Done should be another self-explanatory item. It’s all cards I’ve completed–not just during one effort, but over the course of the whole project.


My first step was to fill the backlog. Everything I needed. For instance, I knew I needed a simple code library to provide some sort of functionality. I also knew there’d need to be some sort of web project that can display that functionality…but I wouldn’t need to link them together yet in order to write a blog post about them, or to get the solution to build in a CI system like Travis.

I decided my first effort would look like this:

  1. Write about this plan, and my high-level thoughts on planning solo projects
  2. Create the Trello board to map the whole thing
  3. Write about the Trello board and my planning process
  4. Setup a simple code library I could test in a CI system
  5. Setup a simple web project just to have it ready
  6. Write about the code skeleton

My second effort will focus on:

  1. Setup Travis-CI to build and test my solution
  2. Write about setting up Travis
  3. Write some tests against the web project’s UI
  4. Get them to run in Travis
  5. Write a post about UI tests
  6. Wire the web project to the code library
  7. Update UI tests to reflect the dynamic logic
  8. Write about wiring up the two

After that, I’m not sure exactly. Logically, deploying the program is the next step, but I’m not going to think that far ahead. Sticking with the road trip mentality, I know where I’m going tonight, I know roughly where I’m going to be the night after that…anything further isn’t super useful to plot in detail. I just don’t know what will happen.

Honestly, I’m already second guessing how big that second effort looks. I’m a big fan of bite size pieces. But, since I don’t have a better idea and all of that is grouped logically together, I’m just going to roll with it until a better idea presents itself.

I’ll repeat this process till I get all the backlog cleared. If something needs to get added to the backlog, or needs to go back to the backlog, I’ll do it. If I learn after the next effort that 3 posts is way too much to try and fit into a week, I’ll trim the next one to account for it.

It feels pretty Agile to me, but as a millennial I hate to put labels on things, y’know…

Q: How Do You Walk From Portland, OR to Portland, ME?

A: One step at a time.

One of the major challenges I had (and still have, if we’re being honest) is looking at a project I want to do and actually delivering it as a solo dev. As time has gone on, I’ve learned that’s not exactly a problem exclusive to solo devs. One of the things that I have really appreciate at Pillar/Industry X.0 is how we approach this problem…and some of it really translates well to hobby code and side projects.

There are two key points I want to harp on:

  • Know where you’re going (what your desired end result is)
  • Do only the next thing (focus on what makes sense at the time)
Know Where You’re Going

My personal problem with a lot of side projects is I get an idea, and I start writing code for it. I don’t think about what the end result is going to be, usually telling myself “Don’t get too far ahead of yourself, just flesh it out a bit and it will become clear as you go along.” While I think this is a pretty decent way to write a novel, I’ve come to realize it’s a terrible way to write software.

There is a not-so-subtle difference between planning too much and planning too little. There’s no real benefit in planning out every single thing in advance, from the size and color of your standard buttons to how to promote your new app. But the general idea–web app or mobile? Is this going to live in Azure or AWS or Heroku or what? Am I using GitHub or BitBucket? Should I be continuously deploying this?–helps guide the architecture, the tech stack, and helps define the tasks.

Writing the code is important (sort of the point), and is broken up in it’s own way. But when does it make sense to setup a build pipeline? The repository? Do I need a production environment? When can I actually see changes when I deploy them, anyway? By keeping these questions in mind, by seeing a bigger picture than just features in the code, it helps guide the overall workflow.

Do Only the Next Thing

Having those tasks ranked and in rough chronological order helps you stay focused. Now you can say, “Self, we are going to test drive this feature here. And when it’s done? We’ll setup a build pipeline so it’ll merge into master on it’s own.” Once that pipeline is working, “Self, now we’ll add this other feature here, because when it’s done we can add it to a UI project!”

By modularizing the tasks, you’ll avoid the trap I always seemed to fall into–“I have a web app with no logic behind it and deploying it is a nightmare” or “This code library I spent a month on is useless till I get it plugged into something and I am just too tired to figure out what to do now.” Being able to focus on checking things off a list is satisfying and keeps things moving, especially when you’re navigating a full time day job.

You know you need to cross the country to get from one Portland to the other, but if you focus on that you’ll never make it. You need to focus on getting to the next stop, rather than the whole trip.

The Great Pipeline Post Project

I had this great idea to blog about setting up a CI/CD pipeline, both for much-needed practice and to relay some lessons learned. Spoiler alert: this is a much bigger deal than it seems at first glance. I sat down to get started and quickly got overwhelmed by the sheer volume of things to work through, especially as they occurred to me randomly, while I was sitting in front of Visual Studio.

That’s where this post came from. What if, instead of just breaking down how to setup a pipeline and all the steps under that umbrella, I started with how I planned this mini project? If my target audience would benefit from seeing all the pieces of CI/CD tied together it’s likely they’d benefit from seeing the pre-work too.

Over the next few posts we’ll start by exploring the planning that goes into standing up a new project and move on into the actual work needed. My goal, the whole time, will be to keep it on track and demonstrate how the joint efforts of knowing the plan and doing on step at a time keep things moving at a steady pace.

Configuration is the Real Magic

I talk about writing software being like magic. It’s how I view the craft in a lot of ways–using arcane words and phrases combined with strange tools to conjure amazing things out of a series of 1’s and 0’s on a circuit board. But when I describe my ultimate career goal as “being a wizard” I mean something, or someone, very specific.

I’m not talking David Copperfield or Penn and Teller. They are very skilled magicians, not wizards. That sort of magic is based on memorizing an illusion, of doing a thing in a certain way because of the reaction you know you’ll elicit. One does a very specific thing, or specific set of things, in a well organized way, over and over and while it gets more polished with practice, it will always be the same.

The words “programming is magic” conjure up Gandalf for me, from the Lord of the Rings universe. For the majority of those adventures, it wasn’t actually magic that made Gandalf the go-to guy–it was never “Gandalf has a spell for that.” What made Gandalf the truly formidable foe and an invaluable ally was the fact he knew things. He understood the old lore, the behavior of people and animals, how the elements of the world fit together and how they could either help or hinder a particular goal.

Gandalf knew the configuration of Middle Earth. Maybe not the *exact* configuration for every situation, but he knew what was missing, or what was the blocker. His magic was always a tool to divine knowledge or to force something into place, to adjust the configuration of a situation. Never magic for the sake of magic, happening in isolation.

“Gandalf knew the configuration of Middle Earth.”

Just like the veteran programmer on the team who always seems able to spot the problem

Continue reading “Configuration is the Real Magic”

It’s Not the Code

I saw an article recently from Code Like a Girl with a headline “Don’t Teach Your Kids to Be Coders.” It’s an excellent piece, and like most people smarter than me Dr. Johnson managed to put words to a feeling that had bothered me in all my classes at CSCC–the code is a tool, not a result.

I pick up skills very well. I’ve been praised (as an adult even) as a quick study. If I can identify a benefit to knowing how to do a thing, especially an immediate benefit, I will learn to do the thing. Most importantly, do it well enough to count. The jobs I’ve excelled at have had simply-defined goals–that type of goals allows me to determine the tasks necessary, and then start practicing the skills.

Continue reading “It’s Not the Code”

Blog at WordPress.com.

Up ↑