mirror of
https://github.com/github/awesome-copilot.git
synced 2026-04-11 10:45:56 +00:00
* Add python-pypi-package-builder skill for Python packaging - Created `SKILL.md` defining decision-driven workflow for building, testing, versioning, and publishing Python packages. - Added reference modules covering PyPA packaging, architecture patterns, CI/CD, testing, versioning strategy, and release governance. - Implemented scaffold script to generate complete project structure with pyproject.toml, CI workflows, tests, and configuration. - Included support for multiple build backends (setuptools_scm, hatchling, flit, poetry) with clear decision rules. - Added secure release practices including tag-based versioning, branch protection, and OIDC Trusted Publishing. * fix: correct spelling issues detected by codespell
258 lines
6.4 KiB
Markdown
258 lines
6.4 KiB
Markdown
# Testing and Code Quality
|
|
|
|
## Table of Contents
|
|
1. [conftest.py](#1-conftestpy)
|
|
2. [Unit tests](#2-unit-tests)
|
|
3. [Backend unit tests](#3-backend-unit-tests)
|
|
4. [Running tests](#4-running-tests)
|
|
5. [Code quality tools](#5-code-quality-tools)
|
|
6. [Pre-commit hooks](#6-pre-commit-hooks)
|
|
|
|
---
|
|
|
|
## 1. `conftest.py`
|
|
|
|
Use `conftest.py` to define shared fixtures. Keep fixtures focused — one fixture per concern.
|
|
For async tests, use `pytest-asyncio` with `asyncio_mode = "auto"` in `pyproject.toml`.
|
|
|
|
```python
|
|
# tests/conftest.py
|
|
import pytest
|
|
from your_package.core import YourClient
|
|
from your_package.backends.memory import MemoryBackend
|
|
|
|
|
|
@pytest.fixture
|
|
def memory_backend() -> MemoryBackend:
|
|
return MemoryBackend()
|
|
|
|
|
|
@pytest.fixture
|
|
def client(memory_backend: MemoryBackend) -> YourClient:
|
|
return YourClient(
|
|
api_key="test-key",
|
|
backend=memory_backend,
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Unit Tests
|
|
|
|
Test both the happy path and the edge cases (e.g. invalid inputs, error conditions).
|
|
|
|
```python
|
|
# tests/test_core.py
|
|
import pytest
|
|
from your_package import YourClient
|
|
from your_package.exceptions import YourPackageError
|
|
|
|
|
|
def test_client_creates_with_valid_key():
|
|
client = YourClient(api_key="sk-test")
|
|
assert client is not None
|
|
|
|
|
|
def test_client_raises_on_empty_key():
|
|
with pytest.raises(ValueError, match="api_key"):
|
|
YourClient(api_key="")
|
|
|
|
|
|
def test_client_raises_on_invalid_timeout():
|
|
with pytest.raises(ValueError, match="timeout"):
|
|
YourClient(api_key="sk-test", timeout=-1)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_process_returns_expected_result(client: YourClient):
|
|
result = await client.process({"input": "value"})
|
|
assert "output" in result
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_process_raises_on_invalid_input(client: YourClient):
|
|
with pytest.raises(YourPackageError):
|
|
await client.process({}) # empty input should fail
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Backend Unit Tests
|
|
|
|
Test each backend independently, in isolation from the rest of the library. This makes failures
|
|
easier to diagnose and ensures your abstract interface is actually implemented correctly.
|
|
|
|
```python
|
|
# tests/test_backends.py
|
|
import pytest
|
|
from your_package.backends.memory import MemoryBackend
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_set_and_get():
|
|
backend = MemoryBackend()
|
|
await backend.set("key1", "value1")
|
|
result = await backend.get("key1")
|
|
assert result == "value1"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_missing_key_returns_none():
|
|
backend = MemoryBackend()
|
|
result = await backend.get("nonexistent")
|
|
assert result is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_removes_key():
|
|
backend = MemoryBackend()
|
|
await backend.set("key1", "value1")
|
|
await backend.delete("key1")
|
|
result = await backend.get("key1")
|
|
assert result is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ttl_expires_entry():
|
|
import asyncio
|
|
backend = MemoryBackend()
|
|
await backend.set("key1", "value1", ttl=1)
|
|
await asyncio.sleep(1.1)
|
|
result = await backend.get("key1")
|
|
assert result is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_different_keys_are_independent():
|
|
backend = MemoryBackend()
|
|
await backend.set("key1", "a")
|
|
await backend.set("key2", "b")
|
|
assert await backend.get("key1") == "a"
|
|
assert await backend.get("key2") == "b"
|
|
await backend.delete("key1")
|
|
assert await backend.get("key2") == "b"
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Running Tests
|
|
|
|
```bash
|
|
pip install -e ".[dev]"
|
|
pytest # All tests
|
|
pytest --cov --cov-report=html # With HTML coverage report (opens in browser)
|
|
pytest -k "test_middleware" # Filter by name
|
|
pytest -x # Stop on first failure
|
|
pytest -v # Verbose output
|
|
```
|
|
|
|
Coverage config in `pyproject.toml` enforces a minimum threshold (`fail_under = 80`). CI will
|
|
fail if you drop below it, which catches coverage regressions automatically.
|
|
|
|
---
|
|
|
|
## 5. Code Quality Tools
|
|
|
|
### Ruff (linting — replaces flake8, pylint, many others)
|
|
|
|
```bash
|
|
pip install ruff
|
|
ruff check . # Check for issues
|
|
ruff check . --fix # Auto-fix safe issues
|
|
```
|
|
|
|
Ruff is extremely fast and replaces most of the Python linting ecosystem. Configure it in
|
|
`pyproject.toml` — see `references/pyproject-toml.md` for the full config.
|
|
|
|
### Black (formatting)
|
|
|
|
```bash
|
|
pip install black
|
|
black . # Format all files
|
|
black . --check # CI mode — reports issues without modifying files
|
|
```
|
|
|
|
### isort (import sorting)
|
|
|
|
```bash
|
|
pip install isort
|
|
isort . # Sort imports
|
|
isort . --check-only # CI mode
|
|
```
|
|
|
|
Always set `profile = "black"` in `[tool.isort]` — otherwise black and isort conflict.
|
|
|
|
### mypy (static type checking)
|
|
|
|
```bash
|
|
pip install mypy
|
|
mypy your_package/ # Type-check your package source only
|
|
```
|
|
|
|
Common fixes:
|
|
|
|
- `ignore_missing_imports = true` — ignore untyped third-party deps
|
|
- `from __future__ import annotations` — enables PEP 563 deferred evaluation (Python 3.9 compat)
|
|
- `pip install types-redis` — type stubs for the redis library
|
|
|
|
### Run all at once
|
|
|
|
```bash
|
|
ruff check . && black . --check && isort . --check-only && mypy your_package/
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Pre-commit Hooks
|
|
|
|
Pre-commit runs all quality tools automatically before each commit, so issues never reach CI.
|
|
Install once per clone with `pre-commit install`.
|
|
|
|
```yaml
|
|
# .pre-commit-config.yaml
|
|
repos:
|
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
rev: v0.4.4
|
|
hooks:
|
|
- id: ruff
|
|
args: [--fix]
|
|
- id: ruff-format
|
|
|
|
- repo: https://github.com/psf/black
|
|
rev: 24.4.2
|
|
hooks:
|
|
- id: black
|
|
|
|
- repo: https://github.com/pycqa/isort
|
|
rev: 5.13.2
|
|
hooks:
|
|
- id: isort
|
|
|
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
rev: v1.10.0
|
|
hooks:
|
|
- id: mypy
|
|
additional_dependencies: [types-redis] # Add stubs for typed dependencies
|
|
|
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
rev: v4.6.0
|
|
hooks:
|
|
- id: trailing-whitespace
|
|
- id: end-of-file-fixer
|
|
- id: check-yaml
|
|
- id: check-toml
|
|
- id: check-merge-conflict
|
|
- id: debug-statements
|
|
- id: no-commit-to-branch
|
|
args: [--branch, master, --branch, main]
|
|
```
|
|
|
|
```bash
|
|
pip install pre-commit
|
|
pre-commit install # Install once per clone
|
|
pre-commit run --all-files # Run all hooks manually (useful before the first install)
|
|
```
|
|
|
|
The `no-commit-to-branch` hook prevents accidentally committing directly to `main`/`master`,
|
|
which would bypass CI checks. Always work on a feature branch.
|