Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Test Automation Strategy: The Testing Pyramid

By Kokil Thapa | Last reviewed: September 2026

Shipping features without a clear Test Automation Strategy: The Testing Pyramid leads to slow pipelines, flaky builds, and teams that stop trusting green checks. On production Laravel applications I maintain, the difference between a ten-minute deploy and a two-hour guessing game usually comes down to test shape—not test count. This guide explains how to layer unit, integration, and end-to-end tests so your CI pipeline stays fast and meaningful.

What Is Test Automation Strategy: The Testing Pyramid?

The testing pyramid is a distribution model for automated checks. Mike Cohn popularised the idea: many small, fast tests at the base; fewer tests that cross boundaries in the middle; a thin cap of full-path UI tests at the top. The goal is speed plus signal, not maximum coverage for its own sake.

In practice, the pyramid answers one question: where should this assertion live? A VAT calculation belongs in a unit test. A payment callback against a sandbox gateway belongs in integration. A checkout flow through the browser belongs at the top—if you automate it at all.

Testing Pyramid LayersE2E / UI (10%)Slow, brittleIntegration (20%)DB, HTTP, queuesUnit (70%)Pure logic, fastFewer tests upMore tests down
Test Automation Strategy: The Testing Pyramid — unit tests form the wide base; E2E tests sit at the narrow top.

For Laravel 13 on PHP 8.3+, the layers map cleanly to PHPUnit or Pest suites, HTTP feature tests, and optional browser tools. Symfony 8.1 projects follow the same shape with PHPUnit and Panther or Playwright at the cap. WordPress and WooCommerce 11.1 shops often invert the pyramid—too many admin UI clicks, almost no pure function tests—which is exactly why those sites feel expensive to change.

How Do You Assign Tests to Each Pyramid Layer?

Start with the failure you want to catch, then pick the lowest layer that can catch it honestly. Lower layers run in milliseconds. Higher layers need databases, browsers, and network calls.

Unit tests — base layer

Unit tests exercise one class or function in isolation. Dependencies are mocked or faked. They should not touch MySQL 9.7, Redis 8.10, or the filesystem unless that is the subject under test.

Good candidates on a legal-tech portal: fee calculators, date validators, slug generators, policy rules, and DTO mappers. On an eCommerce cart: tax rounding, discount stacking, and delivery-zone matching.

// tests/Unit/Services/CourtFeeCalculatorTest.php
public function test_calculates_stamp_duty_for_claim_amount(): void
{
    $calculator = new CourtFeeCalculator();

    $this->assertSame(500.0, $calculator->stampDuty(100_000));
}

See Laravel feature testing best practices for where unit tests stop and HTTP tests begin. Pure logic stays unit-level; anything needing routing belongs higher.

Integration tests — middle layer

Integration tests verify that real components work together. In Laravel, a feature test hitting /api/v1/bookings with a seeded database is integration—not unit—because it crosses HTTP, middleware, validation, and Eloquent.

Typical integration targets:

  • Repository queries with real MySQL or PostgreSQL 18 schema
  • Queue job dispatch and handle with Queue::fake() only when the queue itself is not under test
  • Payment gateway sandbox callbacks (eSewa, Khalti, Stripe)
  • Mail and notification rendering with real templates

API contract testing with Pact belongs here when microservices or mobile clients consume your Laravel API. Contracts catch breaking JSON shape changes that unit tests miss.

End-to-end tests — top layer

E2E tests drive the application like a user: login, fill forms, submit, assert DOM state. They are slow and sensitive to timing, CSS selectors, and third-party widgets.

Reserve E2E for revenue-critical paths only:

  1. Guest checkout on a WooCommerce store
  2. Client document upload on a law-firm portal
  3. Booking confirmation on a trek agency site

Everything else should drop down a layer. I've seen teams maintain forty Cypress specs for admin CRUD that Laravel HTTP tests would cover in a fifth of the runtime.

Where Does This Test Belong?New test caseNeeds browser or full UI?NoYesNeeds DB or HTTP?E2E layerNoYesUnit testIntegrationPick the lowest layer that proves the behaviour
Decision tree for Test Automation Strategy: The Testing Pyramid — always choose the lowest sufficient layer.

What Test Ratios Work in Real CI Pipelines?

The classic 70/20/10 split is a starting point, not a law. A payments-heavy API might run 50/40/10 because integration risk dominates. A content site with little logic might run 80/15/5.

LayerTypical shareRuntime targetPrimary tools (PHP stack)Failure signal
Unit60–75%< 30 s totalPHPUnit 11, Pest 3Logic bug, bad edge case
Integration15–30%1–4 minLaravel HTTP tests, Symfony WebTestCaseBroken query, bad middleware
E2E5–15%3–10 minPlaywright, Dusk, PantherRegressed user journey

