a dish of small red berries surrounded by more of the same berries
Unrelated image from pexels.com to make this post look nicer in social media shares.

Or, let’s talk about context some more!

So last week I talked about how you can’t know if code is good or bad without knowing the context, what you’re actually trying to accomplish with that code, what was most important when it was written (priorities change, that doesn’t mean that the way the code was written at the time wasn’t perfectly reasonable for the context it was written in).

But there’s more to context than that! You also can’t know whether your code is any good in isolation. In other words, unit tests are not enough and can never tell you whether your code actually works. No seriously, they can’t. That’s because your code doesn’t work unless the whole app works, and the only way you can tell whether the whole app works is with end-to-end testing.

It’s great if all the pieces work according to your unit tests, I’m not knocking those, but they can’t tell you the whole story. Only integration tests can do that.

If you haven’t run into integration issues yet, just wait, it’ll happen :) Maybe your controller doesn’t validate user input quite the same way as the service that handles business logic does. Maybe one layer tolerates nulls and another one flips out and throws NPEs. Maybe the front-end and the back-end disagree just slightly on the format of the JSON that gets passed back and forth. Maybe one layer doesn’t return the exact error codes another layer/object/service was expecting.

Hell, maybe everything is great except for some missing assets on the frontend. That’s not covered by an integration test but it’s not really unit test territory either. Some stuff just has to be looked at by a human. Yes, it’s possible to automate frontend testing but if your UI changes very often at all then it may not be worth the trouble of reworking your tests every time it changes. Plus computers are kind of terrible at stuff like “this text doesn’t line up nicely with this text box,” so you’re going to need a human to look at your UI anyway.

It would be great if unit tests were all you need, and if you have really comprehensive unit tests you will catch most of your problems, but in the real world things break not just within components but between them. An application isn’t just a collection of models, views, and controllers (assuming you went with a MVC architecture), it’s also the interactions between those components. If you aren’t testing those interactions, you just aren’t testing enough.

Of course, even integration tests have their limits. I once broke production by changing a part of the system that I thought was unrelated to the part that broke. It was not :( (also cache invalidation is the worst). Integration tests are great for things you know are related, but sometimes you just need a human to click around in the app and notice that when you change setting A, feature B that seems unrelated doesn’t see the update.

If you possibly can, learn from my mistakes: it doesn’t work unless it all works together.