Monitoramento de Qualidade: Métricas e Dashboards

Introdução a Qualidade de Software · 6 de julho de 2026

📖 7 min de leitura

Por Que Monitorar Qualidade?

  • Visibilidade – Entender estado atual
    1. Tendências – Identificar melhoras/pioras
    2. Decisões – Base para ação
    3. 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

    1. Sprint durou: 14 dias
    2. Velocity: 42 story points
    3. Defect density: 0.4/story point

    Métricas de Teste

    MétricaSprint AtualSprint AnteriorTarget
    Test Pass Rate97{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}95{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}>95{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}
    Coverage78{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}75{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}>80{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}
    Execution Time25 min28 min<30 min
    Flaky Tests2{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}3{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}<2{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}

    DORA Metrics

    MétricaValorStatus
    Deployment Frequency12/dayElite
    Lead Time2hHigh
    MTTR45minElite
    Change Failure Rate8{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}Elite

    Defeitos

    1. Total criados: 17
    2. Total resolvidos: 15
    3. Em aberto: 2 (1 medium, 1 low)
    4. Em produção: 1

    Riscos

    ⚠️ Cobertura abaixo do target ⚠️ 1 defeito crítico em produção

    Ações

    1. [ ] Aumentar cobertura para 80{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}
    2. [ ] Investigar causa do defeito em produção
    3. [ ] 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