Cobertura de Testes com HPC e Exercícios Finais¶
Medindo a cobertura de testes com HPC¶
(Esta seção foi inteiramente reescrita para as ferramentas atuais, e o relatório abaixo foi gerado de verdade sobre a nossa biblioteca.)
Nossa suíte passa em todos os testes. Mas... ela testa o quê, exatamente? Essa pergunta tem uma resposta objetiva.
O HPC (Haskell Program Coverage) é um recurso do GHC que instrumenta o código para observar quais partes dele foram realmente executadas durante uma execução do programa. No contexto de testes, isso nos permite ver com precisão quais funções, ramos e expressões foram avaliados pela suíte — e, mais importante, quais não foram. O resultado é um conhecimento exato do percentual de código coberto, e o HPC ainda gera páginas HTML com o código-fonte colorido, facilitando localizar os pontos fracos da suíte.
Com o Stack, obter os dados de cobertura é um parâmetro a mais:
A suíte executa normalmente (todas as propriedades passando, como antes) e, ao final, o Stack imprime o relatório e os caminhos dos arquivos HTML gerados:
Generating coverage report for hs2json's test-suite "hs2json-test"
19% expressions used (30/154)
0% boolean coverage (0/3)
0% guards (0/3), 3 unevaluated
100% 'if' conditions (0/0)
100% qualifiers (0/0)
23% alternatives used (8/34)
0% local declarations used (0/4)
45% top-level declarations used (10/22)
The coverage report for hs2json's test-suite "hs2json-test" is available at
.../.stack-work/install/.../hpc/hs2json/hs2json-test/hpc_index.html
(Os números referem-se ao módulo Prettify; por padrão, o Stack reporta a cobertura do código do pacote exercido pelos testes. Abra o hpc_index.html indicado no navegador para a versão visual.)
Tip
Sem o Stack: o HPC é do próprio GHC, então o fluxo manual equivalente é compilar com o flag -fhpc, executar o programa (o que gera um arquivo .tix com as contagens) e então usar o utilitário hpc: hpc report para o resumo textual e hpc markup para as páginas HTML. O stack test --coverage faz exatamente isso por você.
Lendo o relatório¶
Aprender a ler essas linhas é o que dá valor à ferramenta:
| Métrica | O que mede | Nosso resultado |
|---|---|---|
expressions used |
Quantas expressões do código foram avaliadas ao menos uma vez. É a métrica mais fina. | 19% (30 de 154) |
boolean coverage / guards |
Dos pontos de decisão booleanos (guardas, if), quantos foram avaliados — e para os dois lados. |
0% (0 de 3) |
alternatives used |
Das alternativas de casamento de padrões (equações de função, ramos de case), quantas foram exercitadas. |
23% (8 de 34) |
local declarations |
Definições em where/let executadas. |
0% (0 de 4) |
top-level declarations |
Funções de topo do módulo executadas. | 45% (10 de 22) |
À primeira vista, os números parecem contraditórios: como uma suíte que "passa em tudo" cobre só 19% das expressões? A resposta está na visão por declaração. Abrindo o hpc_index_fun.html (ou o fonte anotado Prettify.hs.html, onde o código nunca executado aparece destacado em amarelo), o padrão salta aos olhos — as funções jamais tocadas pela suíte são:
Ou seja: testamos completamente a metade de construção da biblioteca (empty, char, text, double, line, <>, hcat, fold, punctuate), mas zero da metade de renderização — justamente as funções mais complexas, compact e pretty, com seus where internos (eis os local declarations 0%: transform, best, nicest...) e suas guardas (eis o boolean coverage 0%: as guardas de fits e nicest). A suíte verde estava nos contando só metade da história — e o HPC expôs isso em uma linha. (Curiosidade: as 22 declarações de topo contadas incluem os métodos gerados pelo deriving — show, showsPrec... —, que o HPC também rastreia.)
Fechando o ciclo: da lacuna à propriedade¶
O relatório não é um fim; é o começo da próxima iteração. Vamos escrever uma propriedade que exercite a renderização — um teste baseado em modelo minúsculo para a compact: renderizar compactamente um documento de texto puro deve devolver a própria string:
Rodando de novo com cobertura:
$ stack test --coverage
=== prop_compact_text from test/Spec.hs:45 ===
+++ OK, passed 100 tests.
Passou em todos os testes.
27% expressions used (42/154)
...
35% alternatives used (12/34)
25% local declarations used (1/4)
50% top-level declarations used (11/22)
Uma propriedade de uma linha: expressões cobertas de 19% para 27%, alternativas de 23% para 35%, e a transform interna da compact saiu do zero. É esse o ritmo do desenvolvimento guiado por propriedades e cobertura: a suíte diz "o que testei está correto"; o HPC diz "eis o que você ainda não testou"; e cada lacuna vira a próxima invariante.
Warning
Cobertura alta não prova corretude — mede apenas o que foi executado, não o que foi verificado. Uma propriedade frouxa pode executar tudo e não conferir nada. Use os dois sinais juntos: propriedades fortes para a corretude, cobertura para achar os pontos cegos.
Exercícios¶
1. Escreva propriedades para a metade ainda descoberta da biblioteca e acompanhe a cobertura subindo. Sugestões, em dificuldade crescente:
prettyde um documento semsoftlinenão depende da largura:pretty w (text s)deve ser igual aspara qualquerw;- toda linha de
pretty w d"cabe" — relacione com a intuição da funçãofits(cuidado: quando um pedaço não cabe de jeito nenhum, a linha pode estourarw; a propriedade precisa levar isso em conta); - um teste baseado em modelo relacionando
compactepretty: os dois devem produzir o mesmo texto, a menos de espaços em branco e quebras de linha (comece definindo essa noção de equivalência!).
2. Implemente o método shrink na instância Arbitrary Doc (investigue genericShrink, que exige deriving (Generic) no tipo) e provoque uma falha de propósito para ver o QuickCheck reduzir o contraexemplo ao mínimo.
3. Nosso gerador de Doc escolhe entre os seis construtores com igual probabilidade, e os casos recursivos podem, ocasionalmente, gerar árvores enormes. Investigue as funções sized e frequency do QuickCheck e reescreva o gerador limitando a profundidade da árvore pelo "tamanho" do teste.
Baseado nos Capítulos 5 e 11 de Real World Haskell, copyright 2007, 2008 Bryan O'Sullivan, Don Stewart e John Goerzen, sob licença Creative Commons Attribution-Noncommercial 3.0. Tradução do projeto rwh-ptbr; revisão, atualização para GHC 9.x/Stack/QuickCheck 2.14 e validação de todo o código nesta edição v2.