šŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
šŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
HTML MASTER CLASS /// LEARN TAGS /// BUILD STRUCTURE /// SEMANTIC WEB /// HTML MASTER CLASS /// LEARN TAGS ///

Parameterized Tests with pytest

pytest.mark.parametrize — running the same test logic against many input/output pairs without copy-pasting the test function once per case.

⚔ Total XP: 0|šŸ’» python XP: 0

Skill Matrix

UNLOCK NODES BY LEARNING NEW TAGS.

System Hub

Core logic.

Quick Quiz //

If the SECOND assert (is_even(3) == False) fails inside a single test function with four assert statements, what does pytest's failure report actually tell you?


šŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
šŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.

Testing a function against five different inputs shouldn't require five nearly-identical copy-pasted test functions. pytest.mark.parametrize runs the same test logic once per input set, keeping the test DRY and making it trivial to add another case.

1Why Multiple Assert Statements in One Test Fall Short

Testing a function against several different inputs inside a single test function with multiple assert statements has two real, compounding problems. First, Python's assert statement halts execution the *instant* it fails — a failing second assertion means the third and fourth assertions never even run during that test execution, so a single test run can hide additional failures that would only surface once the first one is fixed, extending debugging into multiple rounds instead of seeing every actual failure at once.

Second, and just as significant: when that second assertion fails, pytest's failure report identifies the specific line and the specific values involved (thanks to assertion rewriting from the pytest fundamentals lesson), but the test as a *whole* is reported as one single pass/fail unit named test_is_even — there's no separate, named record distinguishing 'the is_even(3) case failed' from 'the whole test failed', which becomes genuinely confusing to parse once a test covers more than a couple of cases.

The alternative — copy-pasting the test function once per input, each with a different assert — solves the 'which case failed' problem (each copy fails or passes independently, clearly named), but reintroduces the DRY concern this platform has emphasized throughout: four near-identical function bodies, differing only in their hardcoded input and expected value, is exactly the kind of duplication that makes a later change (updating what the test actually checks) require editing four separate, easy-to-miss locations consistently.

āœ•
—
+
# Unclear which case fails, and adding a case means editing this SAME function
def test_is_even():
    assert is_even(2) == True
    assert is_even(3) == False
    assert is_even(0) == True
    assert is_even(-4) == True
# If the SECOND assert fails, pytest just says 'AssertionError' -- not WHICH case
localhost:3000
The Problem With Multiple Asserts
Multiple asserts in one test
Stops at first failure; unclear reporting; OR copy-pasted duplication

2parametrize: One Test Definition, Many Independent Cases

@pytest.mark.parametrize("number, expected", [(2, True), (3, False), ...]) solves both problems simultaneously. The decorator's first argument names the parameters the test function will receive (number, expected, matching the test function's own parameter names exactly); the second argument is a list of tuples, each providing one complete set of values for those parameters. pytest generates one genuinely independent test *invocation* per tuple — not four assertions inside one test run, but four separate, independently-executed and independently-reported tests.

This directly solves the visibility problem: pytest's test report names each case distinctly (test_is_even[2-True], test_is_even[3-False], and so on, derived automatically from the parameter values), so a single failing case is immediately identifiable by name in the test output, without needing to trace back through which assert statement in a longer function happened to fail first. Because each case is a fully separate test execution, all four run to completion regardless of whether an earlier one fails — you see every failing case from a single test run, not just the first one encountered.

And it directly solves the duplication problem: the actual test *logic* — assert is_even(number) == expected — exists exactly once, in one function body, no matter how many cases are covered. Adding a new case is a genuinely one-line change (appending one more tuple to the list), with zero risk of a copy-pasted duplicate silently drifting out of sync with the others over time.

āœ•
—
+
import pytest

@pytest.mark.parametrize("number, expected", [
    (2, True),
    (3, False),
    (0, True),
    (-4, True),
])
def test_is_even(number, expected):
    assert is_even(number) == expected
# pytest reports FOUR separate tests: test_is_even[2-True], test_is_even[3-False], etc.
localhost:3000
Independent, DRY Test Cases
4 tuples → 4 independently-reported tests
One test body, zero duplication, clear per-case reporting

3When Parametrization Genuinely Pays Off

Parametrization earns its value specifically when a function's core logic is being tested against meaningfully varied inputs that exercise the same code path — boundary conditions (0, negative numbers, very large numbers for a numeric function), different branches of the same conditional logic, or a range of valid and invalid inputs for a validation function. In these cases, the underlying assertion logic (assert is_even(number) == expected) is genuinely identical across every case; only the specific input and expected output differ — exactly the shape parametrize is designed for.

It's worth recognizing when parametrization *isn't* the right fit: if different inputs genuinely require meaningfully different assertion logic, setup, or verification steps (not just different input/output values plugged into the same check), forcing them into one parametrized test can produce a confusing, overly-generic test function trying to handle cases that don't actually share a common shape — separate, clearly-named test functions remain the better choice for genuinely distinct scenarios.

