python-test-automation · git:20260909.eb38ca7 · 2026-09-09 · sha256 80c87c64121caec4

python-test-automation git:20260909.eb38ca7A

Immutable. This exact content is served forever at /api/v1/blob/80c87c64121caec4.

---
name: python-test-automation
description: Guides writing Python functional and end-to-end tests that validate application behavior. Use this skill when writing or refactoring Python test automation.
---
# Python Test Automation (Functional Tests)

Apply this skill when implementing or refactoring Python test scripts that validate application behavior (features, APIs, flows).
It focuses on **structure**, **functional coverage**, and **reliability**; the senior-developer skill owns coverage discipline and linting, and this skill does not relax them.

## Test Scope: Behavior, Not Implementation

- **Functional tests** assert observable behavior: API responses, CLI output, side effects, state changes.
  They validate that features work from the user or client perspective.
- Do not assert on implementation details (internal variables, private methods, call counts) unless the spec explicitly requires it.
  Refactor tests when the implementation changes but behavior is unchanged.
- Trace tests to requirements or specs when they exist (e.g. "Traces: REQ-X", feature ID).
  One test or scenario per behavior; avoid duplicate coverage of the same requirement.

## Structure: Arrange, Act, Assert

- Use a clear **Arrange / Act / Assert** flow: set up preconditions, perform the action under test, assert outcomes.
  Keep each phase obvious; extract setup into fixtures or helpers when it repeats.
- Prefer one logical assertion target per test (one behavior).
  Multiple assertions are fine when they describe a single outcome (e.g. status code and body fields).
  Split tests when they would test different behaviors.
- Name tests to describe the scenario and expected result (e.g. `test_whoami_returns_admin_after_login`, `test_create_task_returns_201_with_id`).

## Fixtures and Test Data

- Use **fixtures** (pytest `@pytest.fixture`, unittest `setUp`/`setUpClass`) for shared setup: config paths, clients, auth tokens, temporary state.
  Avoid global mutable state; prefer dependency injection or explicit passing.
- Prefer **factories or builders** over hardcoded literals when test data varies (e.g. build request payloads from minimal defaults).
  Use constants for values that must be stable across runs (e.g. expected admin handle).
- Isolate tests: each test should be runnable in any order and not depend on another test's side effects.
  Use fresh state or documented shared state (e.g. "requires e2e_020 login first") only when the test framework or suite explicitly supports it.

## Assertions and Failure Messages

- Use **specific assertions** (e.g. `assert status == 200`, `assert "user=admin" in out`) with clear failure messages so logs explain what failed and why.
  Avoid bare `assert x` without a message for non-obvious conditions.
- For APIs: assert status codes, required fields, and error shapes.
  For CLI: assert exit code and relevant stdout/stderr content.
  Prefer structured checks (e.g. JSON path, parsed response) over raw string matching when the contract is structured.

## Determinism and Isolation

- Tests must be **deterministic**: no flaky dependence on timing, order, or external randomness.
  Use fixed inputs, mocks for time/random when necessary, and explicit waits or retries only when testing async/eventual behavior.
- Avoid **shared mutable state** between tests.
  Clean up in teardown or use context managers; do not rely on "run after test X" unless the runner guarantees order and isolation.
- For E2E: document environment assumptions (services up, env vars, auth).
  Prefer runnable scripts that fail fast with a clear message when preconditions are not met.

## Tooling and Frameworks

- Use **pytest** for new Python test suites when possible: fixtures, parametrization (`@pytest.mark.parametrize`), and plugins (e.g. `requests`, `pytest-httpx`) support functional tests well.
  Use **unittest** when the project already standardizes on it.
- For HTTP APIs: use `requests`, `httpx`, or framework-specific test clients.
  For CLI: run the binary via `subprocess` or a small helper; capture stdout/stderr and exit code.
- Follow project conventions: test discovery (e.g. `test_*.py`, `*_test.py`), tags/markers for suites (e.g. `@pytest.mark.e2e`), and the project's runner (e.g. `just e2e`, `pytest -m e2e`).

## BDD and Feature Files

- When the project uses **BDD** (Gherkin/feature files): keep scenarios in feature files; implement step definitions in Python.
  Steps should call into shared helpers or fixtures so behavior is implemented once.
  Avoid duplicating scenario logic in raw scripts.
- Map scenarios to requirements or acceptance criteria when traceability is expected.

## Anti-Patterns to Avoid

- **Testing implementation instead of behavior**: asserting internals that can change without changing the feature.
- **Flaky tests**: reliance on sleep, unsynchronized async behavior, or shared state that varies by run.
- **Opaque failures**: assertions without messages, or large dumps without highlighting the relevant part.
- **Duplicate coverage**: multiple tests that assert the same requirement without adding distinct scenarios.
- **Hardcoded secrets or environment**: use config, env vars, or fixtures so tests run in CI and locally without manual edits.

## Quick Reference

- **Scope:** Assert observable behavior (APIs, CLI, state); avoid asserting implementation details.
- **Structure:** Arrange / Act / Assert; one behavior per test; descriptive names.
- **Data:** Fixtures for setup; factories for varying data; constants for stable expectations.
- **Isolation:** No order dependence; no shared mutable state; deterministic runs.
- **Assertions:** Specific checks with clear failure messages; structured checks for structured outputs.
- **Tooling:** pytest (or project standard); HTTP clients or subprocess for CLI; follow project tags and runner.