Exercício
Neste exercício, você usa o Pytest para testar uma função. Em seguida, você encontrar e corrigir alguns problemas potenciais com a função que causam testes de falha. Olhar para falhas e usar o rico relatório de erros do Pytest é essencial para identificar e corrigir testes problemáticos ou bugs no código de produção.
Para este exercício, usamos uma função chamada admin_command()
que aceita um comando do sistema como entrada e, opcionalmente, o prefixa com a sudo
ferramenta. A função tem um bug, que você descobre escrevendo testes.
Etapa 1 - Adicionar um arquivo com testes para este exercício
Usando as convenções de nome de arquivo do Python para arquivos de teste, crie um novo arquivo de teste. Nomeie o arquivo de teste test_exercise.py e adicione o seguinte código:
def admin_command(command, sudo=True): """ Prefix a command with `sudo` unless it is explicitly not needed. Expects `command` to be a list. """ if sudo: ["sudo"] + command return command
A função
admin_command()
usa uma lista como entrada usando ocommand
argumento e, opcionalmente, pode prefixar a lista comsudo
. Se o argumento dasudo
palavra-chave estiver definido comoFalse
, ele retornará o mesmo comando dado como entrada.No mesmo arquivo, anexe os testes para a
admin_command()
função. Os testes usam um método auxiliar que retorna um comando de exemplo:class TestAdminCommand: def command(self): return ["ps", "aux"] def test_no_sudo(self): result = admin_command(self.command(), sudo=False) assert result == self.command() def test_sudo(self): result = admin_command(self.command(), sudo=True) expected = ["sudo"] + self.command() assert result == expected
Nota
Não é comum ter testes dentro do mesmo arquivo como código real. Para simplificar, os exemplos neste exercício terão código real no mesmo arquivo. Em projetos Python do mundo real, os testes geralmente são separados por arquivos e diretórios do código que estão testando.
Passo 2 - Executar os testes e identificar a falha
Agora que o arquivo de teste tem uma função para testar e alguns testes para verificar seu comportamento, é hora de executar os testes e trabalhar com falhas.
Execute o arquivo com Python:
$ pytest test_exercise.py
A execução deve ser concluída com uma aprovação no teste e uma falha, e a saída da falha deve ser semelhante à seguinte saída:
=================================== FAILURES =================================== __________________________ TestAdminCommand.test_sudo __________________________ self = <test_exercise.TestAdminCommand object at 0x10634c2e0> def test_sudo(self): result = admin_command(self.command(), sudo=True) expected = ["sudo"] + self.command() > assert result == expected E AssertionError: assert ['ps', 'aux'] == ['sudo', 'ps', 'aux'] E At index 0 diff: 'ps' != 'sudo' E Right contains one more item: 'aux' E Use -v to get the full diff test_exercise.py:24: AssertionError =========================== short test summary info ============================ FAILED test_exercise.py::TestAdminCommand::test_sudo - AssertionError: assert... ========================= 1 failed, 1 passed in 0.04s ==========================
A saída falha no
test_sudo()
teste. Pytest está dando detalhes sobre as duas listas que estão sendo comparadas. Neste caso, aresult
variável não tem osudo
comando, que é o que o teste espera.
Passo 3 - Corrigir o bug e fazer os testes passarem
Antes de fazer qualquer alteração, você deve entender por que há uma falha em primeiro lugar. Embora você possa ver que a expectativa não está sendo atendida (sudo
não está no resultado), você tem que descobrir o porquê.
Observe as seguintes linhas de código da admin_command()
função quando a sudo=True
condição for atendida:
if sudo:
["sudo"] + command
A operação das listas não está sendo usada para retornar o valor. Como não está sendo retornado, a função acaba retornando o comando sem sudo
sempre.
Atualize a
admin_command()
função para retornar a operação de lista para que o resultado modificado seja usado ao solicitar umsudo
comando. A função atualizada deve ter esta aparência:def admin_command(command, sudo=True): """ Prefix a command with `sudo` unless it is explicitly not needed. Expects `command` to be a list. """ if sudo: return ["sudo"] + command return command
Execute novamente o teste com Pytest. Tente aumentar a detalhamento da saída usando o
-v
sinalizador com Pytest:$ pytest -v test_exercise.py
Agora verifique a saída. Ele deve mostrar dois testes de aprovação agora:
============================= test session starts ============================== Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 cachedir: .pytest_cache rootdir: /private collected 2 items test_exercise.py::TestAdminCommand::test_no_sudo PASSED [ 50%] test_exercise.py::TestAdminCommand::test_sudo PASSED [100%] ============================== 2 passed in 0.00s ===============================
Nota
Uma vez que a função é capaz de trabalhar com mais valores de invólucros diferentes, mais testes devem ser adicionados para cobrir essas variações. Isso impediria que alterações futuras na função causassem um comportamento diferente (inesperado).
Etapa 4 - Adicionar novo código com testes
Depois de adicionar testes nas etapas anteriores, você deve se sentir confortável para fazer mais alterações na função e verificá-las com testes. Mesmo que as alterações não sejam cobertas por testes existentes, você pode se sentir confiante de que não está quebrando nenhuma suposição anterior.
Neste caso, a admin_command()
função é confiar cegamente que o command
argumento é sempre uma lista. Vamos melhorar isso garantindo que uma exceção com uma mensagem de erro útil seja gerada.
Primeiro, crie um teste que capture o comportamento. Embora a função ainda não esteja atualizada, tente uma abordagem test-first (também conhecida como Test Driven Development ou TDD).
- Atualize o arquivo test_exercise.py para que ele seja importado
pytest
na parte superior. Este teste usa um auxiliar interno dapytest
estrutura:
import pytest
- Agora, anexe um novo teste à classe para verificar a exceção. Este teste deve esperar um
TypeError
da função quando o valor passado para ela não é uma lista:
def test_non_list_commands(self): with pytest.raises(TypeError): admin_command("some command", sudo=True)
- Atualize o arquivo test_exercise.py para que ele seja importado
Execute os testes novamente com o Pytest, todos eles devem passar:
============================= test session starts ============================== Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /private/ collected 3 items test_exercise.py ... [100%] ============================== 3 passed in 0.00s ===============================
O teste é bom o suficiente para verificar
TypeError
, mas seria bom adicionar o código com uma mensagem de erro útil.Atualize a função para gerar uma mensagem de
TypeError
erro explícita com uma útil mensagem:def admin_command(command, sudo=True): """ Prefix a command with `sudo` unless it is explicitly not needed. Expects `command` to be a list. """ if not isinstance(command, list): raise TypeError(f"was expecting command to be a list, but got a {type(command)}") if sudo: return ["sudo"] + command return command
Finalmente, atualize o
test_non_list_commands()
método para verificar a mensagem de erro:def test_non_list_commands(self): with pytest.raises(TypeError) as error: admin_command("some command", sudo=True) assert error.value.args[0] == "was expecting command to be a list, but got a <class 'str'>"
O teste atualizado usa
error
como uma variável que contém todas as informações de exceção. Usandoerror.value.args
o , você pode examinar os argumentos da exceção. Nesse caso, o primeiro argumento tem a cadeia de erro que o teste pode verificar.
Verifique o seu trabalho
Neste ponto, você deve ter um arquivo de teste Python chamado test_exercise.py que inclui:
- Uma
admin_command()
função que aceita um argumento e um argumento de palavra-chave. - Uma
TypeError
exceção com uma mensagem de erro útil naadmin_command()
função. - Uma
TestAdminCommand()
classe de teste que tem umcommand()
método auxiliar e três métodos de teste que verificam aadmin_command()
função.
Todos os testes devem ser aprovados sem erros quando você executá-los no terminal.