Parametrize also composes cleanly with fixtures (the previous lesson) — a parametrized test can still request fixtures normally alongside its parametrized values, and multiple @pytest.mark.parametrize decorators can even be stacked on the same test function to test the Cartesian product of two independent sets of parameters, directly echoing the itertools.product pattern from the Advanced Standard Library module, now applied specifically to generating test case combinations.

āœ•
—
+
@pytest.mark.parametrize("number, expected", [
    (2, True),
    (3, False),
    (0, True),
    (-4, True),
    (1_000_000, True),   # just add another tuple -- that's the entire change
])
def test_is_even(number, expected):
    assert is_even(number) == expected
localhost:3000
When It Fits
Same logic, varied inputs
The right fit for parametrize — genuinely different logic per case is not

4Step-by-Step Breakdown

Five copy-pasted test functions differing only in their input values is exactly the kind of duplication parametrize exists to eliminate — one test definition, five (or five hundred) cases.

Without parametrize, testing multiple cases means either one test with several asserts (unclear WHICH case failed) or copy-pasted near-identical test functions.

Checkpoint: If the SECOND assert (is_even(3) == False) fails inside a single test function with four assert statements, what does pytest's failure report actually tell you?

  • →It reports the failure location precisely enough to identify the line, but doesn't distinguish this as a separate, independently-named failing 'case' the way parametrize would
  • →It clearly lists all four cases and marks specifically which ones passed and which failed

@pytest.mark.parametrize runs the SAME test body once per input tuple -- each case is reported as its OWN separate, independently pass/fail test.

Checkpoint: With @pytest.mark.parametrize and 4 input tuples, how many independently pass/fail-reported tests does pytest actually run?

  • →Four separate tests, each independently reported as passing or failing, and each continuing even if another one fails
  • →Still just one combined test, the same as writing four asserts manually

Adding a new test case is now a ONE-LINE change -- no new function, no copy-pasting, no risk of the new copy silently diverging from the others.

Parameterized tests cover input variety efficiently; Test Coverage measures how much of your actual code all these tests collectively exercise.

Run a Real Parameterized Test Suite. Finish run_parameterized_tests(): one test body, many inputs.

Level Up šŸš€

Advanced cheat sheets, SEO tricks, and interview prep for this topic.

Browser Support

ChromeSupported

Fully supported (via server-side Python execution).

FirefoxSupported

Fully supported (via server-side Python execution).

SafariSupported

Fully supported (via server-side Python execution).

EdgeSupported

Fully supported (via server-side Python execution).

Best Practices

Use parametrize whenever testing the same logic against multiple, meaningfully different inputs

It eliminates copy-pasted near-duplicate test functions, and pytest reports each case independently with a clear, distinct name identifying exactly which input failed.

Don't force genuinely different test scenarios into one parametrized test just to avoid writing separate functions

Parametrize fits cases sharing identical assertion logic with only the input/output values varying — scenarios needing genuinely different setup or verification steps are better served by separate, clearly-named test functions.

Frequent Bugs

THE BUG

Writing multiple separate assert statements testing different input cases inside a single test function, causing execution to stop at the first failure and hiding whether subsequent cases would have also failed.

THE FIX

Use @pytest.mark.parametrize to run each input case as an independent test, so every case executes and reports its own pass/fail status regardless of whether other cases fail.

Real-World Examples

Parameterized Tests for an Input Validation Function

A validate_email function needs to be tested against many valid and invalid email formats, and the team wants clear, individual reporting for exactly which specific format fails if a regression is introduced.

import pytest

@pytest.mark.parametrize("email, expected_valid", [
    ("user@example.com", True),
    ("invalid-email", False),
    ("user@", False),
    ("user+tag@example.co.uk", True),
    ("", False),
])
def test_validate_email(email, expected_valid):
    assert validate_email(email) == expected_valid

Interview Prep

?Frequently Asked Questions

Pascual Vila

Pascual Vila

Frontend Instructor // Code Syllabus

Common Pitfalls & Errors

The Error //

Writing several assert statements for different input cases inside one test function, causing execution to stop at the first failure and never revealing whether later cases would have also failed.

# Wrong: stops at the first failure, unclear which case failed def test_is_even(): assert is_even(2) == True assert is_even(3) == False # if THIS fails, the rest never even run # Correct: independent, clearly-reported cases @pytest.mark.parametrize("number, expected", [(2, True), (3, False)]) def test_is_even(number, expected): assert is_even(number) == expected

The Solution //

Use @pytest.mark.parametrize with a list of input/expected-output tuples so each case runs and reports independently, regardless of whether other cases fail.

Lesson Glossary

[01]@pytest.mark.parametrize

A pytest decorator that runs a single test function independently once per provided set of input parameters.

Code Preview
// @pytest.mark.parametrize context

[02]Test case

One independent execution of a parametrized test, corresponding to one input tuple, reported separately as pass or fail.

Code Preview
// Test case context

[03]Test duplication

The anti-pattern of copy-pasting near-identical test functions that differ only in hardcoded input/expected values.

Code Preview
// Test duplication context

[04]Boundary condition testing

Testing a function against edge-case inputs (zero, negative numbers, empty strings) — a common, natural fit for parametrization.

Code Preview
// Boundary condition testing context

Continue Learning