Recently, I had a one-on-one session with a mentee who brought forward a pull request that sparked a long, detailed conversation about what makes a test good. We talked through everything from assert statement philosophy to end-to-end testing in distributed systems. I want to share those same thoughts with you — not just to help you improve your own test writing, but to build a deeper understanding of what tests are really for.

A Test Is Only as Good as Its Intent

Let me start with a basic truth: your assert statements should be precise. The goal of a test isn’t to confirm that every property exists or matches some value — it’s to confirm that the behavior you care about works as expected.

Sometimes that means writing 18 Assert.Equal statements. But often, it doesn’t. Maybe all you really need is one Assert.NotEqual. If the test’s purpose is to validate a single difference or boundary case, don’t clutter it with unrelated assertions. Be concise. Every assert should answer a question the test is asking.

If the reader can’t tell what your test is verifying at a glance, the test is doing too much — or not enough.

Layering in Quality Starts with Small, Intentional Decisions

One of the simplest but most effective things you can do is override the Equals method on your model classes. This one small act unlocks a massive amount of value. Instead of writing field-by-field comparisons, you can verify entire objects with a clean Assert.Equal(expected, actual).

But don’t stop there. Write unit tests that verify your Equals implementation itself. If you do, you’ll trust that comparison logic everywhere else in your codebase. That’s the kind of layered, structural quality that makes complex testing feel simple. You’re building reliability into the system, instead of duct-taping it on later.

The more you invest in this kind of internal correctness, the less you’ll need to over-explain or over-test the obvious.

End-to-End Tests Should Follow the Box

We also talked about end-to-end tests, and here’s how I explained it. Imagine your system as a house. An end-to-end test should open the front door, toss a box inside, and then follow that box as it moves through the house. It shouldn’t just verify that the door opened and the box went in — that’s not enough. It needs to watch what happens next. Does the box get handed off to someone in the kitchen? Does it get processed in the basement? Does it get filed away in a cabinet? That’s the “end-to-end” part.

If your test stops at the door — verifying only that the request returned a 200 OK—you’re not testing the system. You’re testing that a door exists. A true end-to-end test verifies what the system does with the input.

So, ask yourself: are you following the box?

Think About Failure Modes, Not Just Success

And don’t forget: real systems fail. It’s not enough to test the happy path. You need to simulate how your system behaves when something goes wrong — especially in distributed environments where multiple services depend on each other.

What if a downstream service is unavailable? What if a message queue is delayed or a dependency times out?

These aren’t edge cases — they’re expected scenarios in a distributed system. Your tests should model those failure modes so you can verify how gracefully (or not) your system responds. Maybe you fall back to cached data. Maybe you return a partial response. Maybe you fail fast and alert.

Whatever the behavior is, you should know it, document it, and test it.

These kinds of tests are what differentiate resilient systems from fragile ones. They expose design weaknesses and help you strengthen the seams between components.

Don’t Catch Errors in Tests — Expose Them

One final note we talked through: error handling in tests. Generally, your tests shouldn’t be catching exceptions unless the exception itself is what you’re testing for. Your arrangesection should set up a stable environment. If something breaks during the act phase, it should be because the code under test did something wrong—not because your test didn’t prepare properly.

Catching errors in a try/catch block inside the test usually just hides problems. A better practice is to let the test fail, or write assertions that expect a specific exception. If your Assert.Throws passes, great. If not, you’ve learned something valuable.

Conclusion

Here’s what I tell every engineer I mentor: your tests are part of the system. They’re not just scaffolding or insurance policies. They’re executable documentation that explains what the code is supposed to do.

Make them readable. Make them focused. Make them resilient. Don’t just assert values — assert meaning. Don’t just chase coverage — chase clarity. That’s how you write tests you can trust.

And when you do that, you’re not just building software that works. You’re building software that lasts.