**Why keep tests separate from production code?**
Tests are not part of your shipping product. They live alongside your code to verify it, but users never run them. Keeping tests in a dedicated `tests/` directory (or in `test_*.py` files) makes this separation clear, avoids cluttering imports, and gives you control over what goes into a deployment package.
**How pytest discovers tests:**
pytest walks your project directory tree and applies these rules at each level:
1. **File level** — collect files matching `test_*.py` or `*_test.py`
2. **Module level** — collect functions whose name starts with `test_`
3. **Class level** — collect classes whose name starts with `Test` (no `__init__`)
4. **Method level** — collect methods inside `Test*` classes starting with `test_`
A few examples of what gets collected vs skipped:
```
test_math.py → collected
math_test.py → collected
math.py → not collected
helpers.py → not collected
def test_add(): → collected
def add_test(): → not collected (no test_ prefix)
def check_add(): → not collected
class TestMath: → collected
class MathTest: → not collected (wrong prefix)
class TestMath:
def test_add(): → collected
def add_check(): → not collected
```
You can run `pytest --collect-only` to see exactly what would be collected before actually running anything.
**Plain functions vs test classes:**
Plain functions are simpler and sufficient for most cases:
```python
def test_add():
assert add(2, 3) == 5
def test_subtract():
assert subtract(5, 3) == 2
```
Use classes when you have a group of related tests that share a concept — classes act as a namespace and make the relationship explicit:
```python
class TestUserAccount:
def test_creation(self):
...
def test_deactivation(self):
...
def test_password_change(self):
...
```
Classes also support `setup_method` / `teardown_method` for per-test setup. However, for shared *data*, fixtures (covered next) are a cleaner solution than class-level state.
**Recommended project layout:**
```
my_project/
├── src/
│ ├── users.py
│ └── products.py
├── tests/
│ ├── conftest.py ← shared fixtures (covered in a later topic)
│ ├── test_users.py
│ └── test_products.py
└── pytest.ini
```
Mirroring the `src/` structure inside `tests/` makes it easy to find tests for any given module.
Discovery in Action: Functions, Classes, and Layout
The file below has 6 flat test functions for two topics (string reversal and list filtering). Reorganize them into two `Test*` classes — `TestReverse` and `TestFilter` — without changing any assert statements.
Create a `TestStringUtils` class with three test methods: one for `capitalize_words(s)` (capitalizes each word), one for `count_vowels(s)` (counts a, e, i, o, u case-insensitively), and one for `is_palindrome(s)`.
def capitalize_words(s):
return ' '.join(word.capitalize() for word in s.split())
def count_vowels(s):
return sum(1 for c in s.lower() if c in 'aeiou')
def is_palindrome(s):
return s == s[::-1]
class TestStringUtils:
pass # add three test methods here
Solution
def capitalize_words(s):
return ' '.join(word.capitalize() for word in s.split())
def count_vowels(s):
return sum(1 for c in s.lower() if c in 'aeiou')
def is_palindrome(s):
return s == s[::-1]
class TestStringUtils:
def test_capitalize_words(self):
assert capitalize_words('hello world') == 'Hello World'
assert capitalize_words('python') == 'Python'
def test_count_vowels(self):
assert count_vowels('hello') == 2
assert count_vowels('rhythm') == 0
assert count_vowels('AEIOU') == 5
def test_is_palindrome(self):
assert is_palindrome('racecar') is True
assert is_palindrome('hello') is False
assert is_palindrome('') is True
Add `setup_method` to the `TestShoppingCart` class so each test gets a fresh cart. The cart should be a dict `{'items': [], 'total': 0}`. Verify that `test_cart_still_empty_after_other_test` passes even when run after `test_can_add_item`.
class TestShoppingCart:
# add setup_method here
def test_cart_starts_empty(self):
assert self.cart['items'] == []
def test_total_starts_at_zero(self):
assert self.cart['total'] == 0
def test_can_add_item(self):
self.cart['items'].append('apple')
assert len(self.cart['items']) == 1
def test_cart_still_empty_after_other_test(self):
# this must pass even if test_can_add_item ran first
assert self.cart['items'] == []
Create two test files: `test_math.py` with tests for `add` and `multiply`, and `test_strings.py` with tests for `str.upper()` and `str.lower()`. Run `pytest -k 'math'` and verify only the math tests run. Then run `pytest -k 'multiply'`.
# test_math.py
def add(a, b):
return a + b
def multiply(a, b):
return a * b
# write test_add and test_multiply here
# test_strings.py
# write test_upper and test_lower
Create a minimal project layout: put `string_utils.py` with a `slugify(s)` function (lowercases and replaces spaces with dashes) inside `src/`, put two tests inside `tests/test_string_utils.py`, and add `pytest.ini` with `testpaths = tests`. Run pytest from the project root.
# src/string_utils.py
def slugify(s):
pass # lowercase + replace spaces with dashes
# tests/test_string_utils.py
# import and test slugify here
# pytest.ini
# [pytest]
# testpaths = tests
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.