⚔️ The Code Forge ⚔️
Light
Dark
System
← Back to Articles

Testing Strategy: Why 90% Coverage Is the Sweet Spot

Testing Strategy: Why 90% Coverage Is the Sweet Spot

Published on January 8, 2025 • 10 min read

Let me start with a confession: I used to be a 100% code coverage zealot. I would spend hours writing tests for getter and setter methods, testing framework code, and generally making my test suites twice as long as the actual application code.

Then I started working with real clients on real deadlines, and I learned something important: testing strategy should be about covering the most important code, not checking boxes.

The Diminishing Returns of 100% Coverage

Here's what nobody tells you about chasing 100% test coverage: the last 10% takes 50% of your testing effort. You end up writing tests for:

  • Simple property getters and setters
  • Framework boilerplate code
  • Error conditions that will never happen in production
  • Trivial utility functions

Meanwhile, the complex business logic that actually breaks things gets rushed testing because you're burning time on the trivial stuff.

I learned this the hard way on a financial application where we had 98% test coverage but still shipped a bug in the interest calculation logic because I spent more time testing the date formatting utilities than the core business rules.

Where to Focus Your Testing Energy

After years of trial and error, here's where I put my testing effort:

Business Logic (50% of testing effort)

This is where bugs hurt the most. If your e-commerce platform calculates shipping costs wrong or your inventory system double-allocates products, you lose money and customers.

I write comprehensive unit tests for:

  • Calculation engines
  • Business rule validation
  • State transition logic
  • Data transformation logic

Integration Points (30% of testing effort)

Most production bugs happen at the boundaries between systems. Your code works fine, the API works fine, but somehow they don't work fine together.

Key areas I always test:

  • Database interactions
  • External API calls
  • File system operations
  • Third-party service integrations

User Workflows (20% of testing effort)

This is where E2E testing shines. I focus on the critical paths that users actually take through the application.

My E2E Testing Philosophy

I'm a big believer in E2E tests that mimic real user behavior. Too many developers write E2E tests that are really just integration tests in disguise.

The Page Object Model Is Your Friend

Here's how I structure E2E tests using the Page Object Model:

// LoginPage.ts
export class LoginPage {
  async enterCredentials(email: string, password: string) {
    await this.emailInput.fill(email);
    await this.passwordInput.fill(password);
  }

  async submitLogin() {
    await this.loginButton.click();
    await this.page.waitForURL('/dashboard');
  }

  async expectLoginError(message: string) {
    await expect(this.errorMessage).toContainText(message);
  }
}

// LoginTest.spec.ts
test('User can log in with valid credentials', async () => {
  const loginPage = new LoginPage(page);
  const dashboardPage = new DashboardPage(page);

  await loginPage.enterCredentials('user@example.com', 'password123');
  await loginPage.submitLogin();

  await dashboardPage.expectWelcomeMessage('Welcome back!');
});

This approach makes tests readable and maintainable. When the UI changes, you update the page object, not fifty different test files.

Test User Stories, Not Features

Instead of testing "the login function works," I test "a user can access their dashboard after logging in." The difference is subtle but important - you're testing the value delivered to users, not just technical functionality.

What I Don't Test

This might be controversial, but here's what I deliberately don't spend time testing:

Framework Code

If you're using Angular, React, or .NET, don't test that their built-in functionality works. Test your code that uses the framework, not the framework itself.

Simple Getters and Setters

// Don't test this
public string Name { get; set; }

// Do test this
public string DisplayName => $"{FirstName} {LastName}".Trim();

Third-Party Library Behavior

Assume that well-maintained libraries work as documented. Test your integration with them, not their internal behavior.

Obvious Error Cases

Don't write tests for things like "what happens if we pass null to a method that requires a string." Use proper typing and validation instead.

Technology-Specific Strategies

.NET Testing

With .NET (my preferred backend technology), I lean heavily on:

  • xUnit for unit testing
  • Moq for mocking dependencies
  • FluentAssertions for readable test assertions
  • Microsoft.AspNetCore.Mvc.Testing for integration tests

The .NET ecosystem makes it easy to write fast, reliable tests without a lot of ceremony.

Angular Testing

For Angular applications, I focus on:

  • Unit tests for services and complex components
  • Integration tests for component interactions
  • E2E tests with Playwright for user workflows

Angular's dependency injection makes testing much easier than other frontend frameworks, so I can achieve good coverage without excessive mocking.

Automation Makes It Sustainable

The key to maintaining good testing practices is automation. My testing pipeline:

  1. Fast feedback loop: Unit tests run in under 30 seconds
  2. Integration tests run on every PR
  3. E2E tests run on staging deployments
  4. Performance tests run weekly

If tests take too long, developers stop running them. If they're flaky, developers stop trusting them.

Real-World Example: E-commerce Platform

Let me show you how this looks in practice. On a recent e-commerce project:

90% coverage focused on:

  • Shopping cart calculations (unit tests)
  • Payment processing workflow (integration tests)
  • Order fulfillment process (E2E tests)
  • Inventory tracking logic (unit tests)

What we didn't test:

  • Product image display logic
  • CSS styling behavior
  • Static content rendering
  • Simple CRUD operations

Result: We caught 95% of bugs before production, shipped on time, and the client hasn't had a single payment or inventory issue in 6 months.

The Business Case for Smart Testing

Here's why this approach works better for clients:

Faster Development

By not chasing 100% coverage, we deliver features faster and spend more time on functionality that matters to users.

Better ROI

Testing effort focused on business logic catches the bugs that actually cost money.

Maintainable Test Suites

With fewer, more focused tests, the test suite stays maintainable as the application grows.

When to Adjust the Strategy

Different projects need different approaches:

Financial/Healthcare Applications: Bump to 95% coverage and include more edge case testing

Internal Tools: 80% coverage might be sufficient

Public-Facing Applications: Heavy emphasis on E2E testing for user journeys

APIs: Focus on contract testing and integration tests

Getting Started

If you want to implement this approach:

  1. Audit your current tests - what's actually providing value?
  2. Identify your critical business logic - start there
  3. Map your user journeys - focus E2E testing on these
  4. Automate everything - manual testing doesn't scale
  5. Measure what matters - track bugs found vs. time invested

The Bottom Line

Testing is about risk management, not box checking. A focused 90% coverage that tests the right things will catch more bugs and save more money than an unfocused 100% coverage that tests everything equally.

Your goal should be confidence in your deployments, not perfect metrics. And remember - the best test is often the simplest one that catches real problems.


Want to discuss testing strategies for your project? Let's talk - I love helping teams build testing approaches that actually work.