**The problem:**
When testing a function with multiple inputs, you'd normally write separate test functions:
```python
def test_square_positive(): assert square(3) == 9
def test_square_negative(): assert square(-4) == 16
def test_square_zero(): assert square(0) == 0
```
This is repetitive. If the `square` function changes, you update the same logic in multiple places. `@pytest.mark.parametrize` solves this by running one test function multiple times with different inputs:
```python
import pytest
@pytest.mark.parametrize("n, expected", [
(3, 9),
(-4, 16),
(0, 0),
])
def test_square(n, expected):
assert square(n) == expected
```
Each row creates an independent test case — if `(3, 9)` fails, the others still run. pytest reports each separately.
**Syntax breakdown:**
```python
@pytest.mark.parametrize(
"param_names", # comma-separated string of parameter names
[ # list of values (or tuples for multiple params)
value1,
value2,
]
)
def test_something(param_names):
...
```
For a single parameter: pass a flat list. For multiple parameters: pass a list of tuples, one per test case.
**How test IDs are generated:**
pytest auto-generates a name for each case from the parameter values:
```
test_square[3-9] ← from (3, 9)
test_square[-4-16] ← from (-4, 16)
test_square[0-0] ← from (0, 0)
```
You can override these with `ids=`:
```python
@pytest.mark.parametrize("n, expected", [
(3, 9),
(-4, 16),
], ids=["positive", "negative"])
# → test_square[positive], test_square[negative]
```
**When a parametrized test fails:**
pytest shows exactly which parameter set caused it:
```
FAILED test_math.py::test_square[-4-16]
AssertionError: assert 4 == 16
```
You can then run just that failing case:
```bash
pytest -k "test_square[-4-16]"
```
**When NOT to use parametrize:**
If test cases have completely different logic — different assertions, different function calls — they should be separate test functions. Parametrize is for the same verification logic repeated across different inputs.
Write `square(n)` that returns `n * n`. Use `@pytest.mark.parametrize` to test it with at least 5 different inputs covering: a positive integer, a negative integer, zero, a float, and a large number.
Write `is_palindrome(s)` that returns True if a string reads the same backwards. Use `@pytest.mark.parametrize` with at least 4 palindromes and 3 non-palindromes, including the expected boolean as a second parameter.
Use `@pytest.mark.parametrize` to test `clamp(value, lo, hi)` with at least 5 cases covering: value within range, at lower boundary, at upper boundary, below lower bound, and above upper bound. Use four parameters per row: `value`, `lo`, `hi`, and `expected`.
The parametrized test below auto-generates unhelpful IDs like `test_parse_int[42-42]`. Add `ids=[...]` to the decorator with descriptive names. Run `pytest -v` before and after to see the difference.
Write `is_valid_username(s)` that returns True only if the string is 3–20 characters, contains only letters and digits, and does not start with a digit. Use `@pytest.mark.parametrize` to test at least 3 valid and 4 invalid usernames.
import pytest
def is_valid_username(s):
pass # implement the three validation rules
@pytest.mark.parametrize("username, expected", [
# valid (True) and invalid (False) cases
])
def test_is_valid_username(username, expected):
assert is_valid_username(username) == expected
Solution
import pytest
def is_valid_username(s):
if not s or not (3 <= len(s) <= 20):
return False
if s[0].isdigit():
return False
return all(c.isalnum() for c in s)
@pytest.mark.parametrize("username, expected", [
("alice", True),
("user123", True),
("A1b2C3", True),
("ab", False),
("a" * 21, False),
("1alice", False),
("alice!", False),
("", False),
])
def test_is_valid_username(username, expected):
assert is_valid_username(username) == expected
No split tab
Cookie preferences
We use necessary cookies to run the site. With your permission, we can also save your site preferences and use analytics and advertising cookies to understand usage and support the project.
Open tools in tabs.Exercises, IDE tools, and trainers stay available as site tabs.
Switch without losing context.Move between explanations, code, and utilities while keeping your place.
Use the sidebar as your map.The left panels hold navigation, settings, files, libraries, and tool controls.
PythonJavaScriptSQLite
One IDE, three practical modes
Python in the browser.Run small scripts, try libraries, and practice API requests without installing anything.
JavaScript for quick experiments.Test browser-friendly code and compare ideas next to your learning materials.
SQLite for data practice.Open the database explorer to inspect tables, write queries, and learn SQL workflows locally.
TopicIDE
Work side by side with split tabs
Keep instructions visible.Open an exercise or reference page beside the IDE instead of jumping back and forth.
Compare tools while you learn.Place regex checks, explanations, and code experiments next to each other when the task needs it.
Close the split when you are done.The workspace returns to a single focused tab, and your open site tabs remain available.
Code Typing Trainer
Or plain text
This trainer is designed for a physical keyboard.Open this section on a laptop or desktop with a wide screen. Touch typing practice will not work correctly on a phone.
Speed: 0 chars/min
0 words/min
Best speed (60s): 0 chars/min
0 words/min
Errors: 0
Total time: 0.0 s
To practice touch typing, avoid looking at your physical keyboard.