Introduction
Testing has evolved dramatically in 2026. From the rise of AI-assisted test generation to sophisticated chaos testing in production, modern QA practices emphasize automation, speed, and comprehensive coverage. This guide explores the testing strategies, tools, and patterns that leading engineering teams use to ship reliable software.
Quality is not an act, it’s a habit. In modern software development, testing is integrated into every stage of the development lifecycle, from code review to production monitoring.
The Testing Pyramid
Classic Pyramid
โฑโฒ
โฑ โฒ E2E Tests (few)
โฑโโโโโฒ
โฑ โฒ Integration Tests
โฑโโโโโโโโโฒ
โฑ โฒ Unit Tests (many)
โฑโโโโโโโโโโโโโฒ
Modern Testing Spectrum
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Testing Spectrum โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ Production โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Development โ
โ โ
โ โ Chaos Testing โ Contract Testing โ
โ โ Monitoring โ Integration Tests โ
โ โ A/B Testing โ Unit Tests โ
โ โ Canary Analysis โ Linting โ
โ โ Real User Monitoring โ Static Analysis โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Unit Testing
Best Practices
import pytest
class TestPaymentProcessor:
"""Unit tests for payment processing."""
def test_process_payment_success(self):
"""Test successful payment processing."""
processor = PaymentProcessor(gateway=MockPaymentGateway())
result = processor.process(
amount=Decimal("100.00"),
currency="USD",
card=valid_card
)
assert result.status == "success"
assert result.transaction_id is not None
def test_process_payment_invalid_card(self):
"""Test payment with invalid card."""
processor = PaymentProcessor(gateway=MockPaymentGateway())
with pytest.raises(InvalidCardError):
processor.process(
amount=Decimal("100.00"),
currency="USD",
card=invalid_card
)
def test_process_payment_insufficient_funds(self):
"""Test payment with insufficient funds."""
gateway = MockPaymentGateway()
gateway.balance = Decimal("50.00")
processor = PaymentProcessor(gateway=gateway)
result = processor.process(
amount=Decimal("100.00"),
currency="USD",
card=valid_card
)
assert result.status == "declined"
assert result.reason == "insufficient_funds"
Test Coverage
# Coverage configuration
[tool.coverage.run]
source = ["src"]
omit = ["*/tests/*", "*/migrations/*"]
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"if TYPE_CHECKING:",
"raise NotImplementedError",
"if __name__ == .__main__.:",
]
show_missing = true
Integration Testing
Database Integration
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
@pytest.fixture
def test_db():
"""Create a test database."""
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
yield session
session.close()
def test_create_order(test_db):
"""Test order creation with database."""
order_service = OrderService(db=test_db)
order = order_service.create(
customer_id="cust-123",
items=[{"product_id": "prod-1", "quantity": 2}]
)
assert order.id is not None
assert order.status == "pending"
# Verify in database
retrieved = test_db.query(Order).filter_by(id=order.id).first()
assert retrieved is not None
API Integration
import httpx
import pytest
@pytest.fixture
def api_client():
"""HTTP client for testing."""
return httpx.Client(base_url="http://testserver")
def test_create_user(api_client):
"""Test user creation endpoint."""
response = api_client.post("/api/users", json={
"email": "[email protected]",
"name": "Test User"
})
assert response.status_code == 201
data = response.json()
assert data["email"] == "[email protected]"
assert "id" in data
End-to-End Testing
Playwright Example
# test_e2e.py
from playwright.sync_api import Page, expect
def test_user_checkout_flow(page: Page):
"""Test complete checkout flow."""
# Navigate to store
page.goto("https://shop.example.com")
# Add item to cart
page.click("[data-testid=product-1]")
page.click("[data-testid=add-to-cart]")
# Go to cart
page.click("[data-testid=cart-icon]")
# Proceed to checkout
page.click("[data-testid=checkout-button]")
# Fill checkout form
page.fill("[data-testid=email]", "[email protected]")
page.fill("[data-testid=card-number]", "4242424242424242")
page.fill("[data-testid=expiry]", "12/28")
page.fill("[data-testid=cvc]", "123")
# Place order
page.click("[data-testid=place-order]")
# Verify success
expect(page.locator("[data-testid=order-success]")).to_be_visible()
expect(page.locator("[data-testid=order-number]")).to_contain_text("ORD-")
Cypress Example
// cypress/e2e/checkout.cy.js
describe('Checkout Flow', () => {
beforeEach(() => {
cy.visit('/')
cy.addToCart('product-1')
cy.visit('/checkout')
})
it('should complete purchase', () => {
cy.fillCheckoutForm({
email: '[email protected]',
cardNumber: '4242424242424242',
expiry: '12/28',
cvc: '123'
})
cy.get('[data-testid=place-order]').click()
cy.get('[data-testid=order-success]').should('be.visible')
cy.url().should('include', '/order-confirmation')
})
})
Contract Testing
Provider Contract Testing
# Consumer-driven contract testing
import pytest
from pact import Consumer, Provider
@pytest.fixture
def pact():
return Consumer("payment-consumer").has_pact_with(
Provider("payment-provider")
)
def test_payment_validation(pact):
"""Test payment validation endpoint."""
pact.given("payment validation endpoint exists")
.upon_receiving("a validation request")
.with_request({
"method": "POST",
"path": "/api/validate",
"body": {
"card_number": "4242424242424242",
"amount": 100,
"currency": "USD"
}
})
.will_respond_with({
"status": 200,
"body": {
"valid": True,
"card_type": "visa"
}
})
# Verify the contract
result = payment_client.validate_card(
card_number="4242424242424242",
amount=100,
currency="USD"
)
assert result["valid"] is True
Consumer Contract Testing
# OpenAPI contract
openapi: 3.0.0
paths:
/api/payments:
post:
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- amount
- currency
properties:
amount:
type: number
currency:
type: string
responses:
'200':
description: Payment processed
content:
application/json:
schema:
type: object
properties:
transaction_id:
type: string
status:
type: string
Property-Based Testing
Hypothesis Example
from hypothesis import given, strategies as st
from datetime import datetime, timedelta
@given(
amount=st.decimals(min_value=0.01, max_value=1000000),
currency=st.sampled_from(["USD", "EUR", "GBP"]),
card_number=st.from_regex(r"\d{16}")
)
def test_payment_conversion(amount, currency, card_number):
"""Test currency conversion maintains value."""
converter = CurrencyConverter()
# Convert to USD and back
usd_amount = converter.to_usd(amount, currency)
final_amount = converter.from_usd(usd_amount, currency)
# Should be approximately equal (within rounding)
assert abs(final_amount - amount) < Decimal("0.01")
@given(
dates=st.lists(
st.datetimes(min_value=datetime(2020, 1, 1)),
min_size=1,
max_size=100
)
)
def test_date_sorting(dates):
"""Test that sorting dates produces correct order."""
sorted_dates = sorted(dates)
for i in range(len(sorted_dates) - 1):
assert sorted_dates[i] <= sorted_dates[i + 1]
Test Automation Frameworks
Pytest Configuration
# pytest.ini
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"-v",
"--strict-markers",
"--tb=short",
"--cov=src",
"--cov-report=html",
"--cov-report=term-missing",
]
markers = [
"slow: marks tests as slow",
"integration: marks tests as integration tests",
"e2e: marks tests as end-to-end tests",
]
filterwarnings = [
"ignore::DeprecationWarning",
]
Test Fixtures
import pytest
from typing import Generator
@pytest.fixture(scope="session")
def database_url() -> str:
"""Get database URL from environment."""
return os.environ.get("TEST_DATABASE_URL", "sqlite:///:memory:")
@pytest.fixture(scope="function")
def db_session(database_url) -> Generator[Session, None, None]:
"""Create a new database session for each test."""
engine = create_engine(database_url)
Session = sessionmaker(bind=engine)
session = Session()
yield session
session.close()
Base.metadata.drop_all(engine)
@pytest.fixture
def mock_payment_gateway():
"""Mock payment gateway."""
return MockPaymentGateway()
@pytest.fixture(autouse=True)
def reset_time():
"""Reset time-related mocks between tests."""
yield
# Cleanup any time mocks
pass
Shift-Left Testing
Pre-commit Hooks
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/psf/black
rev: 24.1.0
hooks:
- id: black
- repo: https://github.com/PyCQA/flake8
rev: 7.0.0
hooks:
- id: flake8
- repo: https://github.com/pycqa/isort
rev: 5.13.0
hooks:
- id: isort
- repo: local
hooks:
- id: pytest
name: pytest
entry: pytest
language: system
types: [python]
pass_filenames: false
args: ["--co", "-q"]
CI/CD Integration
# GitHub Actions
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
pip install pytest pytest-cov
pip install -r requirements.txt
- name: Lint
run: |
flake8 src tests
black --check src
isort --check-only src
- name: Type check
run: mypy src
- name: Unit tests
run: pytest tests/unit -v --cov
- name: Integration tests
run: pytest tests/integration -v
env:
DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}
AI-Assisted Testing
Test Generation
# AI-generated test example
from unittest.mock import patch
class TestPaymentProcessing:
"""AI-assisted test generation."""
@patch('app.payment_gateway.process')
def test_ai_generated_payment_flow(self, mock_process):
"""Generated test for payment processing."""
# Setup
mock_process.return_value = {
"status": "success",
"transaction_id": "txn-123"
}
# Execute
result = process_payment(
amount=100,
currency="USD",
card="4242424242424242"
)
# Assert
assert result["status"] == "success"
mock_process.assert_called_once()
Mutation Testing
# Mutmut configuration
[mutmut]
runner = pytest
dictation = pytest --tb=short
configuration =
--no-cov
tests/
Best Practices
- Test behavior, not implementation: Focus on what, not how
- Use descriptive names:
test_payment_declined_for_insufficient_funds - Follow AAA pattern: Arrange, Act, Assert
- Keep tests independent: No shared state between tests
- Mock external dependencies: Database, API calls, file system
- Run tests fast: Target < 10 minutes for full suite
- Test edge cases: Empty inputs, null values, boundary conditions
- Use test doubles: Mocks, stubs, fakes for isolation
Conclusion
Modern testing strategies combine multiple approaches to ensure software quality. From unit tests that catch bugs early to chaos testing that validates production resilience, comprehensive testing is essential for confident software delivery.
In 2026, AI-assisted test generation and property-based testing are becoming mainstream, but the fundamentals remain: test behavior, keep tests fast, and integrate testing into every stage of development.
Comments