On sister sites I deploy with Deployer 7 and GitLab CI, unit and integration suites gate every merge. E2E runs on staging nightly—not on every push—because browser farms eat minutes and budget.

Laravel testing with Pest in CI/CD shows how to parallelise suites by directory. Split tests/Unit from tests/Feature in your pipeline YAML so a logic failure never waits behind a browser job.

External references help when you pitch this to non-technical stakeholders. The Martin Fowler test pyramid article explains the trade-offs in plain language. The PHPUnit 11 documentation covers assertions and test doubles at the base layer.

How Do You Implement the Pyramid in Laravel and Symfony Projects?

Folder structure encodes strategy. If everything lives in tests/Feature, you already have an inverted pyramid—sometimes called the ice-cream cone anti-pattern.

Laravel 12/13 layout

tests/
  Unit/
    Services/
    Models/
    Rules/
  Feature/
    Http/
    Console/
  Browser/          # optional Dusk or external Playwright
    CheckoutTest.php

Use SQLite in memory or a dedicated MySQL schema for integration tests. Never point tests at production data. Test data management for pipelines covers factories, seeders, and anonymised dumps.

For booking systems like Adventure Third Pole Trek, I keep availability algorithms in unit tests and reservation HTTP flows in feature tests. One Playwright spec covers the full guest booking path—that is enough at the top.

Symfony 8.1 layout

Symfony separates similar concerns. Unit tests live beside services. WebTestCase handles kernel boot and HTTP. Keep Panther specs under tests/E2E with a distinct PHPUnit group:

# phpunit.xml.dist excerpt
<groups>
  <exclude>e2e</exclude>
</groups>

Run vendor/bin/phpunit --exclude-group e2e on every commit. Run the e2e group on schedule. Symfony test suite setup with PHPUnit walks through kernel test config in detail.

CI wiring

A practical GitLab CI stage order:

  1. lint — Pint, PHPStan, ESLint if you ship Vue assets built with Vite 8.x
  2. unitphp artisan test --testsuite=Unit
  3. integrationphp artisan test --testsuite=Feature with MySQL service container
  4. e2e — manual or scheduled; Playwright against staging URL
  5. deploy — only after unit + integration green

Composer 2.10 and PHP 8.3 should match production exactly. A common mistake is CI on 8.5 while the server still runs 8.3—tests pass, deploy breaks.

CI Pipeline Aligned to PyramidLint~30 secUnit~45 secIntegration~3 minE2ENightlyDeployStagingMerge request gate: Lint + Unit + IntegrationE2E runs scheduled; blocks release not every pushFast feedback loopDev trusts red buildsAnti-patternE2E on every commit
CI stages mapped to Test Automation Strategy: The Testing Pyramid — gate merges on fast layers, schedule slow UI tests.

What Anti-Patterns Break Test Automation Strategy: The Testing Pyramid?

Knowing the pyramid is easy. Living it is harder. These patterns show up on almost every legacy rescue I touch.

The ice-cream cone

Too many UI tests, almost no units. Symptoms: ten-minute CI, random red builds, and developers who rerun jobs until green. Fix: extract business rules into testable services; replace UI assertions with HTTP feature tests where possible.

The hourglass

Heavy unit and E2E layers with a thin integration middle. API contracts break because nobody tests real JSON against real database constraints. Add feature tests for controllers and Postman/Newman collections in CI for public endpoints.

Testing implementation details

Asserting that a private method was called couples tests to internals. Test outcomes: response codes, database state, dispatched events. Refactors should not shatter the suite.

Shared mutable state

Tests that depend on execution order fail randomly. Use RefreshDatabase in Laravel. Wrap each test in transactions where supported. Reset Redis keys between cases.

Mutation testing beyond code coverage exposes hollow unit suites—high line coverage with weak assertions. Run Infection PHP occasionally on critical modules like pricing or permissions.

Healthy Pyramid vs Ice-Cream ConeHealthyAnti-patternFew E2ESome integrationMany unitMany E2EFew integrationFew unitFast CI, clear failuresTrustworthy green buildsSlow CI, flaky failuresTeam ignores red builds
Healthy Test Automation Strategy: The Testing Pyramid compared to the inverted ice-cream cone anti-pattern.

How Do You Roll Out a Pyramid Strategy on an Existing Codebase?

Greenfield projects adopt the pyramid from day one. Brownfield ones need incremental moves. Do not pause feature work for a six-month "testing initiative."

Week-by-week rollout that works on client projects:

  1. Week 1 — inventory. Count tests by directory. Measure CI time per stage. Identify the three flakiest specs.
  2. Week 2 — stop the bleeding. Quarantine flaky E2E with @group flaky and exclude from gates. Fix or delete—never leave ignored.
  3. Week 3 — unit gaps. Add unit tests for the most-edited service classes from git blame. Target pure functions first.
  4. Week 4 — integration wins. Replace one E2E login flow with an HTTP feature test. Keep a single smoke E2E if stakeholders require it.
  5. Ongoing — ratchet. Require tests for new code paths. No new E2E without team approval. Track suite duration weekly.

