Test Less. Test Better: The Modern Testing Stack
Fewer tests. Faster feedback.
Testing is broken; not because we don’t do it, but because we waste time doing it wrong. We obsess over unit tests that don’t matter. We write coverage reports that lie. We mock everything until our app is disconnected from reality. Then we go live and wonder why things break. Again.
The test pyramid wasn’t meant to be worshipped
The old test pyramid said: write lots of unit tests, fewer integration tests, and just a sprinkle of UI tests. That made sense when systems were simple. When most logic lived in the same codebase. When APIs were internal and your frontend talked to your backend directly.
But those days are gone. (At least for me.)
Today, your app talks to other apps. It uses third-party services, runs across distributed infrastructure, and depends on config, roles, and runtime behavior that no unit test will ever see. And yet, most teams are still clinging to pyramids. They write thousands of unit tests for functions no one cares about and skip the tests that would have caught the real issues.
Test behavior, not branches.
So let’s flip it: test what users do, not how your code is organized. Or even better: realize, that there is no strict test hierarchy
Behavior beats coverage
Your users don’t care whether you’ve tested 95% of your lines of code. They care whether their invoice gets generated. Whether they can sign in with their corporate account. Whether that file upload still works when the internet drops for five seconds.
This is why behavior-driven testing matters. Tools like Playwright and Cypress let you simulate real user actions, not just internal logic. Instead of testing, Does this function return true? you’re testing, Can a manager with restricted access approve a report when the backend service is slow? These are tests that reflect production. They exercise critical paths. They surface regressions that matter. And they do so in a language everyone (from Dev to QA to Product) can understand.
Both Playwright and Cypress are CI-native. They integrate seamlessly with GitHub Actions and Azure DevOps. You can run tests across browsers, inject user roles, and simulate flakiness, all automatically, on every PR. Behavior-first testing isn’t slower. It’s smarter. Because when it fails, you know something real is broken.
Mocks hide the truth. Contracts reveal it.
Most test environments are fake. We mock the API, stub the database, simulate users, and then celebrate that everything passes. Until production doesn’t. Why? Because mocks don’t tell you when something changed upstream. They don’t alert you when a provider you depend on adds a required field or renames a response property. The mock stays the same. But reality moved on.
Contract testing fixes that.
Tools like Pact and PactFlow don’t just mock, they verify. They let consumers define the behavior they expect from a provider: If I send this request, I expect that response. And then they check that the provider still honors that agreement. This isn’t the same as validating against an OpenAPI spec. Swagger-based tools are useful for structural checks, but contract tests go deeper. They verify real behavior across real services. You stop wondering whether the integration still works. You know it does.
Accessibility is not a nice-to-have
Most teams still treat accessibility like something they’ll get around to. Maybe someone runs a Lighthouse scan. Maybe someone files a bug. Maybe someone sues you.
Accessibility isn’t just a checklist, it’s part of quality.
- If a screen reader can’t read your modal, that’s a broken UI
- If keyboard users can’t navigate your app, that’s a critical bug
- If your contrast ratio fails standards, it’s a compliance risk
You can use axe to test accessibility. Add this to your Playwright or Cypress flows. Automate them in your GitHub Action. There is no valid reason to defer this anymore.
If your software is supposed to be used by everyone, it needs to be tested for everyone.
Test how it breaks, not just that it works
Here’s what most tests don’t do: simulate failure. We check the happy path. We test with perfect data. We assume the network is stable, the auth token is valid, and the dependency responds in 200ms. But real systems break in boring, predictable ways:
- A token expires
- A permission is missing
- An API returns malformed JSON
- A service times out
These aren’t edge cases. They’re the default failure modes. Tools like Microsoft Dev Proxy let you simulate bad responses and inject latency or broken payloads into your app in dev. Start injecting chaos. Simulate rate limits. Test bad tokens. Drop headers. Let your app break - on purpose. If you never test failure, you’re not testing production.
Your test suite should be fast or it won’t be used
A modern test stack runs in CI, runs fast, and provides signal. You should know within minutes whether your change is safe, risky, or broken. The goal isn’t just automation. It’s trust. A fast, focused test suite builds trust in your codebase and confidence in every release. So test testing tools for their performance as well.
Testing is a leadership problem
Let’s talk to the business for a moment. If you want to move fast without breaking things, your test strategy matters. If you want teams to innovate without fear, your feedback loops need to be short and reliable. If you want fewer outages, fewer hot fixes, and fewer Friday rollbacks, then testing has to shift from checkbox to strategy. Real testing isn’t about writing more code but about reducing uncertainty. It’s the difference between shipping with hope 🤞 and shipping with confidence.
Test smarter. Not harder.
Coverage is a vanity metric. Confidence is the real one. You don’t need a thousand tests. You need the right ones. Test behaviors, not just functions. Verify contracts, not mocks. Run fast, catch failure, test what actually matters. Because if your test suite doesn’t reflect reality, it’s not protecting you, it’s lying to you. And if you want to move faster, scale smarter, and sleep better, then it’s time to modernize your stack.
This blog post is part of a series on testing software. You can find more parts here:
You May Also Like
Highway to the danger zone: No testing of business apps
Business apps often look safe—but they’re not. Mocked data, manual testing, and lack of ownership make them fragile in ways most teams overlook. This post breaks down why integration-heavy, low-code, …
You can’t outsource accountability
A multi-million-dollar IT project failed spectacularly, not because the vendor was terrible, but because the client wasn’t paying attention. Now, organizations are making the same mistakes with AI. …
The benefits of testing (beyond working apps)
Good testing gives you more than bug-free software. It enables faster delivery, clearer requirements, better team alignment, and safer change. This post explores why testing is your biggest hidden …