Cobertura de testes no Python com Pytest-cov

Domine a Cobertura de Testes com uma popular ferramenta para o Python.

Construindo modelos arquitetônicos com Blender 3D

Garantir a qualidade do software é fundamental para qualquer aplicação, independente da tecnologia ou de sua funcionalidade. Um software sem qualidade começa apresentar defeitos muito antes do que o imaginado, a manutenção se torna cara e cansativa para o time de desenvolvimento.

Podemos garantir a qualidade do nosso software de diferentes formas, como aplicar padrões de design ou implementar testes automatizados, por exemplo. Com os testes o time fica confiável de fazer alterações em qualquer parte da aplicação sem a preocupação de impactar outras partes do sistema.

Testar uma aplicação manualmente e feita por humanos pode demorar semanas e ainda sim erros podem passar, isso sem falar que enquanto uma parte está sendo testada, novas features estão sendo lançadas. Implementando testes automatizados, não é necessário um humano para tal tarefa e o feedback leva questões de segundos.

A gente já falou por aqui sobre testes automatizados om Pytest, portanto vamos criar agora uma cobertura de código, ou seja, verificar o quanto nossa aplicação tem testes que garantem a qualidade.

O que é e para que serve Cobertura de código

A cobertura de código é uma métrica fundamental no desenvolvimento de software, pois quantifica a extensão em que seu código-fonte é testado por meio de testes automatizados. Ao medir a cobertura de código, você obtém uma visão clara de quais partes do seu código foram afetadas por testes e quais permanecem não testadas. Isso é crucial para garantir a qualidade do software, uma vez que ajuda a identificar áreas suscetíveis a erros não detectados. Além disso, a cobertura de código promove a confiabilidade do código, acelerando o processo de desenvolvimento, facilitando a manutenção e promovendo uma cultura de testes sólida. Em resumo, a cobertura de código é uma prática essencial para desenvolvedores que desejam criar software robusto, confiável e de alta qualidade.

Ainda é possível aplicar regras na sua ferramenta de versionamento, como o Github, por exemplo, para que um commit só seja aprovado se o código em questão tiver um bom percentual de cobertura.

Pytest-cov em Python, o que é?

O Pytest-cov é uma extensão (plugin) do framework de teste Pytest que é usado para medir a cobertura de código dos testes automatizados em Python. Ele ajuda os desenvolvedores a determinar quais partes do código-fonte de um projeto estão sendo testadas pelos testes e a calcular a porcentagem de cobertura de código.

Algumas das vantagens no uso o Pytest-cov é: Geração de Relatórios de cobertura, Integração com o Pytest, Integração com ferramentas de CI/CD, excluir arquivos e blocos que não devem ser testados.

Instalando o Pytest-cov

A instalação do Pytst-cov é bem simples e requer apenas um único comando, para isso na raiz do seu projeto rode o comando abaixo.

pip install pytest-cov

Ou se preferir, crie um arquivo na raiz do seu projeto chamada de requirements.txt e insira o comando pytest-cov. Agora rode o comando pip abaixo para instalar as bibliotecas listadas no arquivo.

pip install -r requirements.txt

Agora rode o comando pip abaixo para garantir que o Pytest-cov foi instalado corretamente.

pip show pytest-cov
#pip show pytest-cov | grep Version

Escrevendo um simples caso de teste

O Pytest-cov já implementa o Pytest e adiciona novos recursos para cobertura de código, sendo assim devemos escrever nossos testes normalmente como se estivéssemos usando apenas o Pytest

Considere a classe de matemática abaixo que está no diretório src/app/math.py.

class Math:
    def sum(self, num1, num2):
        return num1 + num2

    def mult(self, num1, num2): # pragma: no cover
        return num1 * num2

Agora observe nosso caso de teste que está no diretório tests/app/test_math.py.

from src.app.math import Math

def test_sum():
    math = Math()
    assert math.sum(1, 1) == 2

Para executar nosso teste basta rodar o comando abaixo no seu terminal.

