Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
Testando com py.test e toxTestando com py.test e tox
Danilo de Jesus da Silva BelliniTwitter: @danilobellini
http://pytest.org/https://tox.readthedocs.org/
https://github.com/schlamar/pytest-covhttp://nedbatchelder.com/code/coverage
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
Aspectos geraisAspectos gerais
● Breve históriaBreve história– Originalmente, parte do “py”Originalmente, parte do “py”
– Holger Krekel (criador)Holger Krekel (criador)
● Objetivo centralObjetivo central– Automatizar (e padronizar) testesAutomatizar (e padronizar) testes
● Testando...Testando...– CPython 2.x (2.6+) e 3.xCPython 2.x (2.6+) e 3.x
– PyPyPyPy
– Códigos em outras linguagensCódigos em outras linguagens
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
Ambiente virtual (opcional)Ambiente virtual (opcional)e instalaçãoe 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 (não-opcional):Instale o py.test, o pytest-cov e o tox (não-opcional):
● 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
Ou no diretório de instalação
caso não tenha criado em
$HOME/pytut
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
Primeiros passosPrimeiros passos
● Arquivo Arquivo test_mul.pytest_mul.py::
● Rodar o teste:Rodar o teste:
● O teste passa?O teste passa?– Corrija o códigoCorrija o código
def multiplica(a, b): return a + b
def test_multiplica_7_8(): assert multiplica(7, 8) == 56
def multiplica(a, b): return a + b
def test_multiplica_7_8(): assert multiplica(7, 8) == 56
$ py.test$ py.test
01_02
01_01
Os números à direita dos slides se referem a uma
sugestão ade organização das etapas do tutorial
em diferentes diretórios ou
commits
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
Como 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 multiplica.pymultiplica.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 multiplica.py$ py.test multiplica.py 01_03
Para organização estrutural em diretórios e outras sugestões/convenções, veja
http://pytest.org/latest/goodpractises.html
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
FibonacciFibonacci
● Com a função fibonacci em um arquivo Com a função fibonacci em um arquivo fib.pyfib.py ... ...
… … faça rotinas de testes (funções) para pelo menos faça rotinas de testes (funções) para pelo menos 8 diferentes entradas válidas em um arquivo 8 diferentes entradas válidas em um arquivo test_fib.pytest_fib.py
● Rodar com:Rodar com:02_01
def fibonacci(x): return n if n <= 1 else fibonacci(n - 1) + fibonacci(n - 2)def fibonacci(x): return n if n <= 1 else fibonacci(n - 1) + fibonacci(n - 2)
from fib import fibonacci
# Continuar ...
from fib import fibonacci
# Continuar ...
$ py.test$ py.test
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
Testes parametrizadosTestes parametrizados
● Implemente uma versão alternativa ao Implemente uma versão alternativa ao test_fib.pytest_fib.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_fib.pytest_fib.py usando: usando:
02_03
02_02
import pytestfrom fib import fibonacci
schema = "n", "out"table = [ # Pares (entrada "n", saída "out") (0, 0), (1, 1), # ... complete com os demais pares]
@pytest.mark.parametrize(schema, table)def test_mapeia_entrada_saida(n, out): assert fibonacci(n) == out
import pytestfrom fib import fibonacci
schema = "n", "out"table = [ # Pares (entrada "n", saída "out") (0, 0), (1, 1), # ... complete com os demais pares]
@pytest.mark.parametrize(schema, table)def test_mapeia_entrada_saida(n, out): assert fibonacci(n) == out
O que ocorre quando se
utiliza o decorator
pytest.mark.parametrize
mais de uma vez?
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
Usando mais de uma vez o decorator Usando mais de uma vez o decorator pytest.mark.parametrizepytest.mark.parametrize
Extra!
import pytest
p = pytest.mark.parametrize
@p("a", [0, 1, 2, 3])@p("b", [5, 7, 9, 12])def test_multiplica_parametrizado(a, b): assert multiplica(a, b) == a * b
import pytest
p = pytest.mark.parametrize
@p("a", [0, 1, 2, 3])@p("b", [5, 7, 9, 12])def test_multiplica_parametrizado(a, b): assert multiplica(a, b) == a * b
● Produto cartesiano!Produto cartesiano!● Possibilita mais testes do que linhas de código para Possibilita mais testes do que linhas de código para
estesestes● Insira este “teste” para o “projeto” da multiplicaçãoInsira este “teste” para o “projeto” da multiplicação
– Na prática, testes não são uma mera repetição do códigoNa prática, testes não são uma mera repetição do código
– Útil quando combinado com oráculosÚtil quando combinado com oráculos
01_04
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
DicasDicas
● Tabelas de dados devem ser iteráveis, não necessariamente Tabelas de dados devem ser iteráveis, não necessariamente listas, e.g.listas, e.g.
● Na presença de muitas rotinas de testes sendo parametrizadas, Na presença de muitas rotinas de testes sendo parametrizadas, pode-se fazer …pode-se fazer …
… … para definir o decorator “@p(...)” para fornecer parâmetros ao para definir o decorator “@p(...)” para fornecer parâmetros ao teste parametrizadoteste parametrizado
● 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 rotinas de As mesmas tabelas podem ser utilizadas em diferentes rotinas de
testes (e.g. testando múltiplas implementações de uma mesma testes (e.g. testando múltiplas implementações de uma mesma tarefa, pattern strategy)tarefa, pattern strategy)
table = enumerate([0, 1, 1, 2, 3, 5, 8, 13])table = enumerate([0, 1, 1, 2, 3, 5, 8, 13]) 02_04
p = pytest.mark.parametrizep = pytest.mark.parametrize
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
ExceçãoExceção● Modifique a função fibonacci para lançar uma exceção Modifique a função fibonacci para lançar uma exceção
ValueError quando a entrada for negativa.ValueError quando a entrada for negativa.
● Mas como testar?Mas como testar?– Gerenciadores de contexto!Gerenciadores 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 deveria ocorrer (entrada contexto, um no qual a exceção deveria ocorrer (entrada negativa) e outro no qual não deveria ocorrer exceção.negativa) e outro no qual não deveria ocorrer exceção.
● Algum teste falhou? Coloque este decorator nele:Algum teste falhou? Coloque este decorator nele:
if n < 0: raise ValueError("Use apenas inteiros positivos")if n < 0: raise ValueError("Use apenas inteiros positivos")
with pytest.raises(ValueError): # Algo que deveria lançar um ValueErrorwith pytest.raises(ValueError): # Algo que deveria lançar um ValueError
[email protected]@pytest.mark.xfail“xfail” = expected to fail
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
Testando a mensagem de exceçãoTestando a mensagem de exceção● O bloco with apenas testa se a exceção ocorreu. Mas a O bloco with apenas testa se a exceção ocorreu. Mas a
exceção poderia ter ocorrido com uma mensagem diferente exceção poderia ter ocorrido com uma mensagem diferente da desejada (e.g. mais de uma forma para o valor de entrada da desejada (e.g. mais de uma forma para o valor de entrada estar fora do domínio de aplicabilidade da função)estar fora do domínio de 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”.
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
OráculosOráculos● Implementação de referência pronta Implementação de referência pronta
para pelo menos parte do domínio.para pelo menos parte do domínio.● Utilizar uma implementação (completa Utilizar uma implementação (completa
ou parcial) para testar outraou parcial) para testar outra– Possibilidade de criação massiva de testes.Possibilidade de criação massiva de testes.
● Teste esta implementação alternativa Teste esta implementação alternativa para 30 entradas diferentes (pequenas)para 30 entradas diferentes (pequenas)
02_08
phi = .5 + .5 * 5 ** .5 # Golden ratio!def fibonacci_closed_form(n): return int(round(phi ** n * 5 ** -.5))
phi = .5 + .5 * 5 ** .5 # Golden ratio!def fibonacci_closed_form(n): return int(round(phi ** n * 5 ** -.5))
Dica: Use nomes para os testes que permita a seleção com:$ pytest -k parte_do_nome
● Testes aleatóriosTestes aleatórios– Sem oráculos, testes aleatórios Sem oráculos, testes aleatórios
representam a resistência do representam a resistência do código a “falha de segmentação” código a “falha de segmentação” e coisas similares (não faremos e coisas similares (não faremos neste minicurso/tutorial).neste minicurso/tutorial).
– As entradas para uso com um As entradas para uso com um oráculo, dentro dos domínios oráculo, dentro dos domínios relevantes, podem ser geradas relevantes, podem ser geradas aleatoriamente.aleatoriamente.
– ReprodutibilidadeReprodutibilidade● Necessário?Necessário?● Seed fixo ou hard-coded?Seed fixo ou hard-coded?
Dica: Usar o decorator @audiolazy.cached (Python 2/3) ou
o @functools.lru_cache (apenas Python 3) na “fibonacci” original
resolve o problema de desempenho
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
Apenas coletar testesApenas coletar testes
● Queremos garantir que o oráculo testado com Queremos garantir que o oráculo testado com valores aleatórios tenha sempre o mesmo seed. Mas valores aleatórios tenha sempre o mesmo seed. Mas antes, façamos os testes aleatórios:antes, façamos os testes aleatórios:
● Veja os valores de “n” a cada chamada com:Veja os valores de “n” a cada chamada com:
from random import sample
@p("n", sample(range(32), 16))def test_oraculo_entrada_aleatoria(n): assert fibonacci_closed_form(n) == fibonacci(n)
from random import sample
@p("n", sample(range(32), 16))def test_oraculo_entrada_aleatoria(n): assert fibonacci_closed_form(n) == fibonacci(n)
$ py.test --collect-only$ py.test --collect-only
02_09
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
Exemplo de conftest.py:Exemplo de conftest.py:Fixando o seedFixando o seed
● Vamos forçar para que os valores de “n” utilizados nos Vamos forçar para que os valores de “n” utilizados nos testes “aleatórios” com oráculo sejam sempre os mesmos.testes “aleatórios” com oráculo sejam sempre os mesmos.
● Crie um arquivo conftest.py no mesmo diretório em que o Crie um arquivo conftest.py no mesmo diretório em que o py.test é chamado contendopy.test é chamado contendo
● pytest_configurepytest_configure– ““Hook” chamado antes da coleta dos testesHook” chamado antes da coleta dos testes
– Há outros “hooks” iniciados com “pytest_” para cada etapa do Há outros “hooks” iniciados com “pytest_” para cada etapa do processo de testesprocesso de testes
import randomdef pytest_configure(config): random.seed(42)
import randomdef pytest_configure(config): random.seed(42) 02_10
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
Personalização das etapas do processo Personalização das etapas do processo do py.test (“hooks”)do py.test (“hooks”)
http://pytest.org/latest/plugins.html● É possível inserir um processo para antes de cada teste, após É possível inserir um processo para antes de cada teste, após
cada teste, antes da coleta, após a coleta (e.g. reordenar os cada teste, antes da coleta, após a coleta (e.g. reordenar os testes), etc. em um arquivo “conftest.py” com funções testes), etc. em um arquivo “conftest.py” com funções iniciadas em “pytest_”iniciadas em “pytest_”– Normalmente usado em casos específicos, e.g. com warnings para Normalmente usado em casos específicos, e.g. com warnings para
remover as alterações realizadas no próprio globals da remover as alterações realizadas no próprio globals da função/método que realizou o warningfunção/método que realizou o warning
● https://docs.python.org/2/library/warnings.html● Warnings são difíceis de testar, tanto com o fixture padrão “recwarn” do Warnings são difíceis de testar, tanto com o fixture padrão “recwarn” do
py.test como usando diretamente o módulo “warnings” padrão do Python.py.test como usando diretamente o módulo “warnings” padrão do Python.
● Para testar algo nessas condições, é recomendável que se Para testar algo nessas condições, é recomendável que se procure por exemplos antes (e.g. AudioLazy).procure por exemplos antes (e.g. AudioLazy).
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
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): """ Nome da nota dado o pitch MIDI (em semitons) caso esteja na pentatônica de Cm.
>>> nome_nota(70) # Bb4 'Bb'
>>> nome_nota(69) # A4 (La central) Traceback (most recent call last): ... ValueError: Fora da escala! """ if pitch % 12 == 0: return "C" if pitch % 12 == 3: return "Eb" if pitch % 12 == 5: return "F" if pitch % 12 == 7: return "G" if pitch % 12 == 10: return "Bb" raise ValueError("Fora da escala!")
def nome_nota(pitch): """ Nome da nota dado o pitch MIDI (em semitons) caso esteja na pentatônica de Cm.
>>> nome_nota(70) # Bb4 'Bb'
>>> nome_nota(69) # A4 (La central) Traceback (most recent call last): ... ValueError: Fora da escala! """ if pitch % 12 == 0: return "C" if pitch % 12 == 3: return "Eb" if pitch % 12 == 5: return "F" if pitch % 12 == 7: return "G" if pitch % 12 == 10: return "Bb" 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
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
Cobertura de códigoCobertura de código● Crie um tox.ini contendoCrie um tox.ini contendo
Assim não é mais necessário digitar parâmetros ao chamar o Assim não é mais necessário digitar parâmetros ao chamar o py.test (exceto parâmetros complementares, e.g. “-k”)py.test (exceto parâmetros complementares, e.g. “-k”)
● --cov-config tox.ini--cov-config tox.ini– Usa o tox.ini como arquivo de configuração do tox, py.test e pytest-cov.Usa o tox.ini como arquivo de configuração do tox, py.test e pytest-cov.
– Avalie a cobertura de código com branching colocando isto também no Avalie a cobertura de código com branching colocando isto também no tox.ini (configura o pytest-cov):tox.ini (configura o pytest-cov):
● --cov nota--cov nota– Especifica o pacote/módulo que deve ser avaliado (nota.py)Especifica o pacote/módulo que deve ser avaliado (nota.py)
● Completar doctests para chegar a 100% de cobertura.Completar doctests para chegar a 100% de 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
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
ToxTox● Gerencia múltiplos ambientes virtuais, para Gerencia múltiplos ambientes virtuais, para
automatizar os testes em todos os ambientesautomatizar os testes em todos os ambientes● Exigências:Exigências:
– Um arquivo setup.py do projeto (instalação/configuração Um arquivo setup.py do projeto (instalação/configuração do “package”)do “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 nos 2 primeiros dos 3 “projetos” criados, Vamos fazer nos 2 primeiros dos 3 “projetos” criados, para testar tanto no Python 2 como no Python 3.para testar tanto no Python 2 como no Python 3.
from setuptools import setupsetup(name="pytut")from setuptools import setupsetup(name="pytut") EsteEste 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
01_0502_11
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
Tox + py.test + doctestsTox + py.test + doctests● Para testar doctests, o py.test avalia todos os “*.py” Para testar doctests, o py.test avalia todos os “*.py”
não apenas os arquivos iniciados com “test_”não apenas os arquivos iniciados com “test_”– Necessário avisar o tox para instalar o pytest-covNecessário avisar o tox para instalar o pytest-cov
– Importar o setup.py gera conflitoImportar o setup.py gera conflito● Soluções possíveis (basta utilizar uma):Soluções possíveis (basta utilizar uma):
● Coloque o tox com o terceiro “projeto” criado neste Coloque o tox com o terceiro “projeto” criado neste tutorialtutorial
[testenv]deps = pytest pytest-covinstall_command = pip install {opts} {packages}
[testenv]deps = pytest pytest-covinstall_command = pip install {opts} {packages}
Altera o comando de instalação das dependências
Extra!
03_04
from setuptools import setupif __name__ == "__main__": setup(name="pytut")
from setuptools import setupif __name__ == "__main__": setup(name="pytut") EvitarEvitar que o “setup”
seja chamado ao importar o setup.py
[pytest]addopts = ... --ignore setup.py
[pytest]addopts = ... --ignore setup.py
Avisar no tox.ini para o py.test ignorar o setup.pypy.test ignorar o setup.py
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
FixturesFixtures● Já usamos fixtures!Já usamos fixtures!
– pytest.mark.parametrizepytest.mark.parametrize
● São utilizadas como PARÂMETROS nas rotinas de teste. São utilizadas como PARÂMETROS nas rotinas de teste. Podemos, por exemplo, criar uma rotina para acessar um Podemos, por exemplo, criar uma rotina para acessar um recurso que precisa ser liberado ao final de seu uso ou recurso que precisa ser liberado ao final de seu uso ou possui um “mock” (banco de dados, instâcia WSGI, etc.).possui um “mock” (banco de dados, instâcia WSGI, etc.).
● Testaremos algo que acessa dados armazenados em um Testaremos algo que acessa dados armazenados em um arquivo. Começamos avaliando se um arquivo “dados.txt” arquivo. Começamos avaliando se um arquivo “dados.txt” existe e está vazio:existe e está vazio:
# Sem fixture (por enquanto)def test_arquivo_vazio(): with open("dados.txt") as arq: assert not arq.read(1)
# Sem fixture (por enquanto)def test_arquivo_vazio(): with open("dados.txt") as arq: assert not arq.read(1)
04_01
test_arquivo.py
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
Yield fixtureYield fixture(fixtures personalizadas)(fixtures personalizadas)
● Definida ao estilo de gerenciador de contexto...Definida ao estilo de gerenciador de contexto...
...e nos testes, aparece como parâmetro:...e nos testes, aparece como parâmetro:
● Pode-se usar a mesma fixture em mais de um teste. Pode-se usar a mesma fixture em mais de um teste. Façam isso em um novo teste que verifica se o Façam isso em um novo teste que verifica se o arquivo não está vazio (com xfail).arquivo não está vazio (com xfail).
04_02
04_03
# Sem fixture (por enquanto)def test_arquivo_vazio(arq): assert not arq.read(1)
# Sem fixture (por enquanto)def test_arquivo_vazio(arq): assert not arq.read(1)
@pytest.yield_fixturedef arq(): with open("dados.txt") as f: yield f
@pytest.yield_fixturedef arq(): with open("dados.txt") as f: yield f
@pytest.yield_fixturedef nome_fixture(): # setup yield valor # teardown
@pytest.yield_fixturedef nome_fixture(): # setup yield valor # teardown Modelo
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
EscopoEscopo● É possível fazer os dois testes (testar se vazio e testar se não vazio) É possível fazer os dois testes (testar se vazio e testar se não vazio)
passarem ao mesmo tempo?passarem ao mesmo tempo?
...e se o arquivo mudar entre os testes?...e se o arquivo mudar entre os testes?
@pytest.yield_fixturedef arq(): with open("dados.txt", "r+") as f: backup = f.read() yield f with open("dados.txt", "w") as f: f.write(backup)
def test_arquivo_vazio(arq): arq.seek(0) assert not arq.read(1) arq.write("A-ha!")
def test_arquivo_nao_vazio(arq): arq.seek(0) assert arq.read(1)
@pytest.yield_fixturedef arq(): with open("dados.txt", "r+") as f: backup = f.read() yield f with open("dados.txt", "w") as f: f.write(backup)
def test_arquivo_vazio(arq): arq.seek(0) assert not arq.read(1) arq.write("A-ha!")
def test_arquivo_nao_vazio(arq): arq.seek(0) assert arq.read(1)
EVITAR! Normalmente testes são INdependentes entre si
@pytest.yield_fixture(scope="session")@pytest.yield_fixture(scope="session")
04_04
Funciona? E se a fixture obtiver “f” somente uma vez na sessão?Funciona? E se a fixture obtiver “f” somente uma vez na sessão?
04_05
O uso de escopo permite otimizar testes evitando a necessidade de setup/teardown para cada teste
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
Outros recursos do py.test:Outros recursos do py.test:Arquivos temporáriosArquivos temporários
● Há diversas formas usando a standard libraryHá diversas formas usando a standard library
● Ou usando o plugin “tmpdir” do py.testOu usando o plugin “tmpdir” do py.test– http://pytest.org/latest/tmpdir.html
from tempfile import NamedTemporaryFileimport pytest
@pytest.yield_fixturedef tmp(): with NamedTemporaryFile() as f: yield f # f.name possui o nome do arquivo
from tempfile import NamedTemporaryFileimport pytest
@pytest.yield_fixturedef tmp(): with NamedTemporaryFile() as f: yield f # f.name possui o nome do arquivo
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
Skip/xfailSkip/xfail
● Há testes que podem fazer sentido em somente um dos Há testes que podem fazer sentido em somente um dos ambientes (e.g. testes que dependem do ambientes (e.g. testes que dependem do itertools.accumulate ou do functools.lru_cache que itertools.accumulate ou do functools.lru_cache que existem apenas no Python 3).existem apenas no Python 3).
● Decorators (“mark”), [possivelmente] com condições para Decorators (“mark”), [possivelmente] com condições para realização do(s) teste(s) marcadosrealização do(s) teste(s) marcados– pytest.mark.skipifpytest.mark.skipif
– pytest.mark.xfailpytest.mark.xfail
● Imperativo (comandos)Imperativo (comandos)– pytest.skippytest.skip
– pytest.xfailpytest.xfail
Usamos o mark.xfail
incondicional no 02_05
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
Mock/stub/fake/dummyMock/stub/fake/dummycom a fixture monkeypatchcom a fixture monkeypatch
● Fixture monkeypatchFixture monkeypatch– Método “setattr”Método “setattr”
– Mesma sintaxe do built-in setattr (e __setattr__)Mesma sintaxe do built-in setattr (e __setattr__)
– Monkeypatch.setattr()Monkeypatch.setattr()
● http://pytest.org/latest/monkeypatch.html
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
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
É possível usar o oráculo do
Fibonacci para verificar os dígitos mais significativos de valores maiores
de entrada?
Extra!
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
MiscelâneaMiscelânea
● Vejam a ajuda do py.test!Vejam a ajuda do py.test!– Todos os parâmetros são opcionais, mas quais são todos Todos os parâmetros são opcionais, mas quais são todos
os parâmetros?os parâmetros?
● Há muita documentação na InternetHá muita documentação na Internet– Links na apresentação (primeiro e último slides)Links na apresentação (primeiro e último slides)
● Open source!Open source!– Colaborações =)Colaborações =)
$ py.test --helpusage: py.test [options] [file_or_dir] [file_or_dir] [...]...
$ py.test --helpusage: py.test [options] [file_or_dir] [file_or_dir] [...]...
Testando com py.test e tox – Danilo J. S. Bellini – @danilobellini2014-11-05 – Recife / PE
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