Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Unit testing checks a small piece of code in isolation; regression testing checks that behavior that already worked still works after a change. They are not competing test types: regression testing is a purpose, and its suite can include unit, integration, API, UI, and end-to-end tests. A practical strategy runs fast unit tests frequently, adds broader checks where component boundaries or business risks justify them, and avoids treating code coverage as a proxy for quality.

Unit testing and regression testing are different concepts

A unit test exercises an individual component or method—a “unit of work”—while keeping infrastructure concerns such as databases, filesystems, and networks outside the test boundary. External dependencies are typically replaced with controlled fakes or mocks so the test can focus on behavior under the developer’s control. Microsoft’s .NET unit-testing guidance describes these boundaries and practices.

Regression testing asks a different question: after a change, does existing functionality still work? It is a purpose and a way to select tests, not one particular test level. A regression suite may include unit tests, integration tests, API checks, UI checks, and end-to-end journeys. Microsoft’s Azure Well-Architected guidance describes regression tests as checks that existing functionality continues to work after changes.

Dimension Unit testing Regression testing
Scope One method, component, or other small unit of work Previously working behavior across one or more system layers
Isolation External dependencies are usually replaced with fakes or mocks Uses the level of realism needed to protect the selected behavior
Typical runtime Fast; often milliseconds or seconds per test Varies from fast unit checks to slower, broader system journeys
Purpose Find defects in a small behavior and provide rapid feedback Detect unintended side effects of a change
Trigger Frequently run during development and on commits Selected according to change risk for local, pull-request, release, or deployment gates

A unit test can also be a regression test: if it was added to prevent a previously fixed defect from returning, it serves both purposes. Conversely, not every regression check is a unit test; an integration test may be the right protection when the failure depends on two services interacting.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How to design a unit test that is useful and stable

Effective unit tests are fast, isolated, repeatable, self-checking, and written while the behavior is being developed. These characteristics are summarized in Microsoft’s unit-testing best practices. ISTQB presents a similar mnemonic, FIRST: Fast, Isolated, Repeatable, Self-Validating, Thorough, in its Foundation Level materials.

Use Arrange, Act, Assert

  1. Arrange: create the object under test, its minimal inputs, and controlled dependencies.
  2. Act: call the single behavior the test is intended to exercise.
  3. Assert: check the expected result or observable postcondition.

For example, a test for a discount calculator should provide a known price and discount, call the calculator once, and assert the expected total. It should not also test database persistence, send a network request, or reproduce the calculator’s implementation inside the assertion. The structure makes a failure easier to diagnose: readers can see the setup, the action, and the expected behavior separately.

Make the failure explain itself

Name a test with the behavior, scenario, and expected outcome—for example, ApplyDiscount_WhenCustomerIsEligible_ReturnsReducedTotal. Keep the setup small and avoid loops, conditionals, or unrelated assertions that make it unclear what failed. Include normal cases, boundaries such as zero or maximum values, and invalid inputs where the code has defined behavior for them.

Keep tests deterministic

A test that passes only when run in a particular order or at a particular time is not dependable feedback. Give each test its own data and controlled dependencies; avoid shared mutable state, uncontrolled clocks, random inputs without a fixed seed, and reliance on external services. If behavior depends on time or randomness, inject a controllable clock or generator where the design permits it. A failure should be reproducible from the test’s own setup, not dependent on a developer’s machine or a live network.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How to build a regression test suite

Start from behavior that matters, not from a target number of tests. List critical user journeys, system contracts, and previously escaped defects; then choose the cheapest test level that can reliably catch each class of failure. A bug fix should normally gain a test that would have failed before the fix and passes afterward. That test becomes durable protection against the same defect returning.

Prioritize by likelihood and impact

Protect high-consequence paths early: authentication and authorization, payments, data integrity, public APIs, and workflows that would be costly or harmful to break. For each area, ask how likely a change is to affect it, what failure would cost, and which test level can detect the failure without excessive maintenance. A small number of realistic integration checks may be more valuable than many brittle end-to-end tests when the risk lies at a service boundary.

Use a layered test pyramid

Use many fast unit tests, fewer integration tests, and fewer slower end-to-end tests. Unit tests pinpoint local behavior. Integration tests check that components work together across important boundaries. End-to-end tests exercise journeys through the full system and can reveal failures that isolated components cannot. Broader tests carry more setup and maintenance cost, so reserve them for behaviors that need that realism rather than duplicating every unit-level assertion at the UI.

Curate the suite as the product changes

Review regression cases after iterations and releases. Add tests for escaped defects and newly critical behavior; remove or revise tests that no longer represent the product’s intended behavior. In fast Agile cycles, rerunning every test is often impractical, so select candidates according to change impact and risk. ISTQB’s Foundation Level materials discuss this need to select regression tests.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Which tests should run on every commit?

Run the fast, deterministic unit suite on each local change and commit. The quick feedback loop helps developers find the cause while the relevant code is still in context. Add integration tests when a change crosses a component boundary; run them after the unit suite passes on pull requests when their runtime and infrastructure needs make that practical. Run broad regression or end-to-end checks at release or deployment gates when the risk warrants the additional time.

One pipeline shape described by Microsoft is unit tests on every commit, integration tests on pull requests after unit tests pass, and regression tests when deployment is triggered. Treat that as a starting point, not a universal schedule: a high-risk change may justify broader checks earlier, while a slow environment may require targeted selection and a later gate. Define what blocks a merge or release, make failures visible, and avoid silently ignoring a failing suite.

When a test is flaky, do not normalize rerunning it until green. Identify whether nondeterminism comes from shared state, timing, order dependence, resource contention, or an uncontrolled external dependency. Fix the cause or temporarily isolate the test from a blocking gate with an explicit owner and resolution plan; otherwise the gate stops communicating useful information.

How much code coverage is enough?

There is no defensible universal percentage in the available guidance. Coverage records which statements, branches, or paths executed during a test run; it does not prove that assertions checked meaningful outcomes. Microsoft warns that high coverage alone does not establish test quality and that pursuing an excessively ambitious target can make the remaining work disproportionately expensive. See Microsoft’s unit-test guidance and Azure Well-Architected guidance.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Set expectations by layer and risk. Critical payment or data-integrity logic may merit stronger behavior protection than low-risk presentation code, even if an overall percentage looks lower. Use coverage to find unexecuted code and ask whether it represents an important behavior; do not write assertions solely to turn uncovered lines green. Pair it with signals that tell you whether the suite is working in practice:

  • Defect escape rate: whether problems reach users or later stages despite passing tests.
  • Flaky-test rate: how often tests produce inconsistent results without a relevant code change.
  • Test duration: whether feedback remains quick enough to be used.
  • Mutation or fault-detection results, where available: whether tests notice deliberately introduced faults.
  • Critical requirement coverage: whether important workflows and contracts have automated protection.

Visual regression checks and ScreenshotNeo

For a browser-based product, a screenshot can be an artifact in a visual regression workflow: capture the same page under controlled conditions and compare it with an approved reference using your chosen comparison process. This supplements behavioral tests; a screenshot does not establish that a form submits correctly, data is valid, or accessibility requirements are met. Keep the page state, viewport, and test data consistent so unrelated differences do not obscure a meaningful visual change.

A do-it-yourself browser capture with Playwright

The following JavaScript example uses Playwright to capture a page after it reaches a stable browser load state. It writes a full-page PNG artifact; it does not implement image comparison or decide whether a difference should fail CI.

  1. Install Node.js and add Playwright to a project: npm install --save-dev playwright.
  2. Save the following as capture.mjs.
  3. Run it with node capture.mjs; the screenshot is written to page.png.

import { chromium } from 'playwright';
const browser = await chromium.launch();
try {
  const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
  await page.goto('https://example.com', { waitUntil: 'networkidle' });
  await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
  await browser.close();
}

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For a real test, replace the sample URL with a stable test environment and ensure the page has predictable data and state. Some pages keep network connections open or load content after the initial navigation; choose a selector or other application-specific readiness condition rather than relying blindly on a network-idle wait. Store artifacts so a failed visual check can be inspected, and decide explicitly how dynamic timestamps, ads, or personalized content are handled.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request can return a screenshot in PNG, JPEG, or WebP, or a PDF. For example, this cURL request captures the page at Stripe and saves the returned image:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo documentation for request options. Cookie banners are accepted and removed before capture, along with known consent platforms, newsletter popups, and chat widgets; each of those cleanup steps can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents. The free plan includes 1,000 shots a month without a card; paid plans start at $5 for 3,000 shots. A returned screenshot can serve as a test artifact, but the request alone does not compare it with a baseline or determine whether a visual difference is acceptable. Sign up for 1,000 free screenshots a month with no card.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting common test-suite problems

A unit test is slow

Check whether it starts a database, reads real files, calls a network service, or performs broad setup that belongs in an integration test. Replace infrastructure with a controlled dependency when the unit boundary permits it. Keep a smaller number of integration tests for risks that genuinely depend on that infrastructure.

A test passes alone but fails in the full suite

Look for shared mutable fixtures, reused records, global configuration, test-order assumptions, and resources that are not cleaned up. Make setup and teardown local to the test where possible, and ensure parallel tests do not write to the same resource.

A test fails intermittently in CI

Check timing assumptions, asynchronous work that is not awaited, external service availability, time-zone differences, and resource contention. Replace arbitrary sleeps with an explicit condition or readiness check. If the test depends on a remote service, decide whether it belongs in a controlled integration environment rather than a frequently run unit suite.

A regression test catches too many unrelated changes

The test may be asserting implementation details or an overly broad snapshot instead of a user-visible contract. Narrow the assertion to behavior the product intends to preserve. For visual checks, stabilize page data and remove dynamic content from the comparison process only when those elements are not part of the behavior being protected.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Coverage rose but defects still escape

Inspect assertions and risk coverage rather than adding tests for lines alone. Confirm that critical failure modes and boundary conditions are exercised, and consider whether the relevant interaction requires an integration or end-to-end check rather than another isolated unit test.

Frequently Asked Questions

Can one test be both a unit test and a regression test?

Yes. A unit test added to keep a previously fixed defect from returning is both: unit describes its scope, while regression describes its purpose.

Should end-to-end tests replace unit tests?

No. End-to-end tests cover full journeys, but they are slower and exercise more moving parts. They complement fast unit checks and targeted integration tests rather than replacing them.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.