pytest
**pytest** is the **most popular Python testing framework**, offering simple syntax, powerful fixtures, and extensive plugin ecosystem that makes writing and running tests enjoyable and productive.
**What Is pytest?**
- **Definition**: Python test framework for writing and executing tests.
- **Philosophy**: Simple yet powerful, convention over configuration.
- **Key Strength**: Minimal boilerplate, intuitive syntax.
- **Ecosystem**: 800+ plugins for extending functionality.
- **Adoption**: De facto standard for Python testing.
**Why pytest Matters**
- **Simplicity**: Write tests as plain functions with assert statements
- **Fixtures**: Powerful reusable setup/teardown mechanisms
- **Parametrization**: Test multiple inputs with single test function
- **Plugins**: Extensive ecosystem covers almost any use case
- **Integration**: Works with Django, Flask, asyncio, etc.
- **Productivity**: Less boilerplate = write tests faster
- **Debugging**: Detailed failure information with pytest output
**Key Features**
**1. Simple Assertions**
```python
def test_user():
user = User("Alice", age=30)
assert user.name == "Alice" # Clear, readable
assert user.age == 30
assert user.is_adult() # Works with methods
```
**2. Fixtures (Setup & Teardown)**
```python
import pytest
@pytest.fixture
def database():
"""Setup: connect to database."""
db = Database()
db.connect()
yield db # Test runs here
db.disconnect() # Teardown
def test_query(database):
"""Fixture automatically injected as parameter."""
result = database.query("SELECT * FROM users")
assert len(result) > 0
```
**3. Parametrization**
```python
@pytest.mark.parametrize("input,expected", [
(1, 2),
(2, 4),
(3, 6)
])
def test_double(input, expected):
"""Test runs once for each parameter pair."""
assert input * 2 == expected
```
**4. Markers & Test Selection**
```python
@pytest.mark.slow
def test_slow_operation():
# Long-running test
pass
@pytest.mark.skip(reason="Not implemented")
def test_future():
pass
# Command line: pytest -m "not slow"
```
**5. Exception Testing**
```python
def test_division_by_zero():
"""Test that exceptions are raised correctly."""
with pytest.raises(ZeroDivisionError):
1 / 0
```
**Common Test Patterns**
**Mocking External Dependencies**:
```python
def test_api_call(mocker):
"""Mock external API calls."""
mock_api = mocker.patch("requests.get")
mock_api.return_value.json.return_value = {"status": "ok"}
result = fetch_data()
assert result["status"] == "ok"
```
**Temporary Files**:
```python
def test_file_processing(tmp_path):
"""pytest provides temporary directories."""
file = tmp_path / "test.txt"
file.write_text("hello")
result = process_file(file)
assert result == "HELLO"
```
**Fixtures with Scope**:
```python
@pytest.fixture(scope="session")
def expensive_resource():
"""Created once per test session."""
return ExpensiveSetup()
@pytest.fixture(scope="function")
def fresh_db():
"""Created for each test function."""
return Database()
```
**Configuration (pytest.ini)**
```ini
[pytest]
testpaths = tests
python_files = test_*.py
python_functions = test_*
addopts = -v --tb=short
markers =
slow: marks tests as slow
integration: integration tests
```
**Running Tests**
```bash
# All tests
pytest
# Specific file
pytest tests/test_user.py
# Specific test
pytest tests/test_user.py::test_login
# With coverage
pytest --cov=myapp
# Parallel execution
pytest -n 4
# Stop on first failure
pytest -x
# Verbose output
pytest -v
# Run marked tests
pytest -m slow
```
**Popular Plugins**
| Plugin | Purpose |
|--------|---------|
| pytest-cov | Code coverage reports |
| pytest-xdist | Parallel test execution |
| pytest-mock | Enhanced mocking helpers |
| pytest-django | Django testing utilities |
| pytest-asyncio | Async test support |
| pytest-timeout | Test timeout limits |
| pytest-repeat | Repeat tests N times |
**Best Practices**
1. **One assertion per test** (usually): Easier to understand failures
2. **Descriptive names**: `test_login_with_invalid_password` better than `test_login`
3. **Arrange-Act-Assert**: Setup → Action → Verification
4. **Use fixtures**: Don't repeat setup code
5. **Independent tests**: No test should depend on another
6. **Mock external deps**: Don't call real APIs/database
7. **Test behavior, not implementation**: Test what it does, not how
8. **Parametrize similar tests**: Reduce code duplication
**pytest vs unittest**
| Feature | pytest | unittest |
|---------|--------|----------|
| Syntax | assert statements | self.assertEqual() |
| Fixtures | Powerful, flexible | setUp/tearDown |
| Parametrization | Built-in | Manual loops |
| Plugins | 800+ available | Limited |
| Learning Curve | Gentle | Steeper |
| Enterprise Use | Increasingly standard | Legacy codebases |
**Common Assertions**
```python
assert x == 5 # Equality
assert x > 0 # Comparison
assert "hello" in text # Membership
assert user is None # Identity
assert callable(func) # Type checking
```
**Test Structure Template**
```python
import pytest
from mymodule import Calculator
@pytest.fixture
def calc():
return Calculator()
def test_addition(calc):
"""Test that addition works correctly."""
# Arrange
a, b = 2, 3
# Act
result = calc.add(a, b)
# Assert
assert result == 5
```
pytest is the **gold standard for Python testing** — combining simplicity for beginners with power for advanced users, making it the framework of choice for professionals and open-source projects worldwide.