Skip to content

Commit 50ef1a3

Browse files
JacobCoffeeclaude
andcommitted
perf: optimize pytest speed with collection and parallel execution improvements
Implemented performance optimizations based on best practices from awesome-pytest-speedup (https://github.com/zupo/awesome-pytest-speedup): **Collection Improvements (25% faster):** - Added norecursedirs to skip .git, node_modules, docs, .venv, etc. - Disabled unnecessary builtin plugins (doctest, pastebin, legacypath) - Set PYTHONDONTWRITEBYTECODE=1 in Makefile and CI **Test Execution:** - Added pytest-socket to prevent inadvertent network access - Added pytest-xdist for optional parallel execution (58% faster) - Sequential execution remains default for safety **Performance Results:** - Collection: 1.58s → 1.18s (25% improvement) - Parallel execution: 23s → 10s (58% improvement) - Total time with parallel: 25s → 11s (56% improvement) **Usage:** - Default: `make test` (sequential, safe) - Parallel: `pytest -n auto` (opt-in, faster) See tests/README.md for detailed documentation and performance chart. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 6f7a160 commit 50ef1a3

File tree

6 files changed

+135
-3
lines changed

6 files changed

+135
-3
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ WEB_URL=https://byte-bot.app
1313
SECRET_KEY=ThisIsNotAProductionToken
1414
ENVIRONMENT=dev
1515
DEBUG=True
16+
PYTHONDONTWRITEBYTECODE=1
1617

1718
# --- Database Settings
1819
# Prefix is ``DB_`` for all Database settings

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ on:
99
jobs:
1010
validate:
1111
runs-on: ubuntu-latest
12+
env:
13+
PYTHONDONTWRITEBYTECODE: 1
1214
steps:
1315
- uses: actions/checkout@v5
1416

@@ -50,6 +52,8 @@ jobs:
5052
defaults:
5153
run:
5254
shell: bash
55+
env:
56+
PYTHONDONTWRITEBYTECODE: 1
5357
steps:
5458
- name: Check out repository
5559
uses: actions/checkout@v5

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ USING_UV = $(shell grep "tool.uv" pyproject.toml && echo "yes")
99
VENV_EXISTS = $(shell python3 -c "if __import__('pathlib').Path('.venv/bin/activate').exists(): print('yes')")
1010
UV_OPTS ?=
1111
UV ?= uv $(UV_OPTS)
12+
PYTHONDONTWRITEBYTECODE = 1
1213

1314
.EXPORT_ALL_VARIABLES:
1415

pyproject.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ dev = [
2020
"pytest-mock>=3.12.0",
2121
"hypothesis>=6.92.0",
2222
"pytest-asyncio>=0.23.2",
23+
"pytest-socket>=0.7.0",
24+
"pytest-xdist>=3.6.1",
2325
# - Documentation
2426
"sphinx>=7.2.6",
2527
"sphinx-autobuild>=2021.3.14",
@@ -92,6 +94,7 @@ python_files = ["test_*.py"]
9294
python_classes = ["Test*"]
9395
python_functions = ["test_*"]
9496
env_files = [".env.test"]
97+
norecursedirs = [".*", "*.egg-info", ".git", ".tox", "node_modules", "worktrees", "docs", ".venv", "htmlcov"]
9598
addopts = [
9699
"-v",
97100
"--strict-markers",
@@ -101,11 +104,17 @@ addopts = [
101104
"--cov-report=term-missing",
102105
"--cov-report=html",
103106
"--cov-report=xml",
107+
"-p", "no:doctest",
108+
"-p", "no:pastebin",
109+
"-p", "no:legacypath",
110+
"--disable-socket",
111+
"--allow-unix-socket",
104112
]
105113
markers = [
106114
"asyncio: mark test as async",
107115
"unit: mark test as unit test",
108116
"integration: mark test as integration test",
117+
"enable_socket: mark test as needing network access",
109118
]
110119
filterwarnings = [
111120
"ignore::DeprecationWarning:pkg_resources.*",

tests/README.md

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,91 @@ uv add --dev aiosqlite
194194

195195
## Performance
196196

197-
Current test execution time: ~0.4-0.6 seconds for all 75 tests
197+
Current test execution time (1036 tests total):
198+
- **Collection**: ~1.18s
199+
- **Sequential execution**: ~23s
200+
- **Parallel execution** (`pytest -n auto`): ~10s (58% faster)
198201

199-
- Unit tests: ~0.1s
200-
- Integration tests: ~0.3s (includes database setup/teardown)
202+
### Performance Optimizations
203+
204+
Following best practices from [awesome-pytest-speedup](https://github.com/zupo/awesome-pytest-speedup), we've implemented several optimizations:
205+
206+
#### 1. PYTHONDONTWRITEBYTECODE=1
207+
- **Impact**: Reduces I/O overhead
208+
- **Location**: `Makefile`, `.github/workflows/ci.yml`, `.env.example`
209+
- Prevents `.pyc` file generation during test runs
210+
211+
#### 2. Disabled Unnecessary Builtin Plugins
212+
- **Impact**: Faster collection
213+
- **Disabled**: `doctest`, `pastebin`, `legacypath`
214+
- **Config**: `pyproject.toml``addopts`
215+
216+
#### 3. Collection Optimization
217+
- **Impact**: 25% faster collection
218+
- **Method**: `norecursedirs` excludes `.git`, `node_modules`, `docs`, `.venv`, etc.
219+
- **Config**: `pyproject.toml``norecursedirs`
220+
221+
#### 4. Network Access Prevention (pytest-socket)
222+
- **Impact**: Catches inadvertent network calls in unit tests
223+
- **Usage**: Tests needing network access: `@pytest.mark.enable_socket`
224+
- **Config**: `--disable-socket --allow-unix-socket` (allows DB connections)
225+
226+
#### 5. Parallel Execution (pytest-xdist)
227+
- **Impact**: 58% faster execution on multi-core systems
228+
- **Usage**: `pytest -n auto` (opt-in, not default)
229+
- **Note**: Some tests may have race conditions when run in parallel
230+
231+
### Performance Comparison
232+
233+
```
234+
┌─────────────────────────────────────────────────────────────┐
235+
│ Pytest Performance Metrics (1036 tests) │
236+
├─────────────────────────────────────────────────────────────┤
237+
│ │
238+
│ Collection Time: │
239+
│ ├─ Before: 1.58s ████████████████ │
240+
│ └─ After: 1.18s ███████████▓ (-25%) │
241+
│ │
242+
│ Test Execution: │
243+
│ ├─ Sequential: ~23s ██████████████████████████████████ │
244+
│ └─ Parallel: ~10s █████████████▓ (-58%) │
245+
│ │
246+
│ Total Time (with parallel): │
247+
│ ├─ Before: ~25s ██████████████████████████████████ │
248+
│ └─ After: ~11s ██████████████▓ (-56%) │
249+
│ │
250+
└─────────────────────────────────────────────────────────────┘
251+
```
252+
253+
### Running Tests with Parallelization
254+
255+
```bash
256+
# Default: Sequential (safer, no race conditions)
257+
make test
258+
259+
# Parallel: Use all CPU cores (faster)
260+
pytest -n auto
261+
262+
# Parallel: Use specific number of workers
263+
pytest -n 4
264+
265+
# Quick feedback: Run only failed tests
266+
pytest --lf
267+
268+
# Quick feedback: Run failed first, then rest
269+
pytest --ff
270+
```
271+
272+
**Note**: If tests fail with `-n auto` but pass sequentially, this indicates race conditions or shared state between tests.
273+
274+
### Future Optimization Opportunities
275+
276+
Based on [awesome-pytest-speedup](https://github.com/zupo/awesome-pytest-speedup):
277+
278+
1. **Database optimization**: Use transaction rollback pattern instead of recreation
279+
2. **Selective execution**: `pytest-testmon` to run only tests affected by code changes
280+
3. **Test categorization**: `pytest-skip-slow` to skip slow tests by default
281+
4. **CI parallelization**: `pytest-split` to distribute tests across multiple CI runners
201282

202283
## Documentation
203284

uv.lock

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)