3 / 80 · 12 Programming for QA · Python Essentials for QA← prev⊞ allnext →☰ Read as one page
1.3pytest: The Test Framework
pytest is the standard Python test framework. It is simpler than unittest, more powerful, and used by the majority of Python QA teams.
Basic Test Structure
# test_login.py
def test_successful_login(api_client):
response = api_client.post("/auth/login", json={
"email": "test@example.com",
"password": "ValidPass123!"
})
assert response.status_code == 200
assert "access_token" in response.json()
def test_login_with_invalid_password(api_client):
response = api_client.post("/auth/login", json={
"email": "test@example.com",
"password": "wrong"
})
assert response.status_code == 401
assert "access_token" not in response.json()
Fixtures
Fixtures provide reusable setup and teardown. They replace the setup/teardown methods of unittest with a more flexible, composable pattern.
# conftest.py
import pytest
import requests
@pytest.fixture(scope="session")
def base_url():
return os.environ.get("API_BASE_URL", "http://localhost:3000/api/v1")
@pytest.fixture(scope="session")
def auth_token(base_url):
r = requests.post(f"{base_url}/auth/login", json={
"email": "admin@test.com", "password": "adminpass"
})
return r.json()["access_token"]
@pytest.fixture
def auth_headers(auth_token):
return {"Authorization": f"Bearer {auth_token}"}
@pytest.fixture
def api_client(base_url, auth_headers):
session = requests.Session()
session.headers.update(auth_headers)
session.base_url = base_url
return session
Fixture scopes control lifecycle:
scope="function"(default): runs before/after each testscope="class": once per test classscope="module": once per filescope="session": once per test run
Parametrize
Run the same test with different data:
@pytest.mark.parametrize("email,expected_status", [
("valid@test.com", 200),
("", 400),
("not-an-email", 422),
("valid@test.com; DROP TABLE users", 422),
("a" * 255 + "@test.com", 422),
])
def test_login_email_validation(api_client, email, expected_status):
response = api_client.post("/auth/login", json={
"email": email, "password": "ValidPass123!"
})
assert response.status_code == expected_status
Markers
Tag tests for selective execution:
@pytest.mark.smoke
def test_health_check(base_url):
assert requests.get(f"{base_url}/health").status_code == 200
@pytest.mark.slow
def test_full_checkout_flow(api_client):
# ... lengthy test
pass
pytest -m smoke # Run only smoke tests
pytest -m "not slow" # Skip slow tests