For Mijar Law Associates–style portals, document-upload workflows tempt teams toward heavy browser tests. Model uploads as feature tests against storage fakes first. Run one E2E across nginx, PHP-FPM, and real S3-compatible storage on staging.

Load and performance sit adjacent to the pyramid, not inside it. Load testing with k6 for PHP apps validates throughput under stress. Database restore testing validates backups. Both complement—not replace—functional automation.

When you need help structuring suites for a new platform, testing and optimization services cover audit, CI setup, and pyramid realignment. For greenfield apps, custom software development bakes the pyramid into architecture from the first migration.

Use the JSON formatter tool when debugging API test payloads. Clean JSON diffs save hours compared to squinting at escaped strings in CI logs.

The Laravel 12 testing documentation is the authoritative reference for HTTP tests, fakes, and database traits. Align your local PHP version with production before you trust any green run.

Key Takeaways

  • Place most assertions at the unit layer; use integration for HTTP, DB, and queue boundaries; keep E2E thin and revenue-focused.
  • Split CI stages so unit and integration gate merges while E2E runs on a schedule against staging.
  • Folder structure (tests/Unit vs tests/Feature vs browser specs) encodes your Test Automation Strategy: The Testing Pyramid—structure is policy.
  • Watch for ice-cream cone and hourglass anti-patterns; replace brittle UI checks with HTTP feature tests where possible.
  • Roll out incrementally: inventory, quarantine flakes, unit-test hot spots, then ratchet on new code.
  • Mutation testing and contract tests strengthen the middle and base layers without adding browser time.

People Also Ask

What is the 70/20/10 rule in test automation?

It suggests roughly 70% unit tests, 20% integration tests, and 10% end-to-end tests. Treat it as a guideline. APIs with heavy external dependencies may shift toward integration. Content-heavy WordPress sites should still push logic into testable PHP functions at the base.

Are E2E tests worth the maintenance cost?

Yes, but sparingly. Automate one happy path per critical revenue flow—checkout, booking, client login. Maintain them like production code: stable selectors, no arbitrary sleeps, run nightly. Everything else belongs lower in the pyramid.

How is the testing pyramid different from the testing trophy?

The trophy model emphasises integration tests as the largest layer—popular in front-end-heavy JavaScript apps. For Laravel and Symfony backends, the classic pyramid still fits: most business logic lives in PHP services testable without a browser.

Can you have too many unit tests?

You can have too many low-value unit tests that mock everything and prove nothing. Focus on behaviour and edge cases. Pair line coverage with mutation testing on modules where bugs cost money—payments, permissions, pricing.

Build a Pyramid Your Team Trusts

A disciplined Test Automation Strategy: The Testing Pyramid turns CI from a ritual into a safety net. Start with directory layout and pipeline stages this week. Add unit tests where git blame shows churn. Demote redundant E2E specs to feature tests. Within a month, builds get faster and releases get calmer.

Need an audit of your current suites or a CI pipeline aligned to these layers? Contact us to review your stack, or explore build pipeline automation best practices and enterprise application development for larger rollouts. Read more on the blog, browse the portfolio, or learn about my approach to shipping reliable web systems since 2010.

Frequently Asked Questions

A distribution model where many fast unit tests form the base, fewer integration tests sit in the middle, and a thin layer of end-to-end UI tests caps the top—optimizing speed and diagnosable feedback.

Roughly 70% unit, 20% integration, and 10% end-to-end tests. It is a starting guideline, not a fixed law—adjust when integration risk or external dependencies dominate.

Start with the failure you want to catch, then pick the lowest layer that can catch it honestly. Pure logic such as VAT calculations, fee calculators, slug generators, and delivery-zone matching stays at the unit layer. Anything crossing HTTP, middleware, validation, Eloquent, queues, or real database schema belongs in integration tests. Reserve end-to-end browser tests for revenue-critical user journeys like guest checkout, client document upload, or booking confirmation—only when lower layers cannot cover the path.

Unit tests exercise one class or function in isolation with mocked dependencies. They should not touch MySQL, Redis, or the filesystem unless that is the subject under test. Good unit targets include fee calculators, date validators, policy rules, tax rounding, and discount stacking. Integration tests in Laravel are typically HTTP feature tests hitting routes with a seeded database—they cross routing, middleware, validation, and Eloquent. Payment gateway sandbox callbacks, queue job handling, mail rendering, and API contract checks also belong at the integration layer.

