Engineering · Quality

Writing Code Is Easy Now.
Testing It? Still Hard.

A practical, no-fluff guide to where different types of tests fit — and how AI is quietly changing the game.

With AI-assisted coding tools at our fingertips, generating code has never been faster. But shipping working code? That still demands a thoughtful, layered testing strategy — and most teams get the balance wrong.

If you’ve ever pushed a bug to production that your test suite completely missed, you know exactly what this post is about. The problem usually isn’t that you had no tests. It’s that you had the wrong kinds of tests in the wrong proportions.

Let’s break down the three layers of the modern testing pyramid, what each one is actually good for, and where the new wave of AI tools fits into all of this.

Layer 1: Unit + Component Tests

This is the foundation of a healthy test suite. Unit tests check that a single function or method does what it’s supposed to do, in isolation, without touching any real database or network. Component tests do the same thing for UI elements — does this button render correctly? Does this form validate the right way?

The beauty of unit tests is that they are blindingly fast. You can run thousands of them in seconds. When one breaks, it points you almost directly at the problem. They’re also cheap to write and maintain compared to higher-level tests.

If you only have time to write one type of test, write unit tests. They give you more signal per hour of engineering effort than anything else in the stack.

Tools to know

🃏JestJS / React
VitestVite-native
JUnit 5Java
🐍pytestPython
⚛️React Testing LibraryComponent tests
🌲Cypress (component)UI isolation
💚Vue Test UtilsVue.js
🎭Playwright (component)Multi-browser
🔷NUnit.NET
🦎JasmineJS BDD
🔬MochaJS flexible
💎RSpecRuby

What they’re great at: Catching logic errors in pure functions, validating edge cases, testing state management, and giving you the confidence to refactor without fear.

What they can’t do: They can’t tell you if your API contract is wrong, if your database query returns the right data, or if two services integrate correctly. That’s where the next layer comes in.

Layer 2: Integration Tests

Integration tests verify that the seams of your system hold together. Can your application actually talk to the database? Does your API return the right shape of data? Does calling Service A trigger the expected behavior in Service B?

These tests are slower than unit tests because they often spin up real (or near-real) infrastructure. They’re more expensive to maintain too — a schema change or an environment config drift can break them in ways that have nothing to do with your code. But they catch a class of bugs that unit tests are completely blind to.

A broken API contract is a perfect example. Your unit tests can pass with flying colors while your frontend and backend are speaking completely different data formats. An integration test catches that instantly.

Tools to know

🐳TestcontainersReal infra in Docker
🚀PostmanAPI contract tests
🦦BrunoGit-native API tests
🧪SupertestNode HTTP testing
REST-assuredJava REST testing
🌱Spring Boot TestSpring integration
🔗PactContract testing
🗄️DBUnitDatabase testing
🌐WireMockAPI mocking

Testcontainers deserves a special mention here — it lets you run real databases, message brokers, and other dependencies in Docker containers as part of your test suite. No more “works on my machine” excuses when the test database doesn’t match production.

Layer 3: End-to-End Tests

End-to-end tests simulate a real user interacting with your real system from a real browser (or device). You’re testing the whole stack at once — frontend, backend, database, APIs, auth — all running together.

They’re incredibly powerful. They’re also expensive, slow, and notoriously flaky. A test that passes locally fails in CI. A test that passed yesterday breaks today because an animation timing changed. This is why the pyramid gets narrow at the top: you want a small, carefully chosen set of E2E tests that cover your most critical user journeys, not exhaustive coverage of every feature.

Think of them as a final sanity check on your most important flows — login, checkout, onboarding, the thing users do every single day. Not a replacement for the layers below.

Tools to know

🎭PlaywrightMulti-browser, fast
🌲CypressJS-first E2E
📱AppiumMobile automation
🐺QA WolfAI-assisted E2E
ShortestAI-driven tests
🤖SeleniumClassic WebDriver
🦾WebdriverIONode + WebDriver
🥒CucumberBDD / Gherkin
🤗TestCafeNo WebDriver needed

Manual Testing Tools

🧑‍💻 Manual Testing — Still Irreplaceable

Automation handles repetition. Manual testing handles judgment. Exploratory testing, UX review, accessibility audits, and edge cases that no script would ever think to run — these still demand a human in the loop.

Test Management & Planning

Before you test anything, you need a way to organize test cases, track coverage, and report results. These tools are the backbone of any structured QA process.

📋Jira + ZephyrTest cases in Jira
🗂️TestRailDedicated test mgmt
📊QaseModern test tracking
📝Azure Test PlansMicrosoft DevOps
🧩TestLinkOpen-source option
📌XrayJira-native testing
🔷QMetryAI-powered test mgmt

Bug Tracking & Reporting

🐛JiraIndustry standard
🐙GitHub IssuesFor OSS projects
📬LinearFast, modern tracker
🗒️BugzillaClassic open-source
🔵Azure DevOpsMS ecosystem

API & Network Testing (Manual)

Testing APIs manually is a core QA skill. These tools let testers inspect requests, mock responses, and validate contracts without writing code.

🚀PostmanGUI API testing
🦦BrunoLocal-first, git-friendly
🌐InsomniaREST & GraphQL
🔥HoppscotchOpen-source, web-based
🕵️Charles ProxyIntercept & inspect
🦈WiresharkDeep packet analysis

Performance & Load Testing

JMeterLoad testing classic
🔫k6Script-based load tests
🌊GatlingScala-based, CI-ready
🏎️LocustPython load testing
📈BlazeMeterCloud scale load tests

Security & Accessibility Testing

🔐OWASP ZAPSecurity scanning
🕸️Burp SuiteWeb app security
axe DevToolsAccessibility audits
🏠WAVEWeb accessibility eval
🔦LighthouseChrome performance+a11y

✅ Manual Testing Strengths

Exploratory testing, UX judgment calls, accessibility review, edge cases, usability issues, and anything requiring human empathy or intuition.

⚠️ Where it Falls Short

Repetitive regression suites, high-volume data tests, cross-browser matrix checks, and anything that needs to run on every commit. That’s automation territory.

Where AI Fits Into All of This

🤖 AI-Assisted Testing — Not a Replacement, a Force Multiplier

AI tools are becoming a genuine part of the testing workflow. They’re not replacing human judgment about what to test — but they’re eliminating a huge amount of the grunt work involved in actually writing the tests.

GitHub Copilot
ChatGPT
Claude
Cursor
Qodo

Here’s the honest picture of where AI tools add real value in testing right now:

Drafting boilerplate tests fast. Writing the setup, teardown, and basic happy-path test for a new function is tedious. AI handles that in seconds so you can focus on the edge cases that actually require thinking.

Spotting coverage gaps. Tools like Qodo analyze your existing test suite and flag functions or branches that aren’t covered yet. It’s like having a junior QA engineer doing a first pass before you review.

Keeping test suites up to date. When you refactor a component, AI can help update the corresponding tests without you rewriting everything from scratch. It reduces the maintenance drag that makes teams stop updating their tests in the first place.

Generating test data. Realistic, varied test data is annoying to create by hand. AI is excellent at generating edge-case inputs, boundary values, and realistic fake data structures.

⚠️ A Note on AI-Generated Tests

  • Always review AI-generated tests before committing them — they can pass without actually testing what you think they’re testing.
  • AI will sometimes write tests that mirror the implementation rather than the intended behavior. A test that just repeats your code logic in test form is basically useless.
  • Use AI to handle the repetitive structure, but write the meaningful assertions yourself.
  • AI is genuinely excellent at generating edge cases you wouldn’t have thought of — lean into that.

Getting the Balance Right

The most common mistake teams make is over-investing in E2E tests because they “feel” more realistic, while their unit test coverage stays thin. This creates a test suite that’s slow, brittle, and hard to debug when something breaks.

A healthier ratio for most teams looks something like this:

70%

Unit + Component Tests

fast feedback, high coverage

20%

Integration Tests

services playing nicely

10%

E2E Tests

critical user paths only

This isn’t a rigid rule — a team building a heavily distributed microservices architecture might lean more on integration tests. A startup with a single frontend and one API might be fine with fewer. The point is the shape of the pyramid, not the exact percentages.

The Bottom Line

The tools have never been better. AI is genuinely taking the repetitive weight off your shoulders. But the judgment about what matters to test, and why — that’s still entirely yours.

A test suite built with intention beats 10,000 auto-generated tests every single time.

Discover more from Lean Impeccable

Subscribe now to keep reading and get access to the full archive.

Continue reading