Skip to content

Commit 58fff73

Browse files
authored
Merge pull request #38 from ThomasTDS/chore/marcadores-tc-automaticos
Registra os marcadores TC-XXX a partir dos .feature e confere a matriz
2 parents de4f8f1 + 4c98c28 commit 58fff73

5 files changed

Lines changed: 68 additions & 28 deletions

File tree

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ qa-api-python/
3737
│ ├── common_steps.py # Steps usados por mais de uma feature (tokens, booking criado, 403, 405)
3838
│ └── booking_data.py # Booking padrão e payloads com campos inválidos
3939
├── tests/
40-
│ └── unit/ # Testes unitários isolados de api/ e models/ (com requests-mock, sem rede)
40+
│ └── unit/ # Testes unitários isolados de api/ e models/ (com requests-mock, sem rede) e checagem da matriz de test cases
4141
├── api/ # API Clients (AuthApiClient e BookingApiClient, sobre a base comum BaseApiClient)
4242
├── models/ # Schemas Pydantic e tipos derivados (shape dos dados da API)
4343
├── reports/ # Relatório HTML e cobertura (coverage.xml) gerados a cada execução (não versionado)
@@ -162,7 +162,7 @@ Autenticação: `POST /auth` com `{ "username": "admin", "password": "password12
162162
- Integração contínua via GitHub Actions, em dois jobs: `unit` roda as checagens estáticas e os testes unitários, sem rede, e `integration` só começa se o `unit` passar, rodando a suíte completa contra a API pública. Assim, uma falha no `integration` com o `unit` verde aponta para a API ou para um cenário, e não para o código dos clients. Os dois rodam a cada push e pull request para `master`, em Python 3.12 e 3.13, e também diariamente às 06:00 UTC (ver [.github/workflows/tests.yml](.github/workflows/tests.yml)) para detectar quebras causadas pela própria API pública, com o relatório HTML publicado como artifact do workflow.
163163
- Dependências atualizadas automaticamente pelo Dependabot (pip e GitHub Actions, semanal — ver [.github/dependabot.yml](.github/dependabot.yml)). PRs de patch/minor com CI verde são mergeados automaticamente ([.github/workflows/dependabot-auto-merge.yml](.github/workflows/dependabot-auto-merge.yml)); bumps de major exigem revisão manual.
164164
- Auditoria de vulnerabilidades conhecidas nas dependências instaladas a cada execução do CI, via [`pip-audit`](https://github.com/pypa/pip-audit); encontrar uma vulnerabilidade quebra o build.
165-
- Rastreabilidade de QA: matriz de test cases em [docs/test-cases.md](docs/test-cases.md), com tags `@TC-XXX` em cada `Cenário` e um subconjunto `@smoke` (`pytest -m smoke`) cobrindo os fluxos ponta-a-ponta mais críticos. Bugs reais encontrados são documentados como GitHub Issues usando o template em [.github/ISSUE_TEMPLATE/bug_report.md](.github/ISSUE_TEMPLATE/bug_report.md).
165+
- Rastreabilidade de QA: matriz de test cases em [docs/test-cases.md](docs/test-cases.md), com tags `@TC-XXX` em cada `Cenário`. Os marcadores `TC-XXX` são registrados automaticamente a partir das tags dos `.feature`, o pytest roda com `--strict-markers` (uma tag com erro de digitação quebra a coleta), e o teste [tests/unit/test_traceability.py](tests/unit/test_traceability.py) garante que a matriz lista exatamente os TCs dos `.feature`, com links para a linha certa de cada cenário e um subconjunto `@smoke` (`pytest -m smoke`) cobrindo os fluxos ponta-a-ponta mais críticos. Bugs reais encontrados são documentados como GitHub Issues usando o template em [.github/ISSUE_TEMPLATE/bug_report.md](.github/ISSUE_TEMPLATE/bug_report.md).
166166

167167
---
168168

‎conftest.py‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import json
22
import os
3+
import re
34
import warnings
45
from collections.abc import Generator
6+
from pathlib import Path
57
from typing import Any
68

79
import pytest
@@ -22,6 +24,28 @@
2224
API_PASSWORD = os.environ.get("API_PASSWORD", "password123")
2325

2426

27+
FEATURES_DIR = Path(__file__).parent / "features"
28+
29+
30+
# Cada cenário tem uma tag @TC-XXX na linha logo acima de "Cenário:". Devolve,
31+
# para cada TC, o arquivo .feature e o número da linha do cenário. É a fonte
32+
# usada para registrar os marcadores e para conferir a matriz em docs/.
33+
def tc_scenarios() -> dict[str, tuple[str, int]]:
34+
found: dict[str, tuple[str, int]] = {}
35+
for path in sorted(FEATURES_DIR.glob("*.feature")):
36+
lines = path.read_text(encoding="utf-8").splitlines()
37+
for index, line in enumerate(lines):
38+
for tc in re.findall(r"@(TC-\d{3})\b", line):
39+
assert tc not in found, f"{tc} aparece em mais de um cenário"
40+
found[tc] = (path.name, index + 2)
41+
return found
42+
43+
44+
def pytest_configure(config: pytest.Config) -> None:
45+
for tc in tc_scenarios():
46+
config.addinivalue_line("markers", f"{tc}: id do test case, ver docs/test-cases.md")
47+
48+
2549
class Context:
2650
def __init__(self, session: requests.Session) -> None:
2751
self.session = session

‎docs/test-cases.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ Rastreabilidade dos casos de teste do projeto. Esta tabela **não duplica** os p
44

55
Um `Esquema do Cenário` com `Exemplos` conta como um único test case (a variação de dados não infla a contagem).
66

7+
A tabela é conferida automaticamente por [tests/unit/test_traceability.py](../tests/unit/test_traceability.py): todo TC dos `.feature` precisa estar aqui, e vice-versa, e cada link precisa apontar para a linha do `Cenário` correspondente.
8+
79
Legenda:
810

911
- **Tipo**: `Funcional` (caminho feliz) ou `Negativo` (validação de erro/bloqueio esperado)

‎pyproject.toml‎

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -32,38 +32,16 @@ include = ["api*", "models*"]
3232

3333
[tool.pytest.ini_options]
3434
testpaths = ["steps", "tests"]
35-
addopts = "--reruns 1 --html=reports/report.html --self-contained-html --cov=api --cov=models --cov-report=term-missing --cov-report=xml:reports/coverage.xml"
35+
addopts = "--strict-markers --reruns 1 --html=reports/report.html --self-contained-html --cov=api --cov=models --cov-report=term-missing --cov-report=xml:reports/coverage.xml"
3636
# O retry só vale para falhas de rede (conexão recusada ou timeout), que são
3737
# instabilidade da API pública. Falha de asserção ou de schema nunca é
3838
# repetida, para que um bug intermitente não passe na segunda tentativa.
3939
only_rerun = ["^(ConnectionError|ConnectTimeout|ReadTimeout):"]
40+
# Os marcadores TC-XXX são registrados no conftest.py, a partir das tags dos
41+
# arquivos .feature. Com --strict-markers, uma tag com erro de digitação
42+
# quebra a coleta em vez de virar só um aviso.
4043
markers = [
4144
"smoke: fluxos ponta-a-ponta mais críticos (pytest -m smoke)",
42-
"TC-001: id do test case, ver docs/test-cases.md",
43-
"TC-002: id do test case, ver docs/test-cases.md",
44-
"TC-003: id do test case, ver docs/test-cases.md",
45-
"TC-004: id do test case, ver docs/test-cases.md",
46-
"TC-005: id do test case, ver docs/test-cases.md",
47-
"TC-006: id do test case, ver docs/test-cases.md",
48-
"TC-007: id do test case, ver docs/test-cases.md",
49-
"TC-008: id do test case, ver docs/test-cases.md",
50-
"TC-009: id do test case, ver docs/test-cases.md",
51-
"TC-010: id do test case, ver docs/test-cases.md",
52-
"TC-011: id do test case, ver docs/test-cases.md",
53-
"TC-012: id do test case, ver docs/test-cases.md",
54-
"TC-013: id do test case, ver docs/test-cases.md",
55-
"TC-014: id do test case, ver docs/test-cases.md",
56-
"TC-015: id do test case, ver docs/test-cases.md",
57-
"TC-016: id do test case, ver docs/test-cases.md",
58-
"TC-017: id do test case, ver docs/test-cases.md",
59-
"TC-018: id do test case, ver docs/test-cases.md",
60-
"TC-019: id do test case, ver docs/test-cases.md",
61-
"TC-020: id do test case, ver docs/test-cases.md",
62-
"TC-021: id do test case, ver docs/test-cases.md",
63-
"TC-022: id do test case, ver docs/test-cases.md",
64-
"TC-023: id do test case, ver docs/test-cases.md",
65-
"TC-024: id do test case, ver docs/test-cases.md",
66-
"TC-025: id do test case, ver docs/test-cases.md",
6745
]
6846

6947
[tool.ruff]

‎tests/unit/test_traceability.py‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import re
2+
from pathlib import Path
3+
4+
from conftest import FEATURES_DIR, tc_scenarios
5+
6+
MATRIX = Path(__file__).parents[2] / "docs" / "test-cases.md"
7+
ROW = re.compile(r"^\| (TC-\d{3}) .*\[([\w.]+):(\d+)\]\(\.\./features/([\w.]+)#L(\d+)\)")
8+
9+
10+
def _matrix_rows() -> dict[str, tuple[str, int, str, int]]:
11+
rows = {}
12+
for line in MATRIX.read_text(encoding="utf-8").splitlines():
13+
match = ROW.match(line)
14+
if match:
15+
tc, text_file, text_line, link_file, link_line = match.groups()
16+
rows[tc] = (text_file, int(text_line), link_file, int(link_line))
17+
return rows
18+
19+
20+
def test_every_tc_in_the_features_is_in_the_matrix_and_vice_versa() -> None:
21+
assert set(_matrix_rows()) == set(tc_scenarios())
22+
23+
24+
def test_every_matrix_link_points_to_the_line_of_its_scenario() -> None:
25+
scenarios = tc_scenarios()
26+
27+
for tc, (text_file, text_line, link_file, link_line) in _matrix_rows().items():
28+
expected = scenarios[tc]
29+
assert (text_file, text_line) == expected, tc
30+
assert (link_file, link_line) == expected, tc
31+
32+
33+
def test_tc_tags_sit_right_above_a_scenario() -> None:
34+
for tc, (file_name, line) in tc_scenarios().items():
35+
scenario = (FEATURES_DIR / file_name).read_text(encoding="utf-8").splitlines()[line - 1]
36+
assert re.match(r"\s*(Cenário|Esquema do Cenário):", scenario), tc

0 commit comments

Comments
 (0)