Testing Environment Setup
Chapter 0: Testing Environment Setup
Introduction
Before writing effective tests, you need a solid testing environment. Like setting up a workshop, having the right tools organized makes all the difference. This chapter covers everything needed to write, run, and manage Python tests professionally.
We'll use pytest, Python's most powerful testing framework. While Python includes unittest in its standard library, pytest offers intuitive syntax, better error messages, powerful plugins, and a thriving ecosystem. Most modern Python projects choose pytest.
By the end of this chapter, you'll have:
- pytest installed and configured
- A proper Python virtual environment
- A well-structured project layout
- Configuration files for pytest
- The ability to run tests from the command line
Installing pytest
Why pytest?
Pytest stands out for several reasons:
- Simple syntax: Tests look like regular functions
- Powerful assertions: Just use
assert, no special methods needed - Rich plugin ecosystem: pytest-cov, pytest-mock, pytest-xdist, and hundreds more
- Excellent error messages: When tests fail, pytest shows exactly what went wrong
- Backwards compatible: Works with unittest tests too
Installing with pip
The simplest way to install pytest is with pip, Python's package installer:
pip install pytestFor specific versions or additional tools:
# Install specific version
pip install pytest==7.4.3
# Install with coverage support
pip install pytest pytest-cov
# Install with mocking support
pip install pytest pytest-mockVerifying Installation
Let's verify pytest is installed correctly and check the version:
Managing Dependencies
For production projects, always document your dependencies:
requirements.txt:
pytest==7.4.3
pytest-cov==4.1.0
pytest-mock==3.12.0pyproject.toml (modern approach):
[project]
dependencies = [
"pytest>=7.4.0",
"pytest-cov>=4.1.0",
]
[project.optional-dependencies]
dev = [
"pytest-mock>=3.12.0",
]Virtual Environments
Why Virtual Environments Matter
Virtual environments isolate your project's dependencies from other Python projects. Without them, you might:
- Install conflicting package versions
- Pollute your system Python installation
- Make your project hard to reproduce on other machines
Think of a virtual environment as a clean, isolated workspace for each project.
Creating a Virtual Environment
Python 3.3+ includes venv in the standard library:
# Create a virtual environment named 'venv'
python3 -m venv venv
# On Windows
python -m venv venvThis creates a venv/ directory containing:
- A Python interpreter
- pip and setuptools
- Space for installed packages
Activating and Deactivating
On macOS/Linux:
# Activate
source venv/bin/activate
# Your prompt changes to show (venv)
(venv) $ python --version
# Deactivate
deactivateOn Windows:
# Activate
venv\Scripts\activate
# Deactivate
deactivateInstalling Dependencies in venv
Once activated, packages install only in this environment:
# Install pytest in the virtual environment
(venv) $ pip install pytest
# Install from requirements.txt
(venv) $ pip install -r requirements.txt
# Freeze current dependencies
(venv) $ pip freeze > requirements.txtBest Practices
- Always use virtual environments for projects
- Never commit
venv/to git (add it to.gitignore) - Document dependencies in
requirements.txtorpyproject.toml - Use consistent naming:
venv,.venv, orenv
Project Structure
Standard Python Project Layout
A well-organized project makes testing easier. Here's a typical structure:
myproject/
āāā src/
ā āāā myproject/
ā āāā __init__.py
ā āāā core.py
ā āāā utils.py
āāā tests/
ā āāā __init__.py
ā āāā test_core.py
ā āāā test_utils.py
āāā venv/
āāā pytest.ini
āāā requirements.txt
āāā setup.py
āāā README.mdTest Directory Organization
Pytest supports two common patterns:
1. Separate tests/ directory (recommended):
project/
āāā src/myproject/
ā āāā module.py
āāā tests/
āāā test_module.py2. Tests alongside code:
myproject/
āāā module.py
āāā test_module.pyThe separate directory keeps production code clean and makes it easy to exclude tests from distribution.
Naming Conventions
Pytest discovers tests by name:
- Test files:
test_*.pyor*_test.py - Test classes:
Test*(capital T) - Test functions:
test_*
Examples:
# test_calculator.py
def test_addition():
assert 2 + 2 == 4
class TestCalculator:
def test_subtraction(self):
assert 5 - 3 == 2The init.py File
Modern Python (3.3+) doesn't require __init__.py for regular packages, but it's still useful for test directories:
# tests/__init__.py
# Can be empty, or contain test fixtures/utilitiespytest Configuration
Configuration Files
Pytest can be configured via:
pytest.ini(dedicated pytest config)pyproject.toml(modern Python projects)setup.cfg(legacy projects)
pytest.ini Basics
Create pytest.ini in your project root:
[pytest]
# Where to find tests
testpaths = tests
# Test file patterns
python_files = test_*.py *_test.py
# Test class patterns
python_classes = Test*
# Test function patterns
python_functions = test_*
# Minimum Python version
minversion = 7.0
# Add options to every pytest run
addopts =
-v
--strict-markers
--tb=shortpyproject.toml Configuration
For modern projects using pyproject.toml:
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"-v",
"--strict-markers",
"--cov=src",
]Common Configuration Options
testpaths: Directories to search for testspython_files: File patterns to matchaddopts: Default command-line optionsmarkers: Custom test markersminversion: Minimum pytest version required
Custom Markers
Define custom markers to categorize tests:
[pytest]
markers =
slow: marks tests as slow
integration: marks tests as integration tests
unit: marks tests as unit testsUse them in tests:
import pytest
@pytest.mark.slow
def test_complex_operation():
# ... slow test
passRun specific markers:
pytest -m "not slow" # Skip slow tests
pytest -m integration # Run only integration testsRunning Your First Test
Creating a Simple Test
Create test_basic.py in your tests directory:
# tests/test_basic.py
def test_addition():
"""Test that addition works correctly."""
result = 2 + 2
assert result == 4
def test_string_concatenation():
"""Test string concatenation."""
result = "Hello" + " " + "World"
assert result == "Hello World"
def test_list_operations():
"""Test list operations."""
numbers = [1, 2, 3]
numbers.append(4)
assert len(numbers) == 4
assert 4 in numbersRunning pytest
From your project directory:
# Run all tests
pytest
# Run with verbose output
pytest -v
# Run specific file
pytest tests/test_basic.py
# Run specific test
pytest tests/test_basic.py::test_additionUnderstanding pytest Output
When you run pytest, you'll see:
============================= test session starts ==============================
platform linux -- Python 3.11.0, pytest-7.4.3, pluggy-1.3.0
rootdir: /home/user/myproject
collected 3 items
tests/test_basic.py ... [100%]
============================== 3 passed in 0.02s ===============================Each . represents a passing test. Failed tests show F, skipped tests show s.
Verbose Mode
Use -v for detailed output:
pytest -v
# Output shows each test name:
tests/test_basic.py::test_addition PASSED [ 33%]
tests/test_basic.py::test_string_concatenation PASSED [ 66%]
tests/test_basic.py::test_list_operations PASSED [100%]Test Discovery
How pytest Finds Tests
Pytest automatically discovers tests using these rules:
- Search directories in
testpaths(or current directory) - Look for files matching
test_*.pyor*_test.py - Inside files, find:
- Functions matching
test_* - Classes matching
Test* - Methods matching
test_*insideTest*classes
- Functions matching
Naming Conventions Deep Dive
ā Valid test names:
# Files
test_user.py
test_api.py
calculator_test.py
# Functions
def test_login():
pass
def test_logout():
pass
# Classes
class TestUser:
def test_creation(self):
passā Invalid test names (pytest won't discover):
# Files
user_tests.py # Wrong pattern
# Functions
def check_login(): # Doesn't start with test_
pass
# Classes
class UserTests: # Doesn't start with Test
passRunning Specific Tests
Target tests precisely:
# Run all tests in a directory
pytest tests/unit/
# Run a specific file
pytest tests/test_api.py
# Run a specific function
pytest tests/test_api.py::test_get_user
# Run a specific method in a class
pytest tests/test_api.py::TestUserAPI::test_create_user
# Run tests matching a pattern
pytest -k "user" # Runs all tests with "user" in the name
pytest -k "not slow" # Skip tests with "slow" in nameCustom Discovery Patterns
Override default patterns in pytest.ini:
[pytest]
# Find tests in different directories
testpaths = tests integration_tests
# Match different file patterns
python_files = test_*.py check_*.py *_test.py
# Match different function patterns
python_functions = test_* check_*Skipping Tests
Mark tests to skip:
import pytest
@pytest.mark.skip(reason="Not implemented yet")
def test_future_feature():
pass
@pytest.mark.skipif(sys.version_info < (3, 10), reason="Requires Python 3.10+")
def test_new_feature():
passNext Steps
You now have a complete testing environment! You've learned how to:
- ā Install pytest and verify it works
- ā Create virtual environments for isolation
- ā Structure Python projects for testing
- ā Configure pytest with pytest.ini
- ā Run tests from the command line
- ā Understand how pytest discovers tests
In the next chapter, we'll dive into Unit Testing Fundamentals, where you'll learn to write comprehensive test suites using pytest's full power.
Course Recommendations
Ready to take your Python testing skills further? Check out these courses on paiml.com:
Python Testing with pytest
- Complete pytest ecosystem coverage
- Test-Driven Development workflows
- Fixtures, parametrization, and plugins
- Real-world testing patterns
- Enroll at paiml.com
DevOps for Python Developers
- CI/CD integration with GitHub Actions
- Automated testing pipelines
- Code quality tools and enforcement
- Docker and containerization for testing
- Enroll at paiml.com
Professional Python Development
- Complete development environment setup
- Virtual environments and dependency management
- Project structure best practices
- Testing strategies for production code
- Enroll at paiml.com