Acessórios de Pytest
As fixtures são funções auxiliares do pytest usadas para criar testes modulares, escaláveis e mantêveis. Você usa acessórios para configurar pré-condições para testes, como conexões de banco de dados, criação de dados de teste ou configuração de um estado do sistema necessário antes que um teste possa ser executado. Eles também podem ser usados para limpeza depois que os testes terminarem de ser executados.
As principais características das luminárias pytest incluem:
- Controle de escopo: as luminárias podem ser configuradas para ter escopos diferentes usando o
scopeparâmetro (comofunction, ,classoumodulesession), que determinam a frequência com que a luminária é chamada. - Manipulação de configuração e desinstalação: o Pytest gerencia o ciclo de vida dos acessórios, configurando e desinstalando automaticamente conforme necessário.
- Injeção de dependência: As fixtures são injetadas em funções de teste como argumentos, deixando claro quais testes dependem de quais fixtures.
- Reutilização e modularidade: os acessórios podem ser definidos em um só lugar e usados em várias funções de teste, módulos ou até mesmo projetos.
Criação de um acessório de arquivo temporário
Ao escrever testes que interagem com arquivos, é comum precisar de arquivos temporários que não desorganizem o sistema de arquivos após o teste. Com o pytest, podemos criar uma fixture que prepara um arquivo temporário. A instalação usa o módulo do tempfile Python para gerar arquivos temporários com segurança, garantindo que eles possam ser usados e excluídos sem afetar o ambiente local. (Nesta versão inicial da nossa instalação, o arquivo não será excluído automaticamente devido ao delete=False sinalizador. Abordaremos a exclusão de arquivo posteriormente.)
Aqui está a aparência da luminária:
import pytest
import tempfile
@pytest.fixture
def tmp_file():
def create():
# Create a named temporary file that persists beyond the function scope
temp = tempfile.NamedTemporaryFile(delete=False)
return temp.name
return create
Nesta configuração, tmp_file() atua como o dispositivo de fixação. O nome do acessório é como os testes o referenciam. Dentro do acessório, a função aninhada create() cria o arquivo somente quando chamada, em vez de na configuração do acessório. Isso permite o controle preciso sobre quando o arquivo temporário é criado, o que é útil em testes em que o tempo e o estado do arquivo são críticos.
Dentro da função aninhada create(), um arquivo temporário é criado e retorna o caminho absoluto para ele. Veja um exemplo de como um teste pode usar o acessório que escrevemos:
import os
def test_file(tmp_file):
path = tmp_file()
assert os.path.exists(path)
Um teste usa uma luminária especificando o nome da luminária como um argumento. Nosso caso de uso simples pode ser facilmente expandido gravando no arquivo ou fazendo modificações, como alterar permissões ou propriedade.
Gerenciamento do escopo
No pytest, o gerenciamento do ciclo de vida dos recursos de teste por meio de rotinas de instalação e teardown é crucial para manter ambientes de teste limpos e eficientes. Você também deseja proteger a integridade dos testes, garantindo que cada teste comece com um estado conhecido e consistente. Por padrão, as fixtures do pytest operam com um function escopo, o que afeta o comportamento de duas maneiras:
- Ciclo de vida por teste: o valor retornado da luminária é recalculado para cada função de teste que o usa, garantindo que cada teste opere com um estado novo.
- Limpeza após cada uso: todas as operações de limpeza necessárias são executadas após cada teste que utiliza a instalação.
O Pytest também permite que as luminárias sejam definidas de forma mais ampla para otimizar o desempenho e o uso de recursos. O uso do escopo é particularmente útil em situações, como o gerenciamento do estado do banco de dados, ou nas quais há configurações de estado complexas cuja criação demanda tempo. Os quatro escopos disponíveis são:
function: escopo padrão, o acessório é executado uma vez por testeclass: o acessório é executado uma vez por classe de teste.module: é executado uma vez para um módulo.session: é executado uma vez por sessão de teste. Esse escopo é útil para operações caras que precisam persistir durante toda a sessão de teste, como inicializar um serviço ou iniciar um servidor de banco de dados.
Nesse caso, executar uma vez significa que o valor retornado é armazenado em cache. Portanto, um acessório que tenha um escopo de "módulo" pode ser chamado várias vezes em um módulo de teste, mas o valor de retorno é o do primeiro teste que o chamou.
Veja como o acessório tmp_file() ficaria com um escopo de módulo:
import pytest
import tempfile
@pytest.fixture(scope="module")
def tmp_file():
def create():
temp = tempfile.NamedTemporaryFile(delete=False)
return temp.name
return create
Gerenciamento de limpeza
O código anterior que especifica a tmp_file instalação cria um arquivo temporário, mas não lida automaticamente com a limpeza após a conclusão dos testes. Para garantir que os arquivos temporários não sejam deixados para trás, você pode usar a fixture do request pytest para registrar uma função de limpeza.
Veja como você pode modificar a tmp_file instalação para incluir a limpeza automática:
import pytest
import tempfile
import os
@pytest.fixture(scope="module")
def tmp_file(request):
# Create a temporary file that persists beyond the function scope
temp = tempfile.NamedTemporaryFile(delete=False)
def create():
# Returns the path of the temporary file
return temp.name
def cleanup():
# Remove the file after the tests are done
os.remove(temp.name)
# Register the cleanup function to be called after the last test in the module
request.addfinalizer(cleanup)
return create
Ao usar request.addfinalizer() e passar a função cleanup() aninhada, a limpeza é chamada dependendo do escopo. Nesse caso, o escopo é module, portanto, depois de todos os testes em um módulo, o pytest chama essa função de limpeza.
Usando conftest.py
Em vez de incluir seus acessórios em seus arquivos de teste, você pode salvá-los em um arquivo conftest.py . Todos os acessórios em conftest.py estão automaticamente disponíveis para seus testes no mesmo diretório sem precisar importá-los explicitamente.
Explorando dispositivos integrados
O Pytest tem muitos fixtures integrados projetados para simplificar os testes. Esses acessórios podem lidar com a configuração e a limpeza automaticamente, permitindo que você se concentre em escrever seus casos de teste em vez de se concentrar no gerenciamento de testes.
As principais luminárias internas incluem:
cache: usado para criar e gerenciar o cache no nível de teste, que é útil para armazenar dados entre sessões de teste.capsys: captura e permite a inspeção destderrestdout, facilitando a inspeção e o teste das saídas do console.tmpdir: fornece um diretório temporário para arquivos que precisam ser criados e usados durante testes.monkeypatch: fornece uma maneira de modificar com segurança o comportamento e os valores de objetos, funções e seu ambiente do sistema operacional.
A função do monkeypatching nos testes
Testar o código que se integra fortemente a recursos externos, como bancos de dados ou APIs externas, pode ser desafiador devido às dependências envolvidas. Uma técnica chamada monkey patching envolve modificar temporariamente seu sistema durante a execução de testes, o que permite independência de sistemas externos e possibilita modificar com segurança o estado e o comportamento do ambiente do sistema operacional durante o teste.
Aqui está um exemplo de como substituir a função os.path.exists() usando o acessório monkeypatch:
import os
def test_os(monkeypatch):
# Override os.path.exists to always return False
monkeypatch.setattr('os.path.exists', lambda x: False)
assert not os.path.exists('/')
Como alternativa, você pode usar o setattr() método com referência direta ao objeto e ao atributo:
def test_os(monkeypatch):
# Specify the object and attribute to override
monkeypatch.setattr(os.path, 'exists', lambda x: False)
assert not os.path.exists('/')
Além de definir atributos e substituir métodos, o monkeypatch componente pode definir e excluir variáveis de ambiente, alterar valores de dicionário e modificar caminhos do sistema. A monkeypatch luminária reverte automaticamente todas as alterações após cada teste, mas ainda é necessário tomar cuidado ao usar a monkeypatch luminária. Aqui estão alguns motivos para ter cuidado ao usá-lo:
-Clareza e manutenção do código: o uso excessivo ou o uso dele de maneiras complexas monkeypatch pode tornar o teste mais difícil de entender e manter. Quando você lê os resultados do teste, talvez não fique imediatamente claro como os componentes devem se comportar normalmente versus como eles são modificados para teste.-Validade dos testes: o uso de monkeypatching pode, às vezes, levar a testes que passam sob condições artificiais, muito diferentes do ambiente de produção. Isso pode criar uma falsa sensação de segurança, pois os testes podem passar porque alteraram demasiadamente o comportamento do sistema.-Dependência excessiva dos detalhes da implementação: os testes que dependem do monkeypatching podem estar firmemente acoplados a detalhes específicos de implementação do código que estão testando. Isso pode tornar os testes frágeis e suscetíveis a interrupções com alterações ainda menores na base de código subjacente.-Complexidade de depuração: os testes de depuração que usam monkeypatch podem ser mais complexos, especialmente se o patch mudar aspectos fundamentais do comportamento dos aplicativos. Entender por que um teste está falhando pode exigir um aprofundamento de como os componentes estão sendo modificados durante o teste.
Embora monkeypatch seja uma ferramenta poderosa para criar ambientes de teste isolados e controlados, ele deve ser usado de forma criteriosa e com uma compreensão clara de como ele afeta o conjunto de testes e o comportamento do aplicativo.