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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Unit testing checks a small, focused piece of program behavior automatically. Start by choosing the test framework that fits your project, then write one readable test around a behavior you can name: arrange inputs, act once, and assert the expected result. Run it locally and in continuous integration (CI), and add tests for important defects when you fix them.

A unit test is a fast feedback tool, not proof that an entire application works. There is no universal rule for how large a “unit” must be, and the value of a test depends on whether it remains understandable and useful as the code changes.

What is unit testing?

A unit test exercises a small portion of a program and checks whether its behavior matches an expectation. The unit might be a function, method, class, or a closely related group of code. Teams draw that boundary differently; Martin Fowler noted in 2014 that the term is “very ill-defined,” and warned against assuming there is one strict definition (Martin Fowler’s discussion of unit tests).

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

In practice, a useful unit test is focused enough that a failure points you toward a particular behavior. It should usually be fast, deterministic, and straightforward to diagnose. A test that depends on a live network service, a real clock, or shared mutable data may fail for reasons unrelated to the code under examination. Where appropriate, control those dependencies or replace them with test doubles; not every dependency needs to be mocked.

Why unit testing matters—and what it costs

Catch regressions while changes are small

A test can alert you when a later change breaks behavior that previously worked. Running tests frequently, including in CI, helps surface faults before release. Microsoft describes testing in Visual Studio as a way to “find errors and faults before your customers do” (Microsoft’s Visual Studio unit-testing guide).

Make expected behavior visible

A well-named test records an example of how a feature is meant to behave. That can help a teammate understand decisions such as whether an empty value is accepted or how a boundary case is handled. Tests are not a substitute for clear design documentation, but executable examples can make assumptions concrete.

Support design without treating tests as proof

Microsoft’s .NET guidance identifies regression protection, documentation, and support for good design among unit testing’s benefits. It also warns that tests that are hard to read or brittle can harm a codebase (Microsoft’s .NET unit-testing best practices). A passing unit test says that the tested behavior passed under the conditions the test set up; it does not show that every component works together or that the whole system meets every requirement.

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

Account for maintenance

Tests are code too. They need useful names, review, occasional refactoring, and enough isolation to avoid unrelated failures. A test coupled to internal implementation details may break after a harmless refactor even though the user-visible behavior is unchanged. Prefer assertions about outcomes that matter to callers, and avoid testing every line simply to increase a coverage number.

How to write your first unit test

Use Arrange–Act–Assert

  1. Arrange: prepare inputs and any dependencies needed for the behavior.
  2. Act: invoke the function or method once.
  3. Assert: check the result or other outcome that matters.

Keep the test centered on one behavior. If it fails, its name and assertion should help explain what broke. Use stable inputs; if time, randomness, or external I/O is relevant, make the test’s conditions controllable rather than relying on whatever happens to be true during a run.

A small Python example with pytest

The official pytest guide uses a plain Python file and ordinary assert statements. Install pytest, save the following files in the same directory, and run the command shown below (pytest getting started).

Rank #3
Sale

Production code in pricing.py:

def total_with_tax(subtotal, tax_rate):
    if subtotal < 0:
        raise ValueError("subtotal must not be negative")
    if tax_rate < 0:
        raise ValueError("tax_rate must not be negative")
    return round(subtotal * (1 + tax_rate), 2)

Test code in test_pricing.py:

import pytest

from pricing import total_with_tax


def test_total_with_tax_rounds_to_cents():
    assert total_with_tax(10, 0.075) == 10.75


def test_total_with_tax_rejects_negative_subtotal():
    with pytest.raises(ValueError, match="subtotal"):
        total_with_tax(-1, 0.075)

Install and run from the project directory:

python -m pip install -U pytest
python -m pytest

The first test checks a normal calculation and the second checks a named invalid-input behavior. A failed assertion is a prompt to investigate: the implementation may be wrong, the expectation may be wrong, or the test setup may not represent the behavior you intended.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

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

Start with a behavior that matters

Good first candidates include a validation rule, a calculation boundary, or a transformation with a clear expected output. Avoid writing a test merely because a function exists. For an important defect, first capture the failing behavior in a test when practical, then change the implementation and confirm the test passes. That leaves a regression check for the failure that mattered.

Which unit-testing framework should you choose?

Begin with the framework native to the project language and check that it works with the team’s runner, IDE, and CI environment. Microsoft’s .NET overview lists MSTest, NUnit, TUnit, and xUnit.net; its Visual Studio documentation also describes support for MSTest, NUnit, xUnit, and other third-party frameworks (.NET testing overview; Visual Studio testing guide). Python developers can start with pytest and its official documentation (pytest documentation).

Choice Useful fit What to check
pytest Python projects seeking a command-line workflow with plain assertions. Fixtures, test selection, diagnostics, and any plugins the project needs. pytest documents informative tracebacks and selection options.
MSTest, NUnit, TUnit, or xUnit.net .NET projects choosing among the frameworks listed in Microsoft’s overview. Existing project conventions, IDE and runner support, CI integration, and team familiarity.
xUnit.net v3 .NET projects using the v3 getting-started path. The guide describes Visual Studio Code integration through xunit.runner.visualstudio and Microsoft.NET.Test.Sdk; confirm package setup against that guide.

Across frameworks, compare language fit, package and setup support, runner and IDE integration, CI support, assertion style, fixtures and parameterization, diagnostics, parallel execution, and maintainability. pytest documents captured output, informative tracebacks, test selection with -k and -m, debugger entry with --pdb, and optional parallel execution through pytest-xdist (pytest command-line usage). These are capabilities to evaluate against your project, not requirements every team must adopt.

How unit tests differ from integration tests

Unit tests focus on a small behavior in isolation or near-isolation. Integration tests check that multiple parts work together, such as application code interacting with a database or service. The boundary is a practical choice, not a universal law: a test that uses a real dependency can still be small and useful, but it may run more slowly or fail for environmental reasons. Use unit tests for quick, precise feedback and add broader tests where interactions are the risk. Neither category by itself proves that the full system is correct.

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

How much test coverage do you need?

There is no single coverage percentage established here as the right target for every project. A line or branch count can show which code ran during a suite, but it cannot tell you whether the assertions checked meaningful outcomes. Prioritize behaviors with significant consequences, boundary conditions, validation and error paths, and defects that have occurred. Use coverage as a way to find untested areas to review—not as a substitute for deciding what deserves a test.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Run tests regularly and diagnose failures

Local and CI workflow

Run the project’s tests while developing and make them part of CI so changes are checked before release. In Visual Studio, Microsoft’s tutorial walks through creating a unit-test project, referencing the production project, adding a test method, and using Test Explorer with Run All (Visual Studio tutorial). For .NET, dotnet test is a cross-platform command suitable for CI/CD scripts (.NET testing overview). For pytest, run pytest from the project directory.

Common problems and fixes

  • The test is not discovered: Check the framework’s naming conventions, file location, and runner or package setup. For pytest, follow its documented test-file and test-function patterns.
  • The test passes on one run and fails on another: Look for uncontrolled time, randomness, shared state, or external services. Make relevant inputs deterministic or isolate the dependency where that improves the test.
  • A harmless refactor breaks many tests: Review whether tests assert internal implementation details rather than the behavior callers rely on. Refactor brittle tests to express outcomes.
  • A failure message does not identify the issue: Give the test a behavior-focused name, keep its setup small, and use explicit assertions that make the expected result clear.
  • The suite slows development: Separate fast focused checks from broader integration checks and review whether expensive setup is necessary for each unit test.

Or skip the browser setup

If the behavior you need is a website screenshot, a unit test is not a substitute for capturing the page: it can test your own code, but a real capture involves a browser and page-loading behavior. ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. Its API accepts one GET request for an image or PDF; clean-up options remove cookie/consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, and cache hits are not billed, with verdict and billing information returned in response headers. Its MCP server offers take_screenshot, get_page_info, and capture_pdf tools for AI agents using Claude, Cursor, or another MCP client. See ScreenshotNeo and the API documentation.

Example cURL request (replace the target URL and API key):

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

The same endpoint can be called from Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)

Or from Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo includes 1,000 screenshots a month on its free plan with no card; paid plans start at $5 for 3,000 screenshots. Those are service plan allowances, not test-runner costs. Sign up for 1,000 free screenshots a month with no card.

Further reading

For a more detailed treatment of test design, Unit Testing: Principles, Practices, and Patterns is a relevant book-length resource.

Frequently Asked Questions

Can unit tests replace manual or exploratory testing?

No. Unit tests check programmed expectations for selected behaviors; they do not explore every user path or prove the whole application works.

Should every dependency be mocked in a unit test?

No. Use test doubles when they improve isolation or determinism; the right boundary depends on what the test is intended to verify.

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

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.