pytest

Testando a cobertura de código com Pytest-cov

Com nossos testes devidamente escritos e bibliotecas instaladas, agora vamos testar a nossa cobertura de código, para isso vamos rodar o comando abaixo no terminal.

pytest --cov=src/ --cov-report=html --cov-report term-missing

A saída será algo como mostra a imagem a seguir.

Os comandos acima podem parecer um pouco confuso, então vamos entender o que ele faz:

  1. pytest  - Inicializa a ferramenta de testes;
  2. --cov=src/ - Define qual é a pasta do nosso projeto, no caso a pasta src/;
  3. --cov-report=html - Aqui definimos como queremos o nosso relatório, ele vai ser em HTML e estará na pasta htmlcov. Caso queira mudar a pasta basta alterar para --cov-report=html:minha_pasta;
  4. --cov-report term-missing - Exibe o resultado do teste no terminal.

Vamos concordar que o comando acima não é nem um pouco intuitivo, são muitas as configurações que podemos definir, então vamos minimizar Início do Narrador isso. Na raiz do seu projeto crie um arquivo chamado pytest.ini e insira o comando abaixo.

[pytest]
addopts = --cov=src/ --cov-report=html --cov-report term-missing

Agora é só a gente rodar o comando pytest no terminal que todas as configurações acima serão levadas em consideração.

Agora nós temos dois problemas, se você analisar a imagem acima na qual rodamos os nossos testes de cobertura, vai notar que o total de cobertura é de 50% devido a uma função não testada e que os arquivos index.py e __init__.py estão no relatório mesmo não contendo nada para ser testado.

Removendo arquivos na cobertura de código com Pytest-cov

É comum a gente ter arquivos de configuração, inicializadores, dentre outros que não fazem parte do código a ser testado. Eles podem impactar diretamente na nota de cobertura quanto constar no relatório. 
Vamos remover alguns arquivos, para isso na raiz do seu projeto crie uma pasta chamada .coveragerc e insira o código abaixo.

[run]
source = src/
omit = __init__.py, *index.py

Perceba que na seção omit a gente insere e separa por vírgula os arquivos que desejamos remover da cobertura. Agora rodando o mesmo teste vamos ter um resultado diferente.

Ignorando blocos de código 

Se você analisar nossa métrica, estamos com 80% de cobertura de código, isso devido a nossa função mult() do arquivo src\app\math.py não ter uma cobertura de testes. Pode acontecer da gente ter um bloco de código que precisa ser ignorado, como um método que está em construção.

Para remover um bloco de código, basta inserir o comando  # pragma: no cover logo acima dele. Para o nosso exemplo, vamos ignorar a nossa função mult(), portanto devemos colocar logo a frente da declaração da nossa função, conforme mostra o código abaixo.

def mult(self, num1, num2): # pragma: no cover
    return num1 * num2

Agora se a gente rodar nossos testes vamos ver que a cobertura ficou em 100%.

Boas práticas na cobertura de código

Algumas boas práticas devem ser levadas em consideração durante a escrita e cobertura de código, são elas:

  1. Nomes únicos para funções de teste - Dê nomes únicos e descritivos às suas funções de teste para facilitar a identificação e compreensão dos casos de teste;
  2. Defina metas de cobertura - Estabeleça metas mínimas de cobertura de código para garantir que um percentual significativo do código seja testado, como um mínimo de 80%;
  3. Evite ignorar blocos de código - Não ignore blocos de código que precisam de testes apenas para aumentar a cobertura de código. Certifique-se de que os testes sejam valiosos e significativos;
  4. Teste arquivos relevantes - Além de blocos de código, não ignore arquivos inteiros que precisam de testes, garantindo que todo o código crítico seja testado;
  5. Testes claros - Escreva testes claros e legíveis para facilitar a manutenção e compreensão por outros membros da equipe.

Continue aprendendo

Confira a nossa videoaula complementar para aprender mais sobre cobertura de testes com Pytest-cov.