(2014-10-27) [seti-ufla-mg] testando com py.test e tox

18
Testando com py.test e tox – Danilo J. S. Bellini Testando com py.test e tox – Danilo J. S. Bellini @danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG @danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG Testando com py.test e tox Testando com py.test e tox Danilo de Jesus da Silva Bellini Danilo de Jesus da Silva Bellini Twitter: Twitter: @danilobellini @danilobellini http://pytest.org/ http://pytest.org/ https://tox.readthedocs.org/ https://tox.readthedocs.org/ https://github.com/schlamar/pytest-cov https://github.com/schlamar/pytest-cov http://nedbatchelder.com/code/coverage http://nedbatchelder.com/code/coverage

Upload: danilo-bellini

Post on 09-Jul-2015

105 views

Category:

Technology


1 download

DESCRIPTION

Minicurso/tutorial sobre o py.test e o tox apresentado na UFLA (Universidade Federal de Lavras), Minas Gerais, no dia 2014-10-27, durante a SETI (Semana de Tecnologia da Informação). http://seti.compjunior.com.br/ Descrição do tutorial: Tutorial para o aprendizado sobre o uso prático do pacote py.test para realização de testes de diversos tipos.

TRANSCRIPT

Page 1: (2014-10-27) [SETI-UFLA-MG] Testando com py.test e tox

Testando com py.test e tox – Danilo J. S. BelliniTestando com py.test e tox – Danilo J. S. Bellini@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG

Testando com py.test e toxTestando com py.test e tox

Danilo de Jesus da Silva BelliniDanilo de Jesus da Silva BelliniTwitter:Twitter: @danilobellini @danilobellini

http://pytest.org/http://pytest.org/https://tox.readthedocs.org/https://tox.readthedocs.org/

https://github.com/schlamar/pytest-covhttps://github.com/schlamar/pytest-covhttp://nedbatchelder.com/code/coveragehttp://nedbatchelder.com/code/coverage

Page 2: (2014-10-27) [SETI-UFLA-MG] Testando com py.test e tox

Testando com py.test e tox – Danilo J. S. BelliniTestando com py.test e tox – Danilo J. S. Bellini@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG

Aspectos geraisAspectos gerais

● Ambos criados por Holger KrekelAmbos criados por Holger Krekel– Originalmente, o py.test era parte de uma coletânea “py”Originalmente, o py.test era parte de uma coletânea “py”

● Objetivo centralObjetivo central– Automatizar (e padronizar) testesAutomatizar (e padronizar) testes

● Testes comTestes com– CPython 2.x (2.6+) e 3.xCPython 2.x (2.6+) e 3.x

– PyPyPyPy

– Pode ser usado para testar códigos em outras linguagensPode ser usado para testar códigos em outras linguagens

Page 3: (2014-10-27) [SETI-UFLA-MG] Testando com py.test e tox

Testando com py.test e tox – Danilo J. S. BelliniTestando com py.test e tox – Danilo J. S. Bellini@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG

Criação do ambiente e instalaçãoCriação do ambiente e instalação

● Admitindo o virtualenv instalado e atualizado (além do pip), crie Admitindo o virtualenv instalado e atualizado (além do pip), crie um diretório “pytut” com o comando:um diretório “pytut” com o comando:

Este será o ambiente para a realização dos testes, além da Este será o ambiente para a realização dos testes, além da instalação do py.test e do tox.instalação do py.test e do tox.

● Ative o ambiente (as linhas ganharão um prefixo):Ative o ambiente (as linhas ganharão um prefixo):

● Instale o py.test, o pytest-cov e o tox:Instale o py.test, o pytest-cov e o tox:

● Para desativar:Para desativar:

$ virtualenv pytut$ virtualenv pytut

$ . pytut/bin/activate$ . pytut/bin/activate

$ deactivate$ deactivate

$ pip install tox pytest pytest-cov$ pip install tox pytest pytest-cov

Page 4: (2014-10-27) [SETI-UFLA-MG] Testando com py.test e tox

Testando com py.test e tox – Danilo J. S. BelliniTestando com py.test e tox – Danilo J. S. Bellini@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG

Primeiros passosPrimeiros passos

● Arquivo Arquivo test_inc.pytest_inc.py::

● Rodar o teste:Rodar o teste:

● O teste passa?O teste passa?– Corrija o códigoCorrija o código

def incrementa(x): return x - 1

def test_incrementa_3(): assert incrementa(3) == 4

def incrementa(x): return x - 1

def test_incrementa_3(): assert incrementa(3) == 4

$ py.test$ py.test

01_02

01_01

Nota: os números à direita dos slides se referem a uma

sugestão de organização das etapas do tutorial

em diferentes diretórios ou

commits

Page 5: (2014-10-27) [SETI-UFLA-MG] Testando com py.test e tox

Testando com py.test e tox – Danilo J. S. BelliniTestando com py.test e tox – Danilo J. S. Bellini@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG

Collection timeCollection timeComo o py.test encontra os testes?Como o py.test encontra os testes?

● Convenção de nomes!Convenção de nomes!– Prefixos “test_” nos arquivos e funções/métodosPrefixos “test_” nos arquivos e funções/métodos

– Prefixo Test nos nomes das classesPrefixo Test nos nomes das classes

● Tente o exemplo anterior novamente, mas mantendo Tente o exemplo anterior novamente, mas mantendo o arquivo com o nome o arquivo com o nome incrementa.pyincrementa.py– O que acontece?O que acontece?

– E chamando com o nome do arquivo como parâmetro?E chamando com o nome do arquivo como parâmetro?$ py.test incrementa.py$ py.test incrementa.py 01_03

Para organização estrutural em diretórios e outras sugestões/convenções, veja

http://pytest.org/latest/goodpractises.html

Page 6: (2014-10-27) [SETI-UFLA-MG] Testando com py.test e tox

Testando com py.test e tox – Danilo J. S. BelliniTestando com py.test e tox – Danilo J. S. Bellini@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG

FatorialFatorial

● Com a função fatorial em um arquivo Com a função fatorial em um arquivo fat.pyfat.py ... ...

… … faça rotinas de testes (funções) para pelo menos faça rotinas de testes (funções) para pelo menos 7 diferentes entradas válidas em um arquivo 7 diferentes entradas válidas em um arquivo test_fat.pytest_fat.py

● Rodar com:Rodar com:02_01

def fatorial(x): return 1 if n <= 1 else n * fatorial(n - 1)def fatorial(x): return 1 if n <= 1 else n * fatorial(n - 1)

from fat import fatorial

# Continuar ...

from fat import fatorial

# Continuar ...

$ py.test$ py.test

Page 7: (2014-10-27) [SETI-UFLA-MG] Testando com py.test e tox

Testando com py.test e tox – Danilo J. S. BelliniTestando com py.test e tox – Danilo J. S. Bellini@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG

Quantidade de testes e testes Quantidade de testes e testes parametrizadosparametrizados

● Implemente uma versão alternativa ao Implemente uma versão alternativa ao test_fat.pytest_fat.py utilizando apenas um único teste com vários “assert”utilizando apenas um único teste com vários “assert”– O que acontece com a contagem dos testes?O que acontece com a contagem dos testes?

● Implemente o Implemente o test_fat.pytest_fat.py usando: usando:

02_03

02_02

import pytestfrom fat import fatorial

