Bom dia a todos!
Da nossa mesa para a sua ... Ou seja, do nosso curso "Desenvolvedor Python", apesar da rápida aproximação do Ano Novo, preparamos uma tradução interessante para você sobre vários métodos de teste em Python.
Este guia é para aqueles que já escreveram um aplicativo Python legal, mas ainda não o escreveram para
eles testes.
Testar em Python é um tópico extenso com várias sutilezas, mas não é necessário complicar as coisas. Em algumas etapas simples, você pode criar testes simples para o aplicativo, aumentando gradualmente a complexidade com base neles.
Neste guia, você aprenderá como criar um teste básico, executá-lo e encontrar todos os erros antes que os usuários o façam! Você aprenderá sobre as ferramentas disponíveis para escrever e executar testes, verificar o desempenho do aplicativo e até mesmo examinar problemas de segurança.
Teste de códigoVocê pode testar o código de várias maneiras. Neste guia, você aprenderá sobre os métodos, do mais simples ao mais avançado.
Automatizado vs. Teste manual
Boas notícias! Provavelmente você já fez o teste, mas ainda não o realizou. Lembra como você iniciou o aplicativo e o usou? Você já testou as funções e fez experiências com elas? Esse processo é chamado de teste exploratório e é uma forma de teste manual.
Teste de pesquisa - teste feito sem um plano. Durante o teste de pesquisa, você pesquisa o aplicativo.
Para criar uma lista completa de testes manuais, basta fazer uma lista de todas as funções do aplicativo, os vários tipos de entrada que ele aceita e os resultados esperados. Agora, toda vez que você altera algo no código, é necessário verificar novamente cada um dos elementos nesta lista.
Parece sombrio, certo?
Portanto, são necessários testes automáticos. Teste automático - a execução do plano de teste (partes do aplicativo que exigem teste, a ordem dos testes e os resultados esperados) usando um script, e não por mãos humanas. O Python já possui um conjunto de ferramentas e bibliotecas para ajudá-lo a criar testes automatizados para o seu aplicativo. Vejamos essas ferramentas e bibliotecas em nosso tutorial.
Testes de Unidade VS. Testes de integraçãoO mundo dos testes é cheio de termos e, agora, conhecendo a diferença entre testes manuais e automatizados, desceremos um nível mais profundo.
Pense em como você pode testar os faróis de um carro? Você acende os faróis (vamos chamá-lo de etapa de teste), saia do carro ou peça a um amigo para verificar se os faróis estão acesos (e isso é uma proposta de teste). Testar vários componentes é chamado teste de integração.
Pense em todas as coisas que devem funcionar corretamente para que uma tarefa simples produza o resultado correto. Esses componentes são semelhantes às partes do seu aplicativo: todas essas classes, funções, módulos que você escreveu.
A principal dificuldade do teste de integração surge quando o teste de integração não fornece o resultado correto. É difícil avaliar o problema, não sendo possível isolar a parte quebrada do sistema. Se os faróis não estiverem acesos, as lâmpadas podem estar quebradas. Ou talvez a bateria esteja fraca? Ou talvez o problema esteja no gerador? Ou até mesmo uma falha no computador da máquina?
Os próprios carros modernos notificarão você sobre uma lâmpada quebrada. Isso é determinado usando um teste de unidade.
O teste de unidade (teste de unidade) é um pequeno teste que verifica a operação correta de um componente individual. O teste de unidade ajuda a isolar a avaria e corrigi-la mais rapidamente.
Nós conversamos sobre dois tipos de testes:
- Um teste de integração que verifica os componentes do sistema e sua interação entre si;
- Um teste de unidade que testa um único componente de um aplicativo.
- Você pode criar os dois testes em Python. Para escrever um teste para a função sum () interna, você precisa comparar a saída de sum () com valores conhecidos.
Por exemplo, dessa maneira, você pode verificar se a soma dos números (1, 2, 3) é 6:
>>> assert sum([1, 2, 3]) == 6, "Should be 6"
Os valores estão corretos, portanto, nada será emitido para o REPL. Se o resultado de
sum()
incorreto, um
AssertionError
será
AssertionError
com a mensagem "Deve ser 6". Verifique a instrução novamente, mas agora com valores inválidos para obter um
AssertionError
:
>>> assert sum([1, 1, 1]) == 6, "Should be 6" Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError: Should be 6
No REPL, você verá
AssertionError
pois o
sum()
não é 6.
Em vez de REPL, coloque isso em um novo arquivo Python chamado
test_sum.py
e execute-o novamente:
def test_sum(): assert sum([1, 2, 3]) == 6, "Should be 6" if __name__ == "__main__": test_sum() print("Everything passed")
Agora você tem um caso de teste escrito (caso de teste), instrução e ponto de entrada (linha de comando). Agora isso pode ser feito na linha de comando:
$ python test_sum.py Everything passed
Você vê o resultado bem-sucedido, "Tudo passou".
sum()
em Python aceita qualquer iterável como o primeiro argumento. Você verificou a lista. Vamos tentar testar a tupla. Crie um novo arquivo chamado
test_sum_2.py
com o seguinte código:
def test_sum(): assert sum([1, 2, 3]) == 6, "Should be 6" def test_sum_tuple(): assert sum((1, 2, 2)) == 6, "Should be 6" if __name__ == "__main__": test_sum() test_sum_tuple() print("Everything passed")
test_sum_2.py
, o script gerará
test_sum_2.py
erro, pois s
um() (1, 2, 2)
deve ser 5, e não 6. Como resultado, o script fornece uma mensagem de erro, uma linha de código e um retorno:
$ python test_sum_2.py Traceback (most recent call last): File "test_sum_2.py", line 9, in <module> test_sum_tuple() File "test_sum_2.py", line 5, in test_sum_tuple assert sum((1, 2, 2)) == 6, "Should be 6" AssertionError: Should be 6
Você pode ver como um erro no código causa um erro no console com informações sobre onde ocorreu e qual foi o resultado esperado.
Esses testes são adequados para verificação simples, mas e se houver mais erros do que em um? Os corredores de teste vêm ao resgate. O Test Executor é um aplicativo especial projetado para realizar testes, verificar dados de saída e fornecer ferramentas para depuração e diagnóstico de testes e aplicativos.
Escolhendo um executor de testeExistem muitos corredores de teste disponíveis para Python. Por exemplo, o unittest é incorporado à biblioteca padrão do Python. Neste guia, usaremos casos de teste e executores de teste mais unittest. Os princípios operacionais mais unittest são facilmente adaptáveis a outras estruturas. Listamos os executores de teste mais populares:
- mais unittest;
- nariz ou nariz2;
- pytest.
É importante escolher um contratado de teste que atenda aos seus requisitos e experiência.
mais unittestO unittest foi integrado à biblioteca padrão do Python desde a versão 2.1. Você provavelmente o encontrará em aplicativos comerciais Python e em projetos de código aberto.
O Unittest possui uma estrutura de teste e um executor de teste. Ao escrever e executar testes, você precisa seguir alguns requisitos importantes.
unittest requer:
- Coloque testes em classes como métodos;
- Use métodos especiais de aprovação. A classe TestCase, em vez da expressão de declaração interna usual.
Para transformar um exemplo escrito anteriormente em um caso de teste mais unívoco, você deve:
- Importar unittest da biblioteca padrão;
- Crie uma classe chamada
TestSum
que herdará a classe TestCase
; - Converta funções de teste em métodos adicionando
self
como o primeiro argumento; - Modifique as instruções adicionando o uso do método
self.assertEqual()
na classe TestCase
; - Altere o ponto de entrada na linha de comando para chamar
unittest.main()
.
Seguindo estas etapas, crie um novo arquivo test_sum_unittest.py com este código:
import unittest class TestSum(unittest.TestCase): def test_sum(self): self.assertEqual(sum([1, 2, 3]), 6, "Should be 6") def test_sum_tuple(self): self.assertEqual(sum((1, 2, 2)), 6, "Should be 6") if __name__ == '__main__': unittest.main()
Ao fazer isso na linha de comando, você obterá uma conclusão bem-sucedida (indicada por.) E outra sem êxito (indicada por F):
$ python test_sum_unittest.py .F ====================================================================== FAIL: test_sum_tuple (__main__.TestSum) ---------------------------------------------------------------------- Traceback (most recent call last): File "test_sum_unittest.py", line 9, in test_sum_tuple self.assertEqual(sum((1, 2, 2)), 6, "Should be 6") AssertionError: Should be 6 ---------------------------------------------------------------------- Ran 2 tests in 0.001s FAILED (failures=1)
Assim, você executou dois testes usando o corredor de teste mais unittest.
Nota: Se você estiver escrevendo casos de teste para Python 2 e 3, tenha cuidado. Nas versões do Python 2.7 e abaixo, o unittest é chamado unittest 2. Quando você importa do unittest, você obtém versões diferentes com funções diferentes no Python 2 e Python 3.Para saber mais sobre o unittest, leia a
documentação unittest .
narizCom o tempo, depois de escrever centenas ou mesmo milhares de testes para um aplicativo, torna-se cada vez mais difícil entender e usar dados de saída mais unidos.
O nariz é compatível com todos os testes escritos com uma estrutura mais unida e pode substituir seu executor de teste. O desenvolvimento do nariz, como um aplicativo de código aberto, começou a desacelerar e o nariz2 foi criado. Se você estiver começando do zero, é recomendável usar o nariz2.
Para começar com o nose2, você precisa instalá-lo no PyPl e executá-lo na linha de comando. O nose2 tentará encontrar todos os scripts de teste com
test*.py
no nome e todos os casos de teste herdados do unittest.TestCase no seu diretório atual:
$ pip install nose2 $ python -m nose2 .F ====================================================================== FAIL: test_sum_tuple (__main__.TestSum) ---------------------------------------------------------------------- Traceback (most recent call last): File "test_sum_unittest.py", line 9, in test_sum_tuple self.assertEqual(sum((1, 2, 2)), 6, "Should be 6") AssertionError: Should be 6 ---------------------------------------------------------------------- Ran 2 tests in 0.001s FAILED (failures=1)
É assim que o teste criado em
test_sum_unittest.py
partir do executor de testes do nose2. O nose2 fornece muitos sinalizadores de linha de comando para filtrar testes executáveis. Para mais informações, consulte a
documentação do
Nose 2 .
pytestpytest suporta casos de teste mais unittest. Mas a vantagem real do pytest é seus casos de teste. Os casos de teste pytest são uma série de funções em um arquivo Python com test_ no início do nome.
Existem outros recursos úteis:
- Suporte para expressões de declaração internas em vez de usar métodos self.assert * () especiais;
- Suporte para filtragem de casos de teste;
- A capacidade de reiniciar a partir do último teste com falha;
- Um ecossistema de centenas de plugins que ampliam a funcionalidade.
Um exemplo de caso de teste TestSum para pytest terá a seguinte aparência:
def test_sum(): assert sum([1, 2, 3]) == 6, "Should be 6" def test_sum_tuple(): assert sum((1, 2, 2)) == 6, "Should be 6"
Você se livrou do TestCase, usando classes e pontos de entrada da linha de comando.
Mais informações podem ser encontradas no
site de documentação do
Pytest .
Escrevendo o primeiro testeCombine tudo o que já aprendemos e, em vez da função interna
sum()
, testamos uma implementação simples com os mesmos requisitos.
Crie uma nova pasta para o projeto, dentro da qual crie uma nova pasta chamada my_sum. Dentro de my_sum, crie um arquivo vazio chamado
_init_.py
. A presença desse arquivo significa que a pasta my_sum pode ser importada como um módulo do diretório pai.
A estrutura da pasta ficará assim:
project/
│
└── my_sum/
└── __init__.py
Abra
my_sum/__init__.py
e crie uma nova função chamada
sum()
, que recebe entradas
my_sum/__init__.py
(lista, tupla, conjunto) e adiciona os valores.
def sum(arg): total = 0 for val in arg: total += val return total
Este exemplo cria uma variável chamada
total
, itera sobre todos os valores em
arg
e adiciona ao
total
. Em seguida, após a conclusão da iteração, o resultado é retornado.
Onde escrever um testeVocê pode começar a escrever um teste criando um arquivo
test.py
que conterá seu primeiro caso de teste. Para testar, o arquivo deve poder importar seu aplicativo, então coloque
test.py
na pasta acima do pacote. A árvore de diretórios ficará assim:
project/
│
├── my_sum/
│ └── __init__.py
|
└── test.py
Você notará que, à medida que adiciona novos testes, seu arquivo se torna mais pesado e difícil de manter; portanto, recomendamos criar a pasta
tests/
e dividir os testes em vários arquivos. Verifique se os nomes de todos os arquivos começam com
test_
, para que os
test_
compreendam que os arquivos Python contêm testes que precisam ser executados. Em projetos grandes, os testes são divididos em vários diretórios, dependendo de sua finalidade ou uso.
Nota: E qual é o seu aplicativo é um único script?
Você pode importar qualquer atributo de script: classes, funções ou variáveis, usando a função __import__()
. Em vez da from my_sum import sum
escreva o seguinte: target = __import__("my_sum.py") sum = target.sum
Ao usar __import__()
você não precisa transformar a pasta do projeto em um pacote e pode especificar o nome do arquivo. Isso é útil se o nome do arquivo entrar em conflito com os nomes das bibliotecas de pacotes padrão. Por exemplo, se math.py
entrar em conflito com o módulo de matemática.Como estruturar um teste simplesAntes de escrever testes, você precisa resolver algumas perguntas:
- O que você quer testar?
- Você está escrevendo um teste de unidade ou de integração?
Você está testando
sum()
. Você pode testar diferentes comportamentos, por exemplo:
- É possível resumir uma lista de números inteiros?
- É possível resumir uma tupla ou um conjunto?
- Posso resumir uma lista de números de ponto flutuante?
- O que acontece se você atribuir um valor ruim à entrada: um único número inteiro ou uma string?
- O que acontece se um dos valores for negativo?
A maneira mais fácil de testar é uma lista de números inteiros. Crie um arquivo
test.py
com o seguinte código:
import unittest from my_sum import sum class TestSum(unittest.TestCase): def test_list_int(self): """ Test that it can sum a list of integers """ data = [1, 2, 3] result = sum(data) self.assertEqual(result, 6) if __name__ == '__main__': unittest.main()
O código neste exemplo:
- Importa
sum()
do pacote my_sum()
que você criou; - Define uma nova classe de caso de teste chamada TestSum que herda
unittest.TestCase
; - Define um método test
.test_list_int()
para testar uma lista inteira. O método .test_list_int()
fará o seguinte
:
- Declara uma variável de
data
com uma lista de valores (1, 2, 3)
; my_sum.sum(data)
valor my_sum.sum(data)
result
my_sum.sum(data)
variável;- Determina que o valor do resultado seja 6 usando o método
.assertEqual()
na classe unittest.TestCase
.
- Define um ponto de entrada da linha de comando que inicia o executor de teste
.main()
.
Se você não souber o que é, ou como
.assertEqual()
é definido, poderá atualizar seu conhecimento de programação orientada a objetos com a
Programação Orientada a Objetos do Python 3 .
Como escrever instruçõesA etapa final ao escrever um teste é verificar se a saída corresponde aos valores conhecidos. Isso é chamado de afirmação. Existem várias diretrizes gerais para escrever declarações:
- Verifique se os testes são repetíveis e execute-os várias vezes para garantir que eles apresentem os mesmos resultados a cada vez;
- Verifique e confirme os resultados que se aplicam à sua entrada - verifique se o resultado é realmente a soma dos valores no exemplo
sum()
.
O Unittest possui muitos métodos para confirmar os valores, tipos e existência de variáveis. Aqui estão alguns dos métodos mais usados:
Método | Equivalente |
---|
.assertEqual (a, b) | a == b |
.assertTrue (x) | bool (x) é True |
.assertFalse (x) | bool (x) é False |
.assertIs (a, b) | a é b |
.assertIsNone (x) | x é nenhum |
.assertIn (a, b) | a em b |
.assertIsInstance (a, b) | instância (a, b) |
.assertIs()
,
.assertIsNone()
,
.assertIn()
e
.assertIsInstance()
têm métodos opostos chamados
.assertIsNot()
e assim por diante.
Efeitos colateraisEscrever testes é mais difícil do que apenas observar o valor de retorno de uma função. Frequentemente, a execução do código altera outras partes do ambiente: atributos de classe, arquivos do sistema de arquivos, valores no banco de dados. Essa é uma parte importante do teste, chamada de efeitos colaterais. Decida se você está testando um efeito colateral antes de incluí-lo em sua lista de reivindicações.
Se você achar que existem muitos efeitos colaterais no bloco de código que deseja testar, estará violando o
Princípio da responsabilidade exclusiva . Violar o princípio da única responsabilidade significa que um pedaço de código faz muitas coisas e requer refatoração. Seguir o princípio da responsabilidade exclusiva é uma ótima maneira de projetar código para o qual não será difícil escrever testes de unidade simples e repetíveis e, finalmente, criar aplicativos confiáveis.
Lançamento do primeiro testeVocê criou o primeiro teste e agora precisa tentar executá-lo. É claro que será aprovado, mas antes de criar testes mais complexos, você precisa garantir que mesmo esses testes sejam bem-sucedidos.
Executando Executores de TesteExecutor de teste - um aplicativo Python que executa o código de teste, valida asserções e exibe os resultados do teste no console. No final do test.py, adicione este pequeno pedaço de código:
if __name__ == '__main__': unittest.main()
Este é o ponto de entrada da linha de comandos. Se você executar esse script executando python
test.py
na linha de comando, ele chamará
unittest.main()
. Isso inicia o
unittest.TestCase
teste detectando todas as classes neste arquivo
unittest.TestCase
do
unittest.TestCase
.
Essa é uma das muitas maneiras de executar o corredor de teste mais unittest. Se você tiver um único arquivo de teste chamado
test.py
, chamar python test.py é uma ótima maneira de começar.
Outra maneira é usar a linha de comando mais unittest. Vamos tentar:
$ python -m unittest test
Isso executará o mesmo módulo de teste (chamado
test
) através da linha de comando. Você pode adicionar parâmetros adicionais para alterar a saída. Um deles é -v para detalhado. Vamos tentar o seguinte:
$ python -m unittest -v test test_list_int (test.TestSum) ... ok ---------------------------------------------------------------------- Ran 1 tests in 0.000s
Executamos um teste no test.py e exibimos os resultados no console. O modo detalhado listou os nomes dos testes realizados e os resultados de cada um deles.
Em vez de fornecer o nome do módulo que contém os testes, você pode solicitar a descoberta automática usando o seguinte:
$ python -m unittest discover
Este comando procurará no diretório atual arquivos com
test*.py
no nome para testá-los.
Se você tiver vários arquivos de teste e seguir o padrão de nomeação
test*.py
, poderá passar o nome do diretório usando o sinalizador -s e o nome da pasta.
$ python -m unittest discover -s tests
O unittest executará todos os testes em um único plano de teste e produzirá os resultados.
Por fim, se o código-fonte não estiver no diretório raiz, mas em um subdiretório, por exemplo, em uma pasta chamada src /, você poderá dizer a unittest onde executar os testes usando o sinalizador -t para importar corretamente os módulos:
$ python -m unittest discover -s tests -t src
O unittest encontrará todos
test*.py
arquivos
test*.py
no diretório
src/
dentro dos
tests
e os executa.
Compreendendo os resultados do teste
Este foi um exemplo muito simples, onde tudo correu bem, então vamos tentar entender a saída de um teste que falhou.
sum()
deve aceitar outras listas de um tipo numérico, por exemplo, frações.
No início do código em
test.py
adicione uma expressão para importar o tipo de
fractions
módulo de
fractions
da biblioteca padrão.
from fractions import Fraction
Agora adicione um teste com uma instrução, esperando um valor incorreto. No nosso caso, esperamos que a soma de ¼, ¼ e ⅖ seja igual a 1:
import unittest from my_sum import sum class TestSum(unittest.TestCase): def test_list_int(self): """ Test that it can sum a list of integers """ data = [1, 2, 3] result = sum(data) self.assertEqual(result, 6) def test_list_fraction(self): """ Test that it can sum a list of fractions """ data = [Fraction(1, 4), Fraction(1, 4), Fraction(2, 5)] result = sum(data) self.assertEqual(result, 1) if __name__ == '__main__': unittest.main()
Se você executar os testes novamente com o teste unittest python -m, faça o seguinte:
$ python -m unittest test F. ====================================================================== FAIL: test_list_fraction (test.TestSum) ---------------------------------------------------------------------- Traceback (most recent call last): File "test.py", line 21, in test_list_fraction self.assertEqual(result, 1) AssertionError: Fraction(9, 10) != 1 ---------------------------------------------------------------------- Ran 2 tests in 0.001s FAILED (failures=1)
Nesta saída, você vê o seguinte:
- A primeira linha mostra os resultados de todos os testes: um falhou (F), um passou (.);
- FAIL mostra alguns detalhes do teste que falhou:
- O nome do método de teste (
test_list_fraction
); - Módulo de
test
( test
) e caso de teste ( TestSum
); - Traceback strings com um erro;
- Detalhes da declaração com o resultado esperado (1) e o resultado real (Fração (9, 10))
Lembre-se de que você pode adicionar informações adicionais à saída de teste usando o sinalizador -v no
python -m unittest
.
Executando testes do PyCharmSe você estiver usando o PyCharm IDE, poderá executar o unittest ou o pytest seguindo estas etapas:
- Na janela da ferramenta Projeto, selecione o diretório de testes.
- No menu de contexto, selecione o comando de execução mais unittest. Por exemplo, 'Unittests in my Tests ...'.
Isso será executado unittest na janela de teste e retornará os resultados no PyCharm:

Mais informações estão disponíveis no
site do PyCharm .
Executando testes do código do Visual StudioSe você usa o IDE do Microsoft Visual Studio Code, o suporte para unittest, nose e pytest já está incorporado no plug-in Python.
Se você o tiver instalado, poderá configurar a configuração de teste abrindo a Paleta de Comandos com Ctrl + Shift + P e escrevendo "Teste Python". Você verá uma lista de opções:

Selecione Depurar todos os testes de unidade, após o qual o VSCode enviará uma solicitação para configurar a estrutura de teste. Clique na engrenagem para selecionar o corredor de teste (mais unittest) e o diretório inicial (.).
Após a conclusão da instalação, você verá o status dos testes na parte inferior da tela e poderá acessar rapidamente os logs de teste e reiniciar os testes clicando nos ícones:

Vimos que os testes estão sendo realizados, mas alguns deles falharam.
O FIM
Na próxima parte do artigo, examinaremos testes para estruturas como Django e Flask.
Estamos aguardando suas perguntas e comentários aqui e, como sempre, você pode ir a Stanislav em um
dia aberto .
Segunda parte