Pytest
Pytest is the standard testing framework for modern Python. Master professional testing strategies used by real engineering teams.
Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Master
This lesson takes you from basic testing → professional test suite architecture.
Part 1: Core pytest — Fixtures, Parametrisation & Basic Mocking
🔥 1. Why pytest?
Feature
pytest
unittest
Assertion syntax
assert x == y
self.assertEqual(x, y)
Boilerplate
None — just functions
Classes required
Fixtures
Powerful, injectable
setUp/tearDown only
Parametrisation
Built-in decorator
Manual loops
Plugin ecosystem
Huge (1000+ plugins)
Limited
🧪 2. Basic Test Structure
- Files: test_*.py
- Functions: test_*
- Classes: class TestSomething: (no __init__)
⚙️ 3. What Is a Fixture?
A fixture is reusable setup logic that you can "inject" into tests via function arguments.
🧱 4. Fixture Scopes
- • function = Clean after every guest (fresh for each test)
- • class = Clean after a group of guests checks out
- • module = Clean once a day (per file)
- • session = Deep clean once a week (per test run)
By default, fixtures are function-scoped. You can control lifespan with scope:
Scope
Runs
Use Case
function
Once per test
Safest, most isolated (default)
class
Once per class
Related tests share instance
module
Once per file
Expensive setup (DB connections)
session
Once per test run
Global resources (app config)
🔁 5. Setup & Teardown with yield
🧪 6. Parametrised Tests
Instead of writing multiple duplicate tests, use parametrisation:
🧪 7. Basic Mocking
Part 2: Advanced Fixtures & Mocking
🔧 Factory Fixtures
🧠 Combining Fixtures + Parametrisation
🔐 Monkeypatching
🛰 Mocking External APIs
🧪 Spy Objects
Part 3: Professional Test Suite Architecture
🧪 Test Suite Structure
🧱 Integration Testing
🧱 Mocking Time and Randomness
🎓 Summary
You've learned professional pytest strategies:
📋 Quick Reference — pytest
Syntax
What it does
def test_fn():
Define a test (must start with test_)
@pytest.fixture
Reusable setup/teardown function
@pytest.mark.parametrize
Run test with multiple inputs
pytest.raises(ValueError)
Assert an exception is raised
mocker.patch('module.fn')
Mock a function with pytest-mock
You can now write fixtures, parametrised tests, and mocks — the full professional pytest toolkit used at tech companies worldwide.
Up next: CLI Tools — build polished command-line applications with argparse and Typer.
Practice quiz
How do pytest tests check conditions?
- self.assertEqual(x, y)
- A plain assert statement, e.g. assert add(2, 3) == 5
- expect(x).toBe(y)
- check(x == y)
Answer: A plain assert statement, e.g. assert add(2, 3) == 5. pytest uses Python's built-in assert — no special methods or boilerplate classes required.
By naming convention, pytest discovers test functions that...
- start with test_
- end with _test
- are inside a Test class only
- have a @test decorator
Answer: start with test_. Files are test_*.py and functions start with test_ — that's how pytest finds them.
What is a pytest fixture?
- A failed test
- Reusable setup logic injected into tests via function arguments
- A mocking library
- A configuration file
Answer: Reusable setup logic injected into tests via function arguments. Fixtures provide reusable setup/teardown that tests receive as arguments.
What is the DEFAULT fixture scope?
- session
- module
- class
- function
Answer: function. Fixtures are function-scoped by default — fresh for each test, the safest and most isolated option.
Which scope runs a fixture only ONCE per entire test run?
- function
- class
- module
- session
Answer: session. session scope creates one instance for the whole run — ideal for global resources like app config.
In a fixture, what does the code AFTER a yield statement do?
- Provides the value to the test
- Runs as teardown after the test finishes
- Skips the test
- Nothing
Answer: Runs as teardown after the test finishes. yield returns the value; the lines after yield run as teardown once the test completes.
Why use @pytest.mark.parametrize?
- To mock external systems
- To run one test function with many input/output cases
- To set fixture scope
- To skip slow tests
Answer: To run one test function with many input/output cases. Parametrisation runs the same test across multiple inputs instead of duplicating test functions.
After fake_api.get.return_value = {'name': 'Boopie'}, what does fetch_user(fake_api) return for result['name']?
- None
- Boopie
- An error
- 'name'
Answer: Boopie. A Mock's return_value is what the call yields, so api.get('/user') returns that dict.
What does fake_api.get.assert_called_once_with('/user') verify?
- That get returned '/user'
- That get was called exactly once with that argument
- That get raised an exception
- That get is a real API
Answer: That get was called exactly once with that argument. It asserts the mock was called exactly one time with the given argument — a key way to verify behavior.
Which pytest construct asserts that a block raises a specific exception?
- pytest.raises(ValueError)
- pytest.expect(ValueError)
- assert ValueError
- pytest.catch(ValueError)
Answer: pytest.raises(ValueError). with pytest.raises(ValueError): ... passes only if that exception is raised inside the block.
Continue this course
- Previous: REST API
- Next: Final Projects