The classic split is roughly 70/20/10, but real pipelines vary. Unit tests typically make up 60–75% of the suite and should finish in under 30 seconds total. Integration tests run 15–30% and target one to four minutes. End-to-end tests stay at 5–15% and take three to ten minutes. A payments-heavy API might shift toward 50/40/10 because integration risk dominates. A content-heavy site with little logic might run closer to 80/15/5. Treat ratios as signals about risk, not coverage quotas.

Folder structure encodes strategy. Keep pure logic in tests/Unit under Services, Models, and Rules. Put HTTP boundary tests in tests/Feature under Http and Console. Optional browser specs live in tests/Browser. Use SQLite in memory or a dedicated MySQL schema for integration tests—never point tests at production data. In GitLab CI, run lint first, then php artisan test --testsuite=Unit, then --testsuite=Feature with a MySQL service container, and gate deploys only after those pass. Match Composer 2.10 and PHP 8.3 to production so green CI actually means safe deploys.

Symfony 8.1 follows the same shape with PHPUnit at the base. Unit tests live beside services. WebTestCase handles kernel boot and HTTP integration. Keep Panther or Playwright specs under tests/E2E with a distinct PHPUnit group. Exclude that group from every commit by setting exclude e2e in phpunit.xml.dist and running vendor/bin/phpunit --exclude-group e2e on merges. Run the e2e group on a schedule against staging. This mirrors Laravel’s split: fast layers gate merges, browser tests validate full paths without blocking every push.

Unit and integration suites should gate every merge because they finish quickly and failures are easy to diagnose. End-to-end tests are slow, flaky-prone, and expensive on browser farms—run them nightly against staging, not on every push. On sites deployed with Deployer 7 and GitLab CI, that pattern keeps pipelines trustworthy without turning every commit into a ten-minute wait. Reserve manual or scheduled E2E stages for revenue-critical smoke paths. Everything else should drop to HTTP feature tests that run on every merge.

Yes, but sparingly. Automate one happy path per critical revenue flow—checkout, booking, client login—and maintain specs like production code with stable selectors and no arbitrary sleeps. Run them nightly, not on every push.

The ice-cream cone means too many UI tests and almost no unit tests—common on WordPress and WooCommerce 11.1 shops where teams click through admin instead of testing PHP logic. Symptoms include ten-minute CI runs, random red builds, and developers rerunning jobs until green. The fix is to extract business rules into testable services, add unit tests for pure functions, and replace brittle UI assertions with Laravel HTTP feature tests where possible. Structure should look like a pyramid, not an inverted cone with a wide browser top and empty base.

The hourglass describes heavy unit and end-to-end layers with a thin integration middle. Teams mock everything at the base and automate full browser flows at the top, but skip real HTTP and database boundary tests. API contracts break because nobody verifies JSON responses against actual schema constraints. Controllers and middleware regress silently. The fix is to add feature tests for controllers, seed real database state, and use contract testing with Pact when mobile clients or microservices consume your API. The middle layer should catch what isolated units and slow E2E both miss.

The testing trophy model puts integration tests as the largest layer—common in front-end-heavy JavaScript applications where most behaviour lives in component interaction. For Laravel and Symfony backends, the classic pyramid still fits because most business logic lives in PHP services testable without a browser. You unit-test calculators, validators, and pricing rules directly. You integration-test HTTP routes, queues, and payment callbacks. You keep a thin E2E cap for checkout or booking smoke paths. Pick the model that matches where your application’s risk actually lives.

Yes—you can accumulate low-value unit tests that mock every dependency and prove nothing useful. High line coverage with weak assertions is a hollow suite. Focus unit tests on behaviour and edge cases in modules where bugs cost money: payments, permissions, pricing, and fee calculations. Pair coverage with occasional mutation testing using Infection PHP on critical modules—mutation testing exposes tests that pass even when logic is broken. Assert outcomes like return values and state changes, not private method calls or internal implementation details that shatter on every refactor.

Beyond inverted pyramids, four patterns break trust fast. Testing implementation details—asserting private methods were called—couples specs to internals; test response codes, database state, and dispatched events instead. Shared mutable state makes order-dependent tests fail randomly; use RefreshDatabase in Laravel, transactions where supported, and reset Redis keys between cases. Flaky E2E left in merge gates erodes confidence—quarantine with a flaky group, fix or delete, never ignore. Finally, running CI on PHP 8.5 while production stays on 8.3 produces false greens that break on deploy.

Do not pause feature work for a six-month testing initiative. Week one: inventory tests by directory, measure CI time per stage, and identify the three flakiest specs. Week two: quarantine flaky E2E, fix or delete—never leave ignored. Week three: add unit tests for the most-edited service classes from git blame, targeting pure functions first. Week four: replace one E2E login flow with an HTTP feature test while keeping a single smoke E2E if stakeholders require it. Ongoing: require tests for new code paths, block new E2E without team approval, and track suite duration weekly.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: