Por Que Monitorar Qualidade?
- Visibilidade – Entender estado atual
- Tendências – Identificar melhoras/pioras
- Decisões – Base para ação
- Accountability – Responsabilidade clara
Métricas de QA
Cobertura de Testes
# Métricas de cobertura
metrics = {
"line_coverage": calculate_line_coverage(),
"branch_coverage": calculate_branch_coverage(),
"function_coverage": calculate_function_coverage(),
"covered_lines": 450,
"total_lines": 500,
"uncovered_lines": [12, 45, 78], # Linhas críticas
"coverage_trend": {
"week_1": 65,
"week_2": 68,
"week_3": 72,
"week_4": 78
}
}
# Dashboard metrics
def calculate_coverage_metrics():
return {
"current": get_current_coverage(),
"target": 80,
"delta": get_current_coverage() - 80,
"trend": get_coverage_trend(weeks=4)
}
Defect Metrics
class DefectMetrics:
def __init__(self, sprint_data):
self.sprint = sprint_data
def defect_leakage_rate(self):
"""{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} de defeitos encontrados em produção"""
prod = len([d for d in self.sprint.defects if d.found_in == 'production'])
total = len(self.sprint.defects)
return (prod / total) * 100 if total > 0 else 0
def defect_density(self):
"""Defeitos por story point"""
return len(self.sprint.defects) / self.sprint.story_points_completed
def mean_time_to_detect(self):
"""Tempo médio para detectar defeito"""
return sum(d.time_to_detect for d in self.sprint.defects) / len(self.sprint.defects)
def mean_time_to_fix(self):
"""Tempo médio para corrigir"""
return sum(d.time_to_fix for d in self.sprint.defects) / len(self.sprint.defects)
def escape_rate(self):
"""{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} de defeitos escapados para produção"""
escaped = len([d for d in self.sprint.defects if d.escaped_to_prod])
total = len(self.sprint.defects)
return (escaped / total) * 100 if total > 0 else 0
def defect_by_severity(self):
return {
"critical": len([d for d in self.sprint.defects if d.severity == 'critical']),
"high": len([d for d in self.sprint.defects if d.severity == 'high']),
"medium": len([d for d in self.sprint.defects if d.severity == 'medium']),
"low": len([d for d in self.sprint.defects if d.severity == 'low'])
}
def defect_by_type(self):
return {
"functional": len([d for d in self.sprint.defects if d.type == 'functional']),
"performance": len([d for d in self.sprint.defects if d.type == 'performance']),
"security": len([d for d in self.sprint.defects if d.type == 'security']),
"ux": len([d for d in self.sprint.defects if d.type == 'ux'])
}
Test Execution Metrics
class TestExecutionMetrics:
def __init__(self, execution_history):
self.history = execution_history
def pass_rate(self):
"""{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} de testes passando"""
passed = len([t for t in self.history if t.status == 'passed'])
total = len(self.history)
return (passed / total) * 100 if total > 0 else 0
def flaky_rate(self):
"""{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} de testes flaky"""
flaky = len([t for t in self.history if t.is_flaky])
total = len(self.history)
return (flaky / total) * 100 if total > 0 else 0
def execution_time(self):
"""Tempo de execução total"""
return sum(t.duration for t in self.history)
def execution_time_trend(self):
"""Tendência de tempo de execução"""
return self.history.group_by('date').avg('duration')
def coverage_trend(self):
"""Tendência de cobertura"""
return self.history.group_by('date').avg('coverage')
def blocked_tests(self):
"""Testes bloqueados"""
return [t for t in self.history if t.status == 'blocked']
def skipped_tests(self):
"""Testes pulados"""
return [t for t in self.history if t.status == 'skipped']
DORA Metrics
Deployment Frequency
def deployment_frequency():
"""
Target: On-demand ou múltiplas vezes por dia
"""
deployments = get_deployments(last_30_days=True)
return {
"count": len(deployments),
"frequency": len(deployments) / 30, # por dia
"target": ">1/day",
"status": "elite" if len(deployments) >= 30 else "high" if len(deployments) >= 5 else "medium"
}
Lead Time for Changes
def lead_time_for_changes():
"""
Target: <1 hora (elite)
"""
changes = get_code_changes()
lead_times = []
for change in changes:
commit_time = change.commit_timestamp
deploy_time = change.deployed_timestamp
lead_time = (deploy_time - commit_time).total_seconds() / 3600 # horas
lead_times.append(lead_time)
return {
"median_hours": statistics.median(lead_times),
"p95_hours": statistics.quantiles(lead_times, n=20)[18], # P95
"target": "<1 hour",
"status": "elite" if statistics.median(lead_times) < 1 else "high"
}
Time to Restore
def time_to_restore():
"""
Target: <1 hora (elite)
"""
incidents = get_incidents(last_30_days=True)
restore_times = []
for incident in incidents:
restore_hours = (incident.resolved_at - incident.detected_at).total_seconds() / 3600
restore_times.append(restore_hours)
return {
"median_hours": statistics.median(restore_times),
"p99_hours": max(restore_times), # P99 worst case
"target": "<1 hour",
"incidents_count": len(incidents)
}
Change Failure Rate
def change_failure_rate():
"""
Target: <15{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} (elite)
"""
deployments = get_deployments(last_30_days=True)
failed = len([d for d in deployments if d.failed or d.rolled_back])
return {
"failed_count": failed,
"total_count": len(deployments),
"rate_percent": (failed / len(deployments)) * 100 if deployments else 0,
"target": "<15{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}",
"status": "elite" if failed / len(deployments) < 0.15 else "high"
}
Dashboard Design
Grafana Dashboard
# dashboard.json
{
"dashboard": {
"title": "QA Metrics Dashboard",
"panels": [
{
"title": "Test Pass Rate",
"type": "stat",
"targets": [
{
"expr": "sum(test_results{status='passed'}) / sum(test_results) * 100"
}
],
"fieldConfig": {
"defaults": {
"unit": "percent",
"thresholds": {
"mode": "absolute",
"steps": [
{"value": 0, "color": "red"},
{"value": 90, "color": "yellow"},
{"value": 95, "color": "green"}
]
}
}
}
},
{
"title": "Test Execution Time",
"type": "timeseries",
"targets": [
{
"expr": "rate(test_execution_seconds_sum[5m]) / rate(test_execution_seconds_count[5m])"
}
],
"fieldConfig": {
"defaults": {
"unit": "s",
"custom": {
"lineWidth": 2,
"fillOpacity": 10
}
}
}
},
{
"title": "Coverage Trend",
"type": "timeseries",
"targets": [
{
"expr": "test_coverage_percent",
"legendFormat": "Coverage {6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}"
}
]
},
{
"title": "DORA Metrics",
"type": "table",
"targets": [
{
"expr": "deployment_frequency"
},
{
"expr": "lead_time_hours"
},
{
"expr": "time_to_restore_hours"
},
{
"expr": "change_failure_rate_percent"
}
]
},
{
"title": "Defect Trends",
"type": "timeseries",
"targets": [
{
"expr": "rate(defects_created_total[1h])",
"legendFormat": "Created"
},
{
"expr": "rate(defects_resolved_total[1h])",
"legendFormat": "Resolved"
}
]
},
{
"title": "Flaky Tests",
"type": "table",
"targets": [
{
"expr": "flaky_tests",
"format": "table"
}
]
}
],
"refresh": "5m",
"time": {
"from": "now-7d",
"to": "now"
}
}
}
Relatórios
Sprint Quality Report
# Sprint Quality Report - Sprint 42
Resumo Executivo
- Sprint durou: 14 dias
- Velocity: 42 story points
- Defect density: 0.4/story point
Métricas de Teste
Métrica Sprint Atual Sprint Anterior Target
Test Pass Rate 97{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} 95{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} >95{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}
Coverage 78{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} 75{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} >80{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}
Execution Time 25 min 28 min <30 min
Flaky Tests 2{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} 3{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} <2{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}
DORA Metrics
Métrica Valor Status
Deployment Frequency 12/day Elite
Lead Time 2h High
MTTR 45min Elite
Change Failure Rate 8{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} Elite
Defeitos
- Total criados: 17
- Total resolvidos: 15
- Em aberto: 2 (1 medium, 1 low)
- Em produção: 1
Riscos
⚠️ Cobertura abaixo do target
⚠️ 1 defeito crítico em produção
Ações
- [ ] Aumentar cobertura para 80{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}
- [ ] Investigar causa do defeito em produção
- [ ] Revisar testes flaky
Conclusão
Monitoramento é essencial para melhoria contínua. As chaves são:
- Definir métricas certas – Alinhadas com objetivos
- Coletar automaticamente – CI/CD integration
- Visualizar claramente – Dashboards efetivos
- Analisar tendências – Não apenas snapshots
- Agir sobre dados – Métricas sem ação não valem nada