schema = "n", "out"table = [ # Pares (entrada "n", saída "out") (0, 1), # ... complete com os demais pares]

@pytest.mark.parametrize(schema, table)def test_mapeia_entrada_saida(n, out): assert fatorial(n) == out

import pytestfrom fat import fatorial

schema = "n", "out"table = [ # Pares (entrada "n", saída "out") (0, 1), # ... complete com os demais pares]

@pytest.mark.parametrize(schema, table)def test_mapeia_entrada_saida(n, out): assert fatorial(n) == out

Page 8: (2014-10-27) [SETI-UFLA-MG] Testando com py.test e tox

Testando com py.test e tox – Danilo J. S. BelliniTestando com py.test e tox – Danilo J. S. Bellini@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG

Pequenas dicas sobre o uso de testes Pequenas dicas sobre o uso de testes parametrizadosparametrizados

● Iteráveis em geral como tabela de dados, e.g.Iteráveis em geral como tabela de dados, e.g.

● Na presença de muitas rotinas de testes sendo Na presença de muitas rotinas de testes sendo parametrizadas, pode-se fazer …parametrizadas, pode-se fazer …

… … para definir o decorator “@p()” indicando teste para definir o decorator “@p()” indicando teste parametrizadoparametrizado

● Schema de um único valor não precisa ser uma tuplaSchema de um único valor não precisa ser uma tupla● As mesmas tabelas podem ser utilizadas em diferentes As mesmas tabelas podem ser utilizadas em diferentes

testes (e.g. uso no pattern strategy, múltiplas testes (e.g. uso no pattern strategy, múltiplas implementações de uma mesma tarefa)implementações de uma mesma tarefa)

table = enumerate([1, 1, 2, 6, 24, 120, 720])table = enumerate([1, 1, 2, 6, 24, 120, 720]) 02_04

p = pytest.mark.parametrizep = pytest.mark.parametrize

Page 9: (2014-10-27) [SETI-UFLA-MG] Testando com py.test e tox

Testando com py.test e tox – Danilo J. S. BelliniTestando com py.test e tox – Danilo J. S. Bellini@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG

ExceçãoExceção

● Modifique a função fatorial para lançar uma exceção Modifique a função fatorial para lançar uma exceção ValueError quando a entrada for negativa.ValueError quando a entrada for negativa.

● Como testar isso?Como testar isso?– Gerenciador de contexto!Gerenciador de contexto!

– Crie pelo menos dois testes contendo esse gerenciador de Crie pelo menos dois testes contendo esse gerenciador de contexto, um no qual a exceção ocorre e outro no qual a contexto, um no qual a exceção ocorre e outro no qual a exceção não ocorre.exceção não ocorre.

● Algum teste falhou?Algum teste falhou?

if n < 0: raise ValueError("Use apenas inteiros positivos")if n < 0: raise ValueError("Use apenas inteiros positivos")

with pytest.raises(ValueError): # Fazer algo que deveria lançar um ValueErrorwith pytest.raises(ValueError): # Fazer algo que deveria lançar um ValueError

02_05

Page 10: (2014-10-27) [SETI-UFLA-MG] Testando com py.test e tox

Testando com py.test e tox – Danilo J. S. BelliniTestando com py.test e tox – Danilo J. S. Bellini@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG

Podemos usar um “xfail” (expected to fail) no primeiro teste de exceção

realizado que sabíamos que falharia (um dos 2 testes do 02_05) inserindo o seguinte decorator na

rotina de testes:

Skip/xfailSkip/xfail

● Há testes que podem fazer sentido em somente um Há testes que podem fazer sentido em somente um dos ambientes (e.g. teste que depende do dos ambientes (e.g. teste que depende do itertools.accumulate do Python 3).itertools.accumulate do Python 3).

● CondicionalCondicional– pytest.mark.skipifpytest.mark.skipif

– pytest.mark.xfailpytest.mark.xfail

● Imperativo (comandos)Imperativo (comandos)– pytest.skippytest.skip

[email protected]@pytest.mark.xfail

Extra!

Page 11: (2014-10-27) [SETI-UFLA-MG] Testando com py.test e tox

Testando com py.test e tox – Danilo J. S. BelliniTestando com py.test e tox – Danilo J. S. Bellini@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG

Testando a mensagem de exceçãoTestando a mensagem de exceção● O bloco with apenas testa se a exceção ocorreu. O bloco with apenas testa se a exceção ocorreu.

Possivelmente a exceção poderia ter ocorrido com uma Possivelmente a exceção poderia ter ocorrido com uma mensagem diferente da desejada (e.g. por existir mais de mensagem diferente da desejada (e.g. por existir mais de uma forma para o valor de entrada estar fora do domínio de uma forma para o valor de entrada estar fora do domínio de aplicabilidade da função)aplicabilidade da função)

● Faça um teste usando a entrada “2j” (o número imaginário 2), Faça um teste usando a entrada “2j” (o número imaginário 2), a qual lança um TypeError. Verifique se a mensagem contém a qual lança um TypeError. Verifique se a mensagem contém a palavra “complex”.a palavra “complex”.

02_07

with pytest.raises(TypeError): try: # Bloco que deveria lançar a exceção except TypeError as exc: assert "complex" in str(exc) raise # Propaga o TypeError

with pytest.raises(TypeError): try: # Bloco que deveria lançar a exceção except TypeError as exc: assert "complex" in str(exc) raise # Propaga o TypeError 02_06

with pytest.raises(TypeError) as excinfo: # Bloco que deveria lançar a exceçãoassert "complex" in str(excinfo.value)

with pytest.raises(TypeError) as excinfo: # Bloco que deveria lançar a exceçãoassert "complex" in str(excinfo.value)

… … e usando o py.code.ExceptionInfo() ...e usando o py.code.ExceptionInfo() ...

Dica: Troque o nome “complex” por outro que não existe na

string, a fim de “testar o teste” e assegurar de que ele realmente está verificando essa

informação.

Page 12: (2014-10-27) [SETI-UFLA-MG] Testando com py.test e tox

Testando com py.test e tox – Danilo J. S. BelliniTestando com py.test e tox – Danilo J. S. Bellini@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG

Doctests (testes de documentação)Doctests (testes de documentação)● Compara textoCompara texto● Implemente a Implemente a

função ao lado em função ao lado em um arquivo um arquivo nota.pynota.py

● Chame o py.test Chame o py.test com:com:

● Coloque um teste Coloque um teste de documentação de documentação com uma string com uma string como entradacomo entrada

def nome_nota(pitch): """ Encontra o nome da nota dado o pitch MIDI (dado em semitons) caso esteja na pentatonica de Am.

>>> nome_nota(69) # A4 (La central) 'A'

>>> nome_nota(70) # Bb4 Traceback (most recent call last): ... ValueError: Fora da escala! """ ans = [] if pitch % 12 == 0: return "C" if pitch % 12 == 2: return "D" if pitch % 12 == 4: return "E" if pitch % 12 == 7: return "G" if pitch % 12 == 9: return "A" raise ValueError("Fora da escala!")

def nome_nota(pitch): """ Encontra o nome da nota dado o pitch MIDI (dado em semitons) caso esteja na pentatonica de Am.

>>> nome_nota(69) # A4 (La central) 'A'

>>> nome_nota(70) # Bb4 Traceback (most recent call last): ... ValueError: Fora da escala! """ ans = [] if pitch % 12 == 0: return "C" if pitch % 12 == 2: return "D" if pitch % 12 == 4: return "E" if pitch % 12 == 7: return "G" if pitch % 12 == 9: return "A" raise ValueError("Fora da escala!")

03_01

$ py.test --doctest-modules$ py.test --doctest-modules

… (Ellipsis):Parte do doctest para “casar com

o que vier”

03_02

Page 13: (2014-10-27) [SETI-UFLA-MG] Testando com py.test e tox

Testando com py.test e tox – Danilo J. S. BelliniTestando com py.test e tox – Danilo J. S. Bellini@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG

Cobertura de códigoCobertura de código● Crie um tox.ini contendoCrie um tox.ini contendo

Agora não é mais necessário digitar parâmetros ao chamar o Agora não é mais necessário digitar parâmetros ao chamar o py.test (exceto parâmetros complementares)py.test (exceto parâmetros complementares)

● A linha “cov-config” serve para unificar o tox.ini como arquivo A linha “cov-config” serve para unificar o tox.ini como arquivo único de configuração do tox (ainda não utilizado), py.test e único de configuração do tox (ainda não utilizado), py.test e pytest-cov. Para tornar tal linha útil, avalie a cobertura de pytest-cov. Para tornar tal linha útil, avalie a cobertura de código com branching colocando isto também no tox.ini código com branching colocando isto também no tox.ini (configura o pytest-cov):(configura o pytest-cov):

● Conseguem completar doctests para chegar a 100% de Conseguem completar doctests para chegar a 100% de cobertura?cobertura? 03_03

[pytest]addopts = --doctest-modules --cov-config tox.ini --cov-report term-missing --cov nota

[pytest]addopts = --doctest-modules --cov-config tox.ini --cov-report term-missing --cov nota

[run]branch = True[run]branch = True

Tente também com “html” no

lugar de “term-missing”

Page 14: (2014-10-27) [SETI-UFLA-MG] Testando com py.test e tox

Testando com py.test e tox – Danilo J. S. BelliniTestando com py.test e tox – Danilo J. S. Bellini@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG

Oráculos e testes aleatóriosOráculos e testes aleatórios

● Uso ao implementar um algoritmo com implementação de Uso ao implementar um algoritmo com implementação de referência pronta para pelo menos parte do domínio.referência pronta para pelo menos parte do domínio.– No caso mais simples, sem oráculos, testes aleatórios No caso mais simples, sem oráculos, testes aleatórios

representam a resistência a “falha de segmentação” ou coisas representam a resistência a “falha de segmentação” ou coisas similares, algo que não faremos neste minicurso.similares, algo que não faremos neste minicurso.

– Com oráculos, podemos simplesmente utilizar uma Com oráculos, podemos simplesmente utilizar uma implementação para testar outra, criando testes massivamente.implementação para testar outra, criando testes massivamente.

● O que ocorre quando se utiliza o decorator O que ocorre quando se utiliza o decorator pytest.mark.parametrize mais de uma vez?pytest.mark.parametrize mais de uma vez?

● Implemente um algoritmo (qualquer) para ordenação de Implemente um algoritmo (qualquer) para ordenação de listas utilizando o built-in “sorted” como oráculo para os listas utilizando o built-in “sorted” como oráculo para os testes em um único teste parametrizado.testes em um único teste parametrizado. 04_01

Extra!

Page 15: (2014-10-27) [SETI-UFLA-MG] Testando com py.test e tox

Testando com py.test e tox – Danilo J. S. BelliniTestando com py.test e tox – Danilo J. S. Bellini@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG

ToxTox● Gerencia múltiplos ambientes virtuais, para automatizar Gerencia múltiplos ambientes virtuais, para automatizar

os testes em todos os ambientesos testes em todos os ambientes● Exigências:Exigências:

– Um arquivo setup.py do projeto (instalação/configuração do Um arquivo setup.py do projeto (instalação/configuração do “package”)“package”)

– Um arquivo tox.ini (configuração do tox e py.test)Um arquivo tox.ini (configuração do tox e py.test)

● Vamos fazer algum dos “projetos” já criados funcionar Vamos fazer algum dos “projetos” já criados funcionar tanto no Python 2 como no Python 3 (exceto o que tanto no Python 2 como no Python 3 (exceto o que utiliza doctests).utiliza doctests).

from setuptools import setupsetup(name="pytut")from setuptools import setupsetup(name="pytut")

Este setup.py é MÍNIMO.O nome é usado pelo tox para

criar um egg

[tox]envlist = py27, py34

[testenv]deps = pytestcommands = py.test

[tox]envlist = py27, py34

[testenv]deps = pytestcommands = py.test

Exemplo de parte do tox.ini que configura o tox.

$ tox$ tox

0X_0Y

Page 16: (2014-10-27) [SETI-UFLA-MG] Testando com py.test e tox

Testando com py.test e tox – Danilo J. S. BelliniTestando com py.test e tox – Danilo J. S. Bellini@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG

Ponto flutuantePonto flutuante

● Comparar por aproximaçãoComparar por aproximação– Valor absoluto da diferençaValor absoluto da diferença

– Erro percentual/relativo (não-simétrico)Erro percentual/relativo (não-simétrico)

– Tolerância em número de bits de mantissaTolerância em número de bits de mantissa

● Há implementações prontasHá implementações prontas– numpy.isclose, numpy.allclosenumpy.isclose, numpy.allclose

– audiolazy.almost_eqaudiolazy.almost_eq

– unittest.TestCase.assertAlmostEqualunittest.TestCase.assertAlmostEqual

● Uso de arredondamentos explícitosUso de arredondamentos explícitos

Extra!

Page 17: (2014-10-27) [SETI-UFLA-MG] Testando com py.test e tox

Testando com py.test e tox – Danilo J. S. BelliniTestando com py.test e tox – Danilo J. S. Bellini@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG

MiscelâneaMiscelânea

● Vejam a ajuda do py.test!Vejam a ajuda do py.test!– Todos os parâmetros são opcionais, mas quais os Todos os parâmetros são opcionais, mas quais os

parâmetros?parâmetros?

● É possível selecionar somente alguns testes para É possível selecionar somente alguns testes para serem rodados.serem rodados.– Separação por arquivo, trivialSeparação por arquivo, trivial

– Usem “-k” para selecionar uma parte da suíte de testes Usem “-k” para selecionar uma parte da suíte de testes (baseado nos nomes dos testes)(baseado nos nomes dos testes)

$ py.test --helpusage: py.test [options] [file_or_dir] [file_or_dir] [...]...

$ py.test --helpusage: py.test [options] [file_or_dir] [file_or_dir] [...]...

$ py.test --collect-only$ py.test --collect-only

Page 18: (2014-10-27) [SETI-UFLA-MG] Testando com py.test e tox

Testando com py.test e tox – Danilo J. S. BelliniTestando com py.test e tox – Danilo J. S. Bellini@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG@danilobellini – SETI / UFLA – 2014-10-27 – Lavras / MG

Perguntas

Perguntas

?FIM!FIM!

Obrigado!Obrigado!

http://pytest.org/http://pytest.org/httphttps://tox.readthedocs.org/s://tox.readthedocs.org/

https://github.chttps://github.com/schlamar/pytest-covom/schlamar/pytest-covhttp://nedbatchelhttp://nedbatchelder.com/code/coverageder.com/code/coverage