1. Definir a evidência exigida para a mudança
Num projeto fictício de migração, a regra de release exige testes unitários, integração com o ledger, validação do contrato da API e um ensaio do pico diário. Cada conjunto responde a uma pergunta diferente. Um teste unitário pode verificar uma regra de cálculo sem provar que duas aplicações comunicam na configuração pretendida. Um teste com uma resposta simulada pode verificar o comportamento do consumidor, mas não demonstra sozinho que a versão real do fornecedor respeita esse contrato. Escreve os limites de cada ensaio, os inputs, a versão do artefacto e o resultado esperado. Depois associa a evidência à decisão concreta: que risco fica suficientemente avaliado e qual permanece? A lista de suites esperadas deve ser verificável. Uma contagem elevada de testes de um único tipo não substitui um conjunto obrigatório que ficou por executar ou por demonstrar.
2. Separar executar, publicar e bloquear
O runner executa testes e produz resultados; o publisher lê ficheiros e torna-os consultáveis; a política decide o que bloqueia. Uma task de publicação pode ter sucesso com falhas no relatório se o controlo correspondente estiver desativado. Também pode tolerar ficheiros ausentes. No caso da aula, uma mudança de diretório deixa os resultados de integração fora da pesquisa, enquanto os unitários continuam verdes. Reconciliar suites esperadas e resultados encontrados evita confundir ausência de ficheiros com ausência de falhas. Confirma ainda formato e origem: a opção VSTest do publisher representa TRX e não obriga a usar o runner VSTest. O mínimo de testes executados deste runner inclui passed, failed e aborted; não é uma contagem de sucessos. Em publicações com muitos ficheiros, confirma testes e configurações dentro das runs agregadas, em vez de usar o número de runs como prova de completude.
3. Produzir cobertura que corresponde ao candidato
Cobertura precisa de recolha durante uma execução relevante, de um relatório legível e de ligação à fonte correta. PublishCodeCoverageResults@2 publica dados previamente gerados; ativar failIfCoverageEmpty deteta ausência, mas não executa os testes em falta. Se os testes correm num contentor e o publisher no host, os caminhos do relatório podem não resolver nesse host. Confirma a localização do resumo e usa pathToSources quando for necessário mapear caminhos não absolutos para a fonte disponível. Não preenchas uma lacuna com o XML de outro commit apenas porque os módulos têm os mesmos nomes. Preserva identidade da versão, parâmetros e exclusões. A publicação de um ficheiro válido também não é um limiar de qualidade. O requisito pode exigir cobertura das alterações, investigação de caminhos críticos ou evidência funcional que a percentagem, por si só, não contém.
4. Interpretar o numerador, o denominador e os caminhos
Dois relatórios do mesmo ficheiro cobrem linhas 1 a 6 e 5 a 8. A união contém oito linhas; somar seis e quatro duplica a sobreposição. Em módulos distintos com 90/100 e 1/10 linhas cobertas, o total é 91/110, cerca de 82,7%, não a média simples de 90% e 10%. O modelo local permite confirmar ambos os cálculos. Há outra distinção: visitar todas as linhas pode deixar um resultado de uma decisão por testar. Na função route do exercício, approved=True visita as linhas, mas não valida o resultado review de approved=False. Excluir esse caminho da medição pode melhorar a percentagem sem acrescentar evidência. Finalmente, cobertura global e diff coverage usam populações diferentes. Num setup que já produz um status de cobertura funcional, uma indicação advisory precisa de uma política adequada se o requisito for bloquear merges abaixo do alvo.
5. Tratar instabilidade sem apagar a primeira falha
Um teste falha e passa num rerun sem alteração de código. Conserva ambas as tentativas: a diferença pode indicar dependência de estado, ordem, temporização ou ambiente. Em Azure DevOps Services, a deteção suportada pode marcá-lo como flaky. Essa marcação ajuda a investigar; não prova que a causa desapareceu. Se a equipa suprime testes flaky do resumo, a percentagem passa a excluir tanto os que passaram como os que falharam nessa população. Apresenta a alteração com os testes não reportados e um responsável pela correção. Não compares a nova percentagem com a anterior como se o âmbito fosse idêntico. Marcar ou desmarcar afeta a avaliação em execuções futuras, não recalcula a execução atual. Uma exceção temporária deve manter visíveis o comportamento ainda não fiável, a evidência alternativa exigida e a data de revisão acordada pela equipa.
6. Otimizar execução sem perder isolamento e âmbito
Dois testes que usam a mesma conta de cliente podem passar isoladamente e falhar em paralelo quando um teardown apaga dados do outro. Neste cenário, separa os dados e limita a limpeza aos recursos criados por cada teste. Aumentar reruns ou introduzir uma espera fixa não garante isolamento. Durante o diagnóstico, verifica a configuração aplicada: runInParallel na task VSTest pode sobrepor MaxCpuCount no runsettings. A execução serial pode ajudar a investigar, mas não substitui uma correção do estado partilhado quando o objetivo é paralelizar com fiabilidade. Seleção por impacto também precisa de limites. Num ambiente TIA suportado, mudanças que a análise não compreende podem levar à execução de todos os testes. Remover esse fallback apenas para reduzir tempo troca uma estratégia de cobertura por uma suposição de ausência de impacto. Documenta onde a otimização é válida.
7. Ensaiar a carga e avaliar condições de falha
Um teste de carga concluído pode não ter critérios configurados. Em Azure Load Testing, Completed com No test criteria distingue-se de Passed com avaliação dos critérios. Define a condição de falha na direção correta: para um mínimo fictício de 500 pedidos/s, falhar abaixo de 500 não é o mesmo que falhar acima de 500. Se o requisito pertence a SubmitPayment, aplica o critério à request correspondente; milhares de leituras rápidas podem diluir a operação no agregado. Auto stop para erros elevados protege a execução, mas não substitui critérios de desempenho. Verifica ainda os engines que geram carga: CPU saturada no gerador pode limitar throughput antes da aplicação. Para comparar versões, conserva carga, dados, capacidade e âmbito comparáveis. Uma melhoria observada com menos utilizadores, mais cache e mais instâncias não permite atribuir a diferença apenas ao código.
8. Levar evidência suficiente ao comité e a RUN
Executa o modelo Python com dados fictícios. A política didática é deliberadamente estrita: exige os testes identificados, o commit certo e aprovação na primeira tentativa. Não é uma regra universal nem uma reprodução do motor Azure. Altera um ID, retira uma linha e introduz uma tentativa falhada antes da aprovada para observar que a cor agregada esconderia diferenças importantes. O modelo também compara conjuntos de linhas e contagens de execução. No handover real, entrega suites esperadas, referências, configuração, resultados, tentativas, limitações e critérios avaliados. Para métricas de servidor em load tests, respeita a interface de configuração suportada e o acesso da identidade às métricas, sem assumir que qualquer chave YAML é efetiva. Se faltar evidência obrigatória, recupera-a ou explicita a decisão pendente. O resumo é separar presença, completude e significado da evidência antes de aprovar a release.
# Original fictional acceptance model, not an Azure task emulator or a JUnit parser.
# All data is synthetic. The strict policy deliberately accepts only complete first-pass evidence.
from fractions import Fraction
expected = {'unit:pricing', 'integration:ledger', 'contract:payments'}
def acceptable(rows, commit, required):
keys = [row.get('id') for row in rows]
if not required or set(keys) != required or len(keys) != len(set(keys)):
return False
return all(row.get('commit') == commit and row.get('outcomes') == ['passed']
for row in rows)
rows = [dict(id=name, commit='commit-A', outcomes=['passed']) for name in sorted(expected)]
assert acceptable(rows, 'commit-A', expected)
assert not acceptable([], 'commit-A', expected)
assert not acceptable(rows[:-1], 'commit-A', expected)
assert not acceptable(rows + [rows[0]], 'commit-A', expected)
assert not acceptable(rows, 'commit-B', expected)
assert not acceptable([{**r, 'outcomes': ['skipped']} for r in rows], 'commit-A', expected)
assert not acceptable([{**r, 'outcomes': ['failed']} for r in rows], 'commit-A', expected)
assert not acceptable([{**r, 'outcomes': ['failed', 'passed']} for r in rows], 'commit-A', expected)
assert not acceptable([{**r, 'outcomes': []} for r in rows], 'commit-A', expected)
assert not acceptable(rows + [dict(id='unexpected', commit='commit-A', outcomes=['passed'])], 'commit-A', expected)
# Two report partitions cover the same file revision. Merge by line identity, not by summing hits.
source_lines = {('commit-A', 'payments.py', n) for n in range(1, 11)}
report_a = {('commit-A', 'payments.py', n) for n in range(1, 7)}
report_b = {('commit-A', 'payments.py', n) for n in range(5, 9)}
covered = report_a | report_b
assert len(report_a) + len(report_b) == 10
assert len(covered) == 8
assert Fraction(len(covered), len(source_lines)) == Fraction(4, 5)
assert covered <= source_lines
# Disjoint modules: 90/100 and 1/10. A simple average of percentages distorts the population.
weighted = Fraction(90 + 1, 100 + 10)
unweighted = (Fraction(90, 100) + Fraction(1, 10)) / 2
assert weighted == Fraction(91, 110)
assert unweighted == Fraction(1, 2)
assert weighted != unweighted
# Minimum execution counts are not a claim that all executed tests passed.
passed, failed, aborted, skipped = 7, 2, 1, 5
executed = passed + failed + aborted
assert executed == 10
assert executed >= 10 and failed > 0
assert executed != passed + failed + aborted + skipped
print('20 fictional test-evidence checks passed')
Um relatório com 300 unitários aprovados não inclui a integração exigida porque o publisher procura no caminho antigo. O caso obriga a recuperar evidência do candidato e corrigir a deteção da lacuna.
Armadilhas comuns
Confundir publicação com execução; substituir evidência atual por relatórios antigos; somar linhas repetidas; esconder flakiness; comparar carga e capacidade diferentes como efeito do código.
Tópicos relacionados: Critérios de aceitação e governance de release · Rastreabilidade de artefactos · Capacidade e observabilidade · Transição para suporte operacional
Uma decisão defensável liga critérios, candidato, âmbito e resultados completos. Mede o que realmente foi exercitado e torna visíveis as condições que ainda não foram demonstradas.
Referência: PublishTestResults@2 reference · AZ-400 objectives 2026-07-27