# Monitoramento de Qualidade: Métricas e Dashboards

**Meta Description:** Aprenda a monitorar qualidade de software: métricas de QA, dashboards de teste, KPIs deDevOps e como usar dados para melhoria contínua.

---

## 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

```python
# 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

```python
class DefectMetrics:
    def __init__(self, sprint_data):
        self.sprint = sprint_data
    
    def defect_leakage_rate(self):
        """% 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):
        """% 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

```python
class TestExecutionMetrics:
    def __init__(self, execution_history):
        self.history = execution_history
    
    def pass_rate(self):
        """% 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):
        """% 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

```python
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

```python
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

```python
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

```python
def change_failure_rate():
    """
    Target: <15% (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%",
        "status": "elite" if failed / len(deployments) < 0.15 else "high"
    }
```

---

## Dashboard Design

### Grafana Dashboard

```yaml
# 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 %"
          }
        ]
      },
      {
        "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

```markdown
# 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% | 95% | >95% |
| Coverage | 78% | 75% | >80% |
| Execution Time | 25 min | 28 min | <30 min |
| Flaky Tests | 2% | 3% | <2% |

## DORA Metrics
| Métrica | Valor | Status |
|---------|-------|--------|
| Deployment Frequency | 12/day | Elite |
| Lead Time | 2h | High |
| MTTR | 45min | Elite |
| Change Failure Rate | 8% | 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%
- [ ] Investigar causa do defeito em produção
- [ ] Revisar testes flaky
```

---

## Conclusão

Monitoramento é essencial para melhoria contínua. As chaves são:

1. **Definir métricas certas** - Alinhadas com objetivos
2. **Coletar automaticamente** - CI/CD integration
3. **Visualizar claramente** - Dashboards efetivos
4. **Analisar tendências** - Não apenas snapshots
5. **Agir sobre dados** - Métricas sem ação não valem nada
