perf: banco de teste pré-migrado nos testes de backend #43

Merged
jhony75 merged 1 commit from feature/backend-test-fixture-speedup into develop 2026-09-08 15:10:30 -03:00
Owner

Segunda parte da investigação de velocidade da suíte de backend (a primeira, instrumentação de fases + --durations=30, está na PR da branch ci-speed-investigation).

Causa raiz confirmada

_load_app(tmp_path, monkeypatch) era duplicado idêntico em 16 arquivos de teste e sempre apontava pra um SQLite vazio. O lifespan do FastAPI roda database.init_db() (== alembic upgrade head) a cada TestClient(app) — isso sozinho custava ~1.5s por teste (isolei essa medição diretamente, e bate com o --durations=30 real da CI: praticamente todo teste do top 30 ficava entre 2.5s e 4.9s). Com ~110 dos 151 testes nesse caminho, essa replay de migration respondia por ~180 dos ~250s da suíte inteira. Controle: os 30 testes que nunca tocam _load_app (test_architecture.py, test_hubspot_client.py, test_scenario_status.py) rodam em 1.55s total.

Mudança

Centraliza _load_app em backend/tests/conftest.py (novo) e muda seu comportamento: agora aponta pra uma cópia de um banco já migrado até head, construído uma única vez por sessão de pytest, em vez de um banco vazio. database.init_db() continua rodando de verdade a cada TestClient(app) via o lifespan de main.py — nada mudou lá — só que, como a cópia já está em head, vira uma checagem idempotente de ~50ms em vez de reaplicar as 18 migrations do zero.

Isolamento entre testes continua total: cada teste ainda recebe seu próprio arquivo físico (cópia fresca), nunca compartilha conexão, transação ou estado com outro teste — não é rollback/savepoint compartilhado, é um arquivo novo por teste, só que pré-populado em vez de vazio.

_load_app_from_scratch preserva o comportamento antigo (banco vazio, sem atalho nenhum) e é usado exclusivamente por test_alembic_initialization_is_idempotent_and_reaches_head — o único teste que existe especificamente pra provar que o Alembic aplica as migrations do zero. Esse continua rodando as migrations de verdade, sem nenhum atalho.

Resultado

Suíte inteira (151 testes, nenhum removido) caiu de ~262s para ~67s localmente — quase 4x mais rápida — sem tirar cobertura nenhuma.

Verificação

  • pytest -q — 151 passed (mesma contagem de antes)
  • pip check — ok
  • compileall — ok
  • Restore drill (scripts/sqlite-restore-drill.py) — não tocado, continua rodando init_db() do jeito que sempre rodou
Segunda parte da investigação de velocidade da suíte de backend (a primeira, instrumentação de fases + `--durations=30`, está na PR da branch `ci-speed-investigation`). ## Causa raiz confirmada `_load_app(tmp_path, monkeypatch)` era duplicado idêntico em 16 arquivos de teste e sempre apontava pra um SQLite vazio. O lifespan do FastAPI roda `database.init_db()` (== `alembic upgrade head`) a cada `TestClient(app)` — isso sozinho custava ~1.5s por teste (isolei essa medição diretamente, e bate com o `--durations=30` real da CI: praticamente todo teste do top 30 ficava entre 2.5s e 4.9s). Com ~110 dos 151 testes nesse caminho, essa replay de migration respondia por ~180 dos ~250s da suíte inteira. Controle: os 30 testes que nunca tocam `_load_app` (`test_architecture.py`, `test_hubspot_client.py`, `test_scenario_status.py`) rodam em 1.55s total. ## Mudança Centraliza `_load_app` em `backend/tests/conftest.py` (novo) e muda seu comportamento: agora aponta pra uma **cópia** de um banco já migrado até `head`, construído uma única vez por sessão de pytest, em vez de um banco vazio. `database.init_db()` continua rodando de verdade a cada `TestClient(app)` via o lifespan de `main.py` — nada mudou lá — só que, como a cópia já está em head, vira uma checagem idempotente de ~50ms em vez de reaplicar as 18 migrations do zero. **Isolamento entre testes continua total**: cada teste ainda recebe seu próprio arquivo físico (cópia fresca), nunca compartilha conexão, transação ou estado com outro teste — não é rollback/savepoint compartilhado, é um arquivo novo por teste, só que pré-populado em vez de vazio. `_load_app_from_scratch` preserva o comportamento antigo (banco vazio, sem atalho nenhum) e é usado exclusivamente por `test_alembic_initialization_is_idempotent_and_reaches_head` — o único teste que existe especificamente pra provar que o Alembic aplica as migrations do zero. Esse continua rodando as migrations de verdade, sem nenhum atalho. ## Resultado Suíte inteira (151 testes, nenhum removido) caiu de **~262s para ~67s** localmente — quase 4x mais rápida — sem tirar cobertura nenhuma. ## Verificação - `pytest -q` — 151 passed (mesma contagem de antes) - `pip check` — ok - `compileall` — ok - Restore drill (`scripts/sqlite-restore-drill.py`) — não tocado, continua rodando `init_db()` do jeito que sempre rodou
perf: banco de teste pre-migrado, evita replay de Alembic em cada teste
All checks were successful
EEG QA CI / validate-and-build (pull_request) Successful in 5m2s
EEG QA CI / Deploy production (pull_request) Has been skipped
EEG QA CI / Deploy development (pull_request) Has been skipped
9e2420ccce
_load_app(tmp_path, monkeypatch) era duplicado identico em 16 arquivos e
sempre apontava pra um SQLite vazio -- o lifespan do FastAPI roda
database.init_db() (== alembic upgrade head) a cada TestClient(app), e
isso sozinho custava ~1.5s por teste (medido isoladamente e confirmado
batendo com o --durations=30 real da CI: praticamente todo teste do top
30 ficava entre 2.5s e 4.9s). Com ~110 dos 151 testes nesse caminho, essa
replay respondia por ~180 dos ~250s da suite inteira.

Centraliza _load_app em conftest.py e muda seu comportamento: agora
aponta pra uma COPIA de um banco ja migrado ate head, construido uma
unica vez por sessao de pytest (_build_template_db), em vez de um banco
vazio. database.init_db() continua rodando de verdade a cada
TestClient(app) via o lifespan de main.py -- nada mudou la -- so que,
como a copia ja esta em head, vira uma checagem idempotente de ~50ms em
vez de reaplicar as 18 migrations do zero. Isolamento entre testes
continua total: cada teste ainda recebe seu proprio arquivo fisico (copia
fresca), nunca compartilha conexao ou estado com outro teste.

_load_app_from_scratch preserva o comportamento antigo (banco vazio, sem
atalho) e e usado exclusivamente por
test_alembic_initialization_is_idempotent_and_reaches_head, que existe
especificamente pra provar que o Alembic aplica as migrations do zero --
esse continua rodando as migrations de verdade, sem nenhum atalho.

Resultado local: suite inteira (151 testes) caiu de ~262s para ~67s, sem
tirar nenhum teste nem reduzir cobertura.
jhony75 merged commit 25257121ee into develop 2026-09-08 15:10:30 -03:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jhony75/eeg-qa!43
No description provided.