Unidade 8 · a prova
A prova do pão
A receita da unidade 4 precisa aguentar o turno. assert, AssertionError, pytest — sem esperar o caixa apertar o programa por você.
input não é método. Um arquivo test_*.py afirma o que a função deve devolver. pytest escreve o try, o except e o relatório. Função testável tem entrada e return claros — não só print.
Não só na mão, não só o outro
Até agora o teste era: rodar, digitar, olhar a tela, de novo. Ou esperar alguém (um curso, um colega) quebrar o seu arquivo. Na vida — projeto próprio ou trabalho — o hábito é outro: além do código que resolve o problema, um pouco de código que afirma que a solução está certa. Automático. Repetível. Sem você na frente do teclado a cada vez.
Não dá para testar infinitos números: o programa nunca pararia. Dá para escolher representantes — o óbvio, o zero, o negativo — e deixar o computador repetir isso sem cansaço.
A mesa quadrada, de novo
Unidade 4: área da mesa quadrada. Lado vezes lado. Arquivo mesa.py. Um main pergunta o lado, chama area, imprime. A função é duas linhas.
def main():
lado = int(input("lado? "))
print("área:", area(lado))
def area(n):
return n * n
if __name__ == "__main__":
main()
Na mão: lado 2 → 4. Lado 3 → 9. Parece certo. Ainda não testou 0, negativo, nem o resto do infinito. E o if __name__ já está ali de propósito: na unidade 7, importar o arquivo não deve disparar o main. Vamos importar area noutro arquivo.
Um programa só para testar
Convenção: o arquivo se chama test_ + o que você testa. test_mesa.py. Função test_area — testar area. from mesa import area (o prefixo mesa.area cansa; unidade 7).
Primeira versão, sem palavra nova: se o resultado não for o que a matemática manda, imprima o erro. Nada se estiver certo. Silêncio = passou.
from mesa import area
def test_area():
if area(2) != 4:
print("2² não deu 4")
if area(3) != 9:
print("3² não deu 9")
def main():
test_area()
if __name__ == "__main__":
main()
Definir test_area não a executa. Por isso o main, como sempre. python test_mesa.py: tela vazia. Bom sinal — os dois if não dispararam.
Um bug que um teste não vê
Quebre area de propósito: return n + n em vez de n * n. Rode o teste de novo. Só uma mensagem: 3² não deu 9. O caso 2 passou. Por quê?
area está com + no lugar de *. O teste do 2 passa e o do 3 falha. Por quê?
Dois testes já valem mais que um. Ainda assim: a função tem duas linhas; o arquivo de teste já tem mais. Se forem dez casos, ninguém vai querer escrever isso.
Afirmar, em uma palavra
assert é palavra da linguagem: afirme que isto é verdadeiro. Se for, silêncio. Se a expressão booleana for falsa, AssertionError — traceback, arquivo, número da linha. Menos amigável que o seu print; metade do código.
def test_area():
assert area(2) == 4
assert area(3) == 9
Com o bug do +, o traceback aponta assert area(3) == 9. Cabe a você inferir o porquê. Unidade 6: try / except pega erro. Dá para pegar AssertionError e imprimir “3² não deu 9”. Um passo à frente (mensagem clara) e um atrás (mais linhas de novo).
try:
assert area(2) == 4
except AssertionError:
print("2² não deu 4")
Copiar isso para 3, −2, −3 e 0: trinta linhas para duas de area. Positivos costumam se comportar igual entre si; negativos, igual entre si; zero pode ser estranho. Não é ciência exata — é instinto do que a função promete. Com o +, 2 e 0 passam (sorte outra vez); 3 e os negativos denunciam. Vários testes pegaram o erro. Ninguém escreve trinta linhas de try para sempre.
Uma linha assert (área, conta, o que for verdadeiro).
assert area(2) == 4Deixe a ferramenta escrever o resto
Teste de unidade: testar um pedaço — em geral, uma função. Python traz um framework na caixa; pytest é de terceiro, instala com pip install pytest (unidade 7), e costuma ser mais curto. Convenção: ele acha test_*.py e def test_..., chama sozinho, pega o AssertionError, formata o relatório. Você não escreve main, nem if __name__, nem try, nem print no arquivo de teste.
from mesa import area
def test_area():
assert area(2) == 4
assert area(3) == 9
assert area(-2) == 4
assert area(-3) == 9
assert area(0) == 0
Oito linhas. Rode pytest test_mesa.py — não python. Com o bug: um F vermelho (fail). No bloco FAILURES, a primeira afirmação do 2 não tem E embaixo; a do 3 tem. O relatório mostra algo como 6 == 9: area(3) devolveu 6. Chapéu de detetive: na linha do return, mais em vez de vezes. Corrija para *, rode de novo: um ponto verde. Cem por cento destes testes — não prova que o código é perfeito; prova que passou o que você pediu. Falta o 1000, a string, o que o chefe vai digitar. A obrigação de escolher os casos é sua.
O pytest encontra testes pelo quê?
O que é testável
E se no input alguém digitar bolo em vez de um inteiro? int("bolo") é ValueError (unidade 6). Isso está no main. Estes testes não exercitam o main — e isso é de propósito. area tem parâmetro e return: você controla o que entra. Por isso fatie o programa em funções pequenas. Tudo no main, ou tudo solto no arquivo, é difícil de cravar num assert.
Dá para afirmar que area("bolo") levanta TypeError (não dá para fazer "bolo" * "bolo"). Não é assert area("bolo") == …. É outra função da biblioteca pytest.
import pytest
from mesa import area
def test_str():
with pytest.raises(TypeError):
area("bolo")
Como o teste diz “eu espero um TypeError se passarem texto para area”?
Um assert que para os outros
Cinco asserts na mesma test_area: o primeiro que falha (o 3, com o bug) aborta a função. Os negativos nem rodam. Uma falha já prova que está quebrado — mas várias pistas de uma vez aceleram o diagnóstico. Separe: test_positivo, test_negativo, test_zero. pytest chama as três mesmo se uma falhar. Relatório: dois F e um ponto; ou F.F na linha curta. Zero passou (0+0), positivo falhou no 3, negativo falhou no −2. A conta aponta para o +.
Pode usar for e lista dentro do teste. Continua sendo um teste para o pytest. O ideal é teste pequeno o bastante para um humano olhar e dizer: isso está certo. Senão você vai precisar de teste do teste.
Por que partir os asserts em test_positivo, test_negativo e test_zero?
Pelo menos um assert … == … (pode cobrir 2 e 0, ou 3 e −3).
assert 2 * 2 == 4assert 0 * 0 == 0O recado que não devolve
Unidade 7: recados.py, função ola. Se ela só faz print(f"olá, {nome}"), não tem return. assert ola("Ana") == "olá, Ana" compara com None — o print é efeito na tela, como na unidade 1. Por isso, quando o programa cresce, o melhor é a função devolver a frase e o main imprimir.
def ola(para="fila"):
return f"olá, {para}"
def main():
print(ola(input("nome? ").strip()))
if __name__ == "__main__":
main()
# test_recados.py
from recados import ola
def test_padrao():
assert ola() == "olá, fila"
def test_argumento():
assert ola("Ana") == "olá, Ana"
Duas funções: padrão e argumento. Se uma quebrar, o relatório diz qual. Loop com nomes da fila (Ana, Bia, Leo) cabe dentro de um teste — desde que continue óbvio.
Qual função é mais fácil de testar com assert … == …?
Uma pasta de testes
Poucos testes: um arquivo basta. Muitos: uma pasta, por exemplo testes/, com vários test_*.py. pytest aceita pytest testes e procura dentro. Para o Python tratar a pasta como pacote (módulo numa pasta, unidade 7), crie lá um __init__.py mesmo vazio. É o sinal. Aí o import from mesa import area continua fazendo sentido conforme a árvore de pastas do projeto.
O ganho real, em equipe: alguém muda mesa.py; os testes que você deixou gritam se a área da mesa deixou de ser lado vezes lado. Não apague o teste para “ficar verde”.
Para o pytest rodar uma pasta de testes, o que aquele __init__.py vazio faz?
Oficina · test_area
test_... com assert sobre a área da mesa (lado * lado) ou sobre uma conta equivalente. No computador: pip install pytest se faltar, e pytest test_mesa.py. Aqui, cole o teste.
Cole def test_... com assert.
def test_area():
assert 2 * 2 == 4
assert 3 * 3 == 9