S03 - Atividade de Aprendizagem (1 de 2): Testando Funções
Durante esta aula, você aprenderá como usar uma abordagem mais sistemática para desenvolver códigos. Especificamente, você aprenderá a escrever funções de teste que verificam automaticamente se as funções do programa estão corretas. Você aprenderá como usar um módulo do Python, chamado pytest para executar suas funções de teste e aprenderá como ler a saída do pytest, para ajudá-lo a encontrar e corrigir erros em seu código.
Conceitos
Aqui estão os conceitos e assuntos de programação em Python que você deve aprender durante esta aula:
Testes Ineficientes
Durantes suas aulas anteriores, você testou programas executando-os, digitando a entrada do usuário, lendo a saída do programa e verificando que a saída estava correta. Esta é uma forma válida de testar o programa. No entanto, é demorado, tedioso e propenso a erros. Uma maneira muito melhor de testar um programa é testar suas funções individualmente e escrever funções de teste separadas que verifiquem automaticamente se as funções do programa estão corretas.
Neste curso, você escreverá funções de teste em um arquivo Python separado do seu programa Python. Em outras palavras, você manterá o código normal do programa e o código de teste em arquivos separados.
Instruções Assert
Em um programa de computador, uma asserção é uma instrução que faz com que o computador verifique se uma comparação é verdadeira. Se for, ele continuará a executar o código no programa. Caso contrário, gerará um AssertionError, o que provavelmente fará com que o programa seja encerrado. (Na Semana 05, você aprenderá a escrever um código para lidar com erros, de modo que um programa não seja encerrado quando o computador indicar um erro.)
Um programador escreve asserções em um programa para informar o computador sobre comparações que devem ser verdadeiras, para que o programa seja executado com sucesso. A palavra-chave Python para escrever uma asserção
é assert. Imagine um programa usado por um banco para rastrear saldos de contas, depósitos e saques. Um programador pode escrever as primeiras linhas da função deposito dessa forma:
def deposito(valor):
# Para que este programa funcione corretamente e
# para que os registros bancários estejam corretos, não devemos
# permitir que alguém deposite um valor zero ou negativo.
assert valor > 0
⋮
A instrução assert na linha 5 do exemplo anterior fará com que o computador verifique se o valor é maior que zero (0). Se o valor for maior que zero, o computador continuará executando o programa. Entretanto, se o valor for zero ou menor que zero (ou seja, negativo), o computador gerará um AssertionError, o que provavelmente fará com que o programa seja encerrado.
Um programador pode escrever qualquer comparação válida do Python em uma instrução assert. Aqui estão alguns exemplos de vários programas não relacionados:
assert temperatura < 0
assert len(primeiro_nome) > 0
assert saldo == 0
assert ano_letivo != "3º ano do ensino médio"
Módulo pytest
pytest é um módulo Python de terceiros que facilita a escrita e execução de funções de teste. Embora existam outros módulos de teste em Python, o pytest é considerado um dos mais fáceis de usar. Por ser um módulo de terceiros, pytest não é incluído na instalação padrão do Python. Isso significa que, ao instalar o Python no seu computador, o pytest não vem junto, e você precisará instalá-lo separadamente. Nesta aula, você usará um módulo padrão do Python chamado pip para instalar o pytest.
O pytest permite que um programador escreva funções de teste simples. O nome de cada função de teste deve começar com " test_", e cada função de teste deve usar a instrução assert do Python para verificar se uma função de programa retorna um resultado correto. Por exemplo, se quisermos verificar se a função min interna funciona corretamente, poderíamos escrever uma função de teste desta forma:
def test_min():
assert min(7, -3, 0, 2) == -3
Na função de teste anterior, a instrução assert fará com que o computador primeiro chame a função min e passe 7, −3, 0 e 2 como argumentos para a função min. A função min encontrará o valor mínimo dos seus parâmetros e retornará esse valor mínimo. Então a instrução assert comparará o valor mínimo retornado com −3. Se o valor retornado não for −3, a instrução assert gerará uma exceção que fará com que o pytest imprima uma mensagem de erro.
Caso queira verificar a documentação original do pytest, acesse este link pytest (conteúdo em inglês)
Comparando Números de Ponto Flutuante
Na memória de um computador, tudo (todos os números, textos, sons, imagens, filmes, etc.) é armazenado usando o sistema numérico binário. Ao executar um programa Python, um computador armazena números inteiros em binário, de uma forma que representa exatamente os números inteiros. Por exemplo, um computador armazena o número inteiro 23 como 00010111 em binário, que é uma representação exata do decimal 23. Entretanto, um computador aproxima números de ponto flutuante (números com dígitos após a casa decimal). Por exemplo, ao executar um programa Python, um computador armazena o número de ponto flutuante 23.7 como o binário 0100000000110111101100110011001100110011001100110011001100110011. Este número binário é na verdade 23.69999999999999928945726424 em decimal, que é uma aproximação de 23.7
Como os computadores aproximam números de ponto flutuante, devemos compará-los cuidadosamente em nossas funções de teste. É uma prática ruim verificar se números de ponto flutuante são iguais usando apenas o operador de igualdade (==). Uma maneira melhor de comparar dois números de ponto flutuante é subtraí-los e verificar se a diferença é pequena, como mostrado na linha 6 do exemplo 4.
# Exemplo 4
# A variável "e" e a variável "f" podem ser quaisquer números
# de pontos flutuante de um cálculo matemático.
e = 7.135
f = 7.128
if abs(e - f) < 0.01:
print(f"{e} e {f} são próximos o suficiente.")
print("Vamos considerá-los iguais.")
else:
print(f"{e} e {f} não são próximos o suficiente,")
print("portanto não são iguais.")
No exemplo 4 na linha 6, se a diferença entre e e f for menor que 0.01, o computador considerará os dois números como sendo iguais. O número 0.01 na comparação na linha 6 é chamado de tolerância. A tolerância é a maior diferença entre dois números de ponto flutuante que o programador aceita para ainda considerar que os números são iguais.
Função approx
A comparação no exemplo 4 na linha 6 é um pouco tediosa de escrever e ler. Além disso, às vezes é difícil escolher a tolerância. O módulo pytest contém uma função chamada approx, para nos ajudar a comparar números de ponto flutuante mais facilmente. A função approx * (conteúdo em inglês) compara dois números de ponto flutuante e retorna True se eles forem iguais dentro de uma tolerância apropriada.
approxnão é uma função. É uma classe Python. Como os estudantes do CSE 111 ainda não aprenderam sobre classes, este documento se refere aapproxcomo uma função. Para as finalidades do CSE 111, é suficiente que os estudantes pensem emapproxcomo uma função.
A função approx tem o seguinte cabeçalho:
def approx(valor_esperado, rel=None, abs=None, nan_ok=False)
Observe que os três últimos parâmetros da função approx têm valores padrão: rel=None, abs=None, nan_ok=False. Como eles têm valores padrão, quando chamamos approx, não precisamos passar argumentos para os três últimos parâmetros. Em outras palavras, em uma função de teste, podemos chamar approx dessa forma:
def test_funcao():
assert valor_real == approx(valor_esperado)
Se chamarmos a função approx com apenas um argumento, ela comparará o valor real com o valor esperado e retornará True se a diferença entre os dois valores for menor que valor_esperado / 1000000, que é a tolerância padrão.
No entanto, essa tolerância pode não ser adequada em todos os casos. A função approx possui dois parâmetros opcionais. A função approx possui dois parâmetros opcionais, rel e abs, que permitem definir uma tolerância mais apropriada para a comparação.
Por exemplo, para testar a função math.sqrt, podemos escrever:
# Exemplo 5
def test_sqrt():
assert math.sqrt(5) == approx(2.24, rel=0.01)
Observe o argumento nomeado rel na linha 3 do exemplo anterior. Esse argumento faz com que a função approx calcule a tolerância com base no valor esperado. Assim, a instrução assert verifica se o valor real retornado de math.sqrt(5) está dentro de 1% (0.01) de 2.24.
Quando usamos o parâmetro rel, a função approx executa um código semelhante ao exemplo 6 para determinar se os valores reais e esperados são iguais
# Exemplo 6
# Calcula a tolerância.
tolerancia = valor_esperado * rel
# Usa a tolerância para determinar se os valores
# reais e esperados são próximos o suficiente para serem
# considerados iguais.
if abs(valor_real - valor_esperado) < tolerancia:
return True
else:
return False
Aprendemos a partir das linhas 3 e 7 do exemplo 6, que approx retornará True se a diferença entre o valor real retornado de math.sqrt(5) e o valor esperado for menor que 0.0224 (2.24 * 0.01).
Também podemos usar o argumento abs para dar uma tolerância a approx. Podemos escrever um teste para a função math.sqrt dessa forma:
# Exemplo 7
def test_sqrt():
assert math.sqrt(5) == approx(2.24, abs=0.01)
Observe o argumento nomeado abs na linha 3 do exemplo anterior. O argumento nomeado abs faz com que a função approx retorne True se a diferença entre os valores reais e esperados for menor que o número em abs (0.01 no exemplo anterior). Isso é diferente do argumento nomeado rel, que faz com que approx retorne True, se a diferença for menor que rel * valor_esperado. O argumento chamado abs é mais simples e fácil de entender do que o argumento chamado rel.
Como Testar uma Função
Para testar uma função, você deve fazer o seguinte:
-
Escreva uma função que faça parte do seu programa Python normal.
-
Pense em diferentes valores de parâmetros que farão com que o computador execute todo o código em sua função e possivelmente farão com que sua função falhe ou retorne um resultado incorreto.
-
Em um arquivo Python separado, escreva uma função de teste que chame a sua função de programa e use uma instrução
assertpara verificar automaticamente se o valor retornado da função do seu programa está correto. -
Usa
pytestpara executar a função de teste. -
Leia a saída do
pyteste use-a para ajudar a encontrar e corrigir erros tanto na função de programa quanto na função de teste.
Exemplo
Abaixo está uma função simples chamada fahr_para_celsius, que converte uma temperatura de Fahrenheit para Celsius e retorna a temperatura em Celsius. A função fahr_para_celsius faz parte de um programa Python maior em um arquivo chamado clima.py.
# clima.py
def fahr_para_celsius(fahr):
"""Converter uma temperatura em Fahrenheit para
Celsius e retornar a temperatura em Celsius.
"""
celsius = (fahr - 32) * 5 / 9
return celsius
Queremos testar a função fahr_para_celsius. No cabeçalho de função na linha 2 em clima.py, vemos que fahr_para_celsius recebe um parâmetro chamado fahr. Para testar adequadamente esta função, devemos chamá-la pelo menos três vezes com os seguintes argumentos.
-
um número negativo
-
zero
-
um número positivo
Em um arquivo separado chamado test_clima.py, escrevemos uma função de teste chamada test_fahr_para_celsius da seguinte forma:
# test_clima.py
from clima import fahr_para_celsius
from pytest import approx
import pytest
def test_fahr_para_celsius():
"""Testa a função fahr_para_celsius chamando-a e
comparando os valores retornados com os valores esperados.
Observe que esta função de teste usa pytest.approx para comparar
números de ponto flutuante.
"""
assert fahr_para_celsius(-25) == approx(-31.66667)
assert fahr_para_celsius(0) == approx(-17.77778)
assert fahr_para_celsius(32) == approx(0)
assert fahr_para_celsius(70) == approx(21.1111)
# Chama a função main que faz parte do pytest para que o
# o computador execute as funções de teste neste arquivo.
pytest.main(["-v", "--tb=line", "-rN", __file__])
Observe em test_clima.py nas linhas 11–14 que a função de teste test_fahr_para_celsius chama a função de programa fahr_para_celsius quatro vezes: uma vez com um número negativo, uma vez com zero e duas vezes com números positivos. Observe também que a função de teste usa assert e approx.
Após escrever a função de teste, usamos pytest para executar a função de teste. Na linha 17 em vez de escrever uma chamada para a função main, como fazemos em arquivos de programa, escrevemos uma chamada para a função pytest.main. No CSE 111, na parte inferior de todos os arquivos de teste, escreveremos uma chamada para pytest.main, exatamente como mostrado na linha 17. Esta chamada para pytest.main fará com que o módulo pytest execute todas as funções de teste no arquivo test_clima.py Quando o pytest executa as funções de teste, ele produz um resultado que nos informa se os testes foram aprovados ou reprovados, dessa forma:
> python test_clima.py =================================== test session starts =================================== plataforma win32--Python 3.13.4, pytest-8.4.1, pluggy-1.6.0 cachedir: .pytest_cache rootdir: C:\Users\CSE111\week3 collected 1 item test_clima.py:: test_fahr_para_celsius PASSED [100%] ==================================== 1 passed in 0.10s ====================================
Conforme mostrado acima, o pytest executa a função test_fahr_para_celsius, que chama a função fahr_para_celsius quatro vezes e verifica se fahr_para_celsius retorna o valor correto a cada vez. Podemos ver da saída do pytest, "PASSED [100%]" e "1 passed", que a função fahr_para_celsius retornou o resultado esperado (correto) quatro vezes.
Separando o Código do Programa e o Código de Teste
Em CSE 111, escreveremos as funções de teste em um programa separado das funções do programa. É uma boa ideia separar as funções de teste e as funções do programa porque essa separação facilita o lançamento de um programa para os usuários sem liberar as funções de teste para eles. Em geral, os usuários de um programa não querem as funções de teste. Uma consequência de escrever funções do programa e funções de teste em arquivos separados é que devemos adicionar uma instrução de importação no topo do arquivo de teste que importa todas as funções do programa que serão testadas.
A linha 2 de test_clima.py acima é um exemplo de uma instrução import que importa funções de um arquivo de programa. A linha 3 corresponde a este modelo:
from file_name import function_1, function_2, … function_N
Quando o computador importa funções de um arquivo, ele executa imediatamente todas as instruções que não estão escritas dentro de uma função. Isso inclui a instrução para chamar a função main:
# Inicia este programa
# chamando a função main.
main()
Isto significa que quando executamos nossas funções de teste, o computador importará nossas funções do programa e ao mesmo tempo, executará a chamada para main(), que iniciará a execução do programa. Entretanto, não queremos que o computador execute o programa enquanto executa as funções de teste, então temos um problema. Como podemos fazer com que o computador importe as funções do programa sem executar a função main? Felizmente, os desenvolvedores do Python nos deram uma solução para esse problema. Em vez de escrever o seguinte código para iniciar a execução do nosso programa:
# Inicia este programa
# chamando a função main.
main()
Escrevemos uma instrução if acima da chamada para main() dessa forma:
# Se este arquivo for executado dessa forma:
# > python programa.py
# então chama a função main. No entanto, se este arquivo for simplesmente
# importado (por exemplo, para um arquivo de teste), então pula a chamada para main.
if __name__ == "__main__":
main()
Escrever a instrução if acima da chamada para main() é a maneira correta de escrever o código para iniciar um programa. A linguagem de programação Python garante que quando o computador importa as funções do programa (para testá-las), a comparação na instrução if será falsa, então o computador vai pular a chamada para main(). Em outro momento, quando o computador executa o programa (não as funções de teste), a comparação na instrução if será verdadeira, o que fará com que o computador chame a função main e inicie o programa.
Quais Funções de Programa Deveríamos Testar?
Como programadores responsáveis, queremos garantir que todas as funções do nosso programa funcionem corretamente. Idealmente, devemos escrever pelo menos um teste para cada função.
No entanto, isso nem sempre é prático. As funções mais fáceis de testar são aquelas que:
- Recebem parâmetros;
- Retornam um valor.
Já as funções mais difíceis de testar são aquelas que:
- Solicitam entrada do usuário;
- Imprimem resultados no terminal;
- Desenham elementos em uma interface gráfica.
Nas próximas oito aulas do CSE 111, escreveremos testes apenas para funções que não interagem diretamente com o usuário nem imprimem no terminal — ou seja, as que são fáceis de testar.
Por esse motivo, não escreveremos testes para a função main, pois ela geralmente:
- Recebe entrada do usuário;
- Exibe informações no terminal.
Vídeo
Assista ao vídeo a seguir, que mostra a criação de duas funções de teste e uso do pytest para executá-las.
Documentação
A documentação oficial online (conteúdo em inglês) do pytest contém muito mais informações sobre o uso do pytest.
Resumo
Durante esta aula, você aprenderá a escrever funções de teste que verificam automaticamente se as funções do programa estão funcionando corretamente. No CSE 111, você escreverá funções de teste em um arquivo Python separado do seu arquivo de programa. No topo do arquivo de teste, você importará as funções do programa. Então você escreverá uma função de teste para cada função de programa, exceto o main. Em uma função de teste, você escreverá instruções assert que comparam o valor retornado de uma função de programa com o valor esperado. Você usará um módulo Python padrão chamado pytest para executar suas funções de teste. Quando um teste falha, você usará a saída do pytest para ajudar a encontrar e corrigir os erros no seu código.
Atividade
Objetivo
Melhorar sua capacidade de verificar a exatidão das funções escrevendo uma função de teste e executando-a com pytest.
Tarefa
Escreva uma função de teste para uma função criada anteriormente e execute os testes utilizando pytest.
Documentação Útil
-
pipé um módulo Python padrão que você pode usar para baixar e instalar módulos de terceiros. Durante a atividade desta aula, você usará opippara baixar e instalar opytest, de forma que você poderá usar opytestno seu código de teste. -
O vídeo Instalando Pacotes Python
pipmostra um instrutor usando opippara instalar outros módulos Python.
Etapas
Faça o seguinte:
-
Abra um novo janela de terminal no VS Code fazendo o seguinte:
-
Abra o VS Code.
-
Na barra de menu do VS Code, clique em "Terminal"
-
No menu, clique em "Novo Terminal"
Isso abrirá uma janela do terminal na parte inferior da janela do VS Code. Um terminal é uma janela ou quadro onde um usuário pode digitar e executar comandos.
-
-
Copie e cole o seguinte comando no janela do terminal e execute-o pressionando a tecla Enter. Este comando atualizará o
pipe várias outras partes dos módulos de instalação do Python para que opipfuncione corretamente.-
Usuários do MacOS:
python3 -m pip install --user --upgrade pip setuptools wheel
-
Usuários do Windows:
python -m pip install --user --upgrade pip setuptools wheel
- Se o seu computador estiver executando o sistema operacional Windows e o comando acima não funcionar, tente o comando
pyem vez do comandopythondessa forma:
py -m pip install --user --upgrade pip setuptools wheel
- Se o seu computador estiver executando o sistema operacional Windows e o comando acima não funcionar, tente o comando
-
-
Instale o módulo
pytestcopiando, colando e executando o seguinte comando na janela do terminal.-
Usuários do MacOS:
python3 -m pip install --user pytest
-
Usuários do Windows:
python -m pip install --user pytest
- Se o seu computador estiver executando o sistema operacional Windows e o comando acima não funcionar, tente o comando
pyem vez do comandopythondessa forma:
py -m pip install --user pytest
- Se o seu computador estiver executando o sistema operacional Windows e o comando acima não funcionar, tente o comando
-
-
Baixe estes dois arquivos Python: palavras.py e test_palavras.py e salve-os na mesma pasta.
-
Abra o arquivo
palavras.pybaixado no VS Code. Observe que o arquivopalavras.pycontém duas pequenas funções chamadasprefixoesufixo. Observe também que cada função tem uma string de documentação (uma string entre aspas triplas imediatamente abaixo do cabeçalho de função) que descreve o que a função faz. Leia as strings de documentação para ambas as funções. -
Abra o arquivo
test_palavras.pybaixado no VS Code. Emtest_palavras.pyexamine a funçãotest_prefixo. Observe que ela não aceita parâmetros e contém nove instruçõesassert. Cada instrução assert chama a funçãoprefixoe então compara o valor retornado da funçãoprefixocom o valor esperado. -
Em
test_palavras.pyescreva uma função chamadatest_sufixoque seja semelhante à funçãotest_prefixo. A funçãotest_sufixonão deve receber parâmetros e deve conter nove instruçõesassertque chamam a função´sufixocom estes parâmetros:Argumentos Valor de
Retorno
Esperados1 s2 "" "" "" "" "correto" "" "imparcial" "" "" "prever" "" "" "cansado" "fatigado" "ado" "nadando" "voando" "ando" "trator" "redutor" "tor" "animal" "pedestal" "al" "respeitoso" "precioso" "oso" -
Salve seu arquivo
test_palavras.pye execute-o clicando no ícone verde de execução no VS Code.
Procedimento de Teste
Verifique se seu programa de teste funciona corretamente seguindo cada etapa deste procedimento:
-
Execute seu programa de teste e certifique-se de que a saída do programa de teste seja semelhante à saída da execução do exemplo abaixo.
> python test_palavras.py =================================== test session starts =================================== platform win32--Python 3.13.4, pytest-8.4.1, pluggy-1.6.0 -- : C:\Users\CSE111\week03 cachedir: .pytest_cache rootdir: C:\CSE111\week03 collected 2 items test_palavras.py:: test_prefixo PASSED [ 50%] test_palavras.py:: test_sufixo PASSED [100%] ==================================== 2 passed in 0.23s ====================================
Exemplo de Solução
Quando terminar seu programa, consulte o exemplo de solução para comparar com o seu.
Primeiro, procure concluir o programa sem olhar o exemplo de solução. No entanto, se já tiver trabalhado nele por bastante tempo e ainda estiver com dificuldades, sinta-se à vontade para usá-lo como apoio para finalizar seu programa.
Gráfico de Chamadas
O gráfico de chamadas a seguir mostra as chamadas e retornos das funções no exemplo de solução desta tarefa. Neste gráfico, vemos que o computador começa a executar as funções de teste de exemplo chamando a função pytest.main. Ao executar a função pytest.main, o computador chama a função test_prefixo. Ao executar a função test_prefixo, o computador chama a função prefixo. Em seguida, ainda executando a função pytest.main, o computador chama a função test_sufixo. Ao executar a função test_sufixo, o computador chama a função sufixo.
Ponderar
Durante esta tarefa, você baixou um arquivo Python que contém duas funções do programa chamadas prefixo e sufixo. Você escreveu uma função de teste chamada test_sufixo, semelhante à função test_prefixo que foi fornecida a você. Você usou o pytest para executar ambas as funções de teste e examinou a saída do pytest para verificar se as funções de teste foram aprovadas. Como as funções de teste chamaram prefixo e sufixo com muitos argumentos diferentes e verificaram (usando assert) que os valores retornados de prefixo e sufixo estavam corretos, podemos assumir que as funções prefixo e sufixo funcionam corretamente. Você acha que escrever e executar funções de teste ajudará você a escrever programas melhores?
Links Úteis:
- Voltar para: Visão Geral da Semana | Página Inicial