O livro "O Caminho do Python. Faixa preta para desenvolvimento, dimensionamento, teste e implantação ”

imagem Oi, habrozhiteli! O Python Path permite aprimorar suas habilidades profissionais e aprender o máximo possível sobre os recursos da linguagem de programação mais popular. Você aprenderá como escrever um código eficaz, criar os melhores programas em um tempo mínimo e evitar erros comuns. É hora de se familiarizar com a computação e memoização multithread, obter aconselhamento especializado no campo da API e design de banco de dados, bem como olhar dentro do Python para expandir sua compreensão da linguagem. Você precisa iniciar um projeto, trabalhar com versões, organizar testes automáticos e escolher um estilo de programação para uma tarefa específica. Em seguida, você estudará declarações de funções eficazes, selecionará estruturas e bibliotecas de dados adequadas, criará programas, pacotes sem problemas e otimizará programas no nível do bytecode.

Trecho. Executando testes em paralelo


A execução de suítes de teste pode ser demorada. Essa é uma ocorrência comum em grandes projetos, quando um conjunto de testes leva alguns minutos para ser concluído. Por padrão, o pytest executa testes sequencialmente, em uma ordem específica.

Como a maioria dos computadores possui processadores com vários núcleos, você pode acelerar se separar os testes para executar em vários núcleos.

Para isso, o pytest possui um plugin pytest-xdist que pode ser instalado usando o pip. Este plug-in estende a linha de comando pytest com o argumento ––numprocesses (abreviado –n), que pega o número de núcleos usados ​​como argumento. O lançamento do pytest –n 4 executará o conjunto de testes em quatro processos paralelos, mantendo um equilíbrio entre a carga de núcleos disponíveis.

Devido ao fato de que o número de núcleos pode variar, o plug-in também aceita a palavra-chave automática como um valor. Nesse caso, o número de núcleos disponíveis será retornado automaticamente.

Criando objetos usados ​​em testes usando acessórios


Nos testes de unidade, muitas vezes é necessário executar um conjunto de operações padrão antes e depois da execução do teste, e essas instruções envolvem certos componentes. Por exemplo, você pode precisar de um objeto que expresse o estado da configuração do aplicativo e ele deve ser inicializado antes de cada teste e, em seguida, redefinido para seus valores iniciais após a execução. Da mesma forma, se o teste depender de um arquivo temporário, esse arquivo deverá ser criado antes do teste e excluído depois. Esses componentes são chamados de luminárias . Eles são instalados antes do teste e desaparecem após sua execução.

No pytest, os equipamentos são declarados como funções simples. A função de fixação deve retornar o objeto desejado para que, nos testes onde é usado, esse objeto possa ser usado.

Aqui está um exemplo de um acessório simples:

import pytest @pytest.fixture def database(): return <some database connection> def test_insert(database): database.insert(123) 

O dispositivo elétrico do banco de dados é usado automaticamente por qualquer teste que tenha o argumento do banco de dados em sua lista. A função test_insert () receberá o resultado da função database () como o primeiro argumento e usará esse resultado como achar melhor. Com esse uso de acessórios, você não precisa repetir o código de inicialização do banco de dados várias vezes.

Outra característica comum do teste de código é a capacidade de remover supérfluos após a operação do aparelho. Por exemplo, feche a conexão com o banco de dados. A implementação do dispositivo elétrico como um gerador adicionará funcionalidade para limpar objetos verificados (Listagem 6.5).

Listagem 6.5. Limpando um objeto verificado


 import pytest @pytest.fixture def database(): db = <some database connection> yield db db.close() def test_insert(database): database.insert(123) 
Como usamos a palavra-chave yield e criamos um gerador a partir do banco de dados, o código após a instrução yield é executado apenas no final do teste. Este código fechará a conexão com o banco de dados no final do teste.

Fechar a conexão com o banco de dados para cada teste pode causar desperdício injustificado de poder de computação, pois outros testes podem usar uma conexão já aberta. Nesse caso, você pode passar o argumento do escopo para o decorador do dispositivo elétrico, especificando seu escopo:

 import pytest @pytest.fixture(scope="module") def database(): db = <some database connection> yield db db.close() def test_insert(database): database.insert(123) 

Ao especificar o parâmetro scope = “module”, você inicializou o equipamento uma vez para o módulo inteiro e agora uma conexão de banco de dados aberta estará disponível para todas as funções de teste que o solicitarem.

Você pode executar algum código geral antes ou depois do teste, definindo acessórios como usados ​​automaticamente com a palavra-chave autouse, em vez de especificá-los como argumento para cada função de teste. A concretização da função pytest.fixture () com o argumento True, a palavra-chave autouse, garante que os equipamentos sejam chamados todas as vezes antes de executar o teste no módulo ou classe em que é declarado.

 import os import pytest @pytest.fixture(autouse=True) def change_user_env(): curuser = os.environ.get("USER") os.environ["USER"] = "foobar" yield os.environ["USER"] = curuser def test_user(): assert os.getenv("USER") == "foobar"</source     .  ,    :     ,      ,       . <h3>  </h3>           ,    ,   ,         .          Gnocchi,    . Gnocchi      <i>storage API</i>.    Python          .       ,      API   .        ,      (    storage API),  ,       .   ,   <i> </i>,     ,        .  6.6          ,    :    mysql,   —  postgresql. <blockquote><h4> 6.6.      </h4> <source lang="python">import pytest import myapp @pytest.fixture(params=["mysql", "postgresql"]) def database(request): d = myapp.driver(request.param) d.start() yield d d.stop() def test_insert(database): database.insert("somedata") 
O dispositivo de driver recebe dois valores diferentes como parâmetro - os nomes dos drivers de banco de dados que são suportados pelo aplicativo. test_insert é executado duas vezes: uma para o banco de dados MySQL e a segunda para o banco de dados PostgreSQL. Isso facilita a repetição do mesmo teste, mas com cenários diferentes, sem adicionar novas linhas de código.

Testes gerenciados com objetos fictícios


Objetos fictícios (ou stubs, objetos simulados) são objetos que imitam o comportamento de objetos de aplicativos reais, mas em um estado controlado especial. Eles são mais úteis na criação de ambientes que descrevem minuciosamente as condições para o teste. Você pode substituir todos os objetos, exceto o testado, por objetos fictícios e isolá-lo, além de criar um ambiente para teste de código.

Um caso de uso é criar um cliente HTTP. É quase impossível (ou melhor, incrivelmente difícil) criar um servidor HTTP no qual você possa executar todas as situações e cenários para cada valor possível. Clientes HTTP são especialmente difíceis de testar para cenários de erro.

A biblioteca padrão possui um comando simulado para criar um objeto fictício. A partir do Python 3.3, o mock foi integrado à biblioteca unittest.mock. Portanto, você pode usar o snippet de código abaixo para fornecer compatibilidade com versões anteriores entre o Python 3.3 e versões anteriores:

 try: from unittest import mock except ImportError: import mock 

A biblioteca simulada é muito fácil de usar. Qualquer atributo disponível para o objeto mock.Mock é criado dinamicamente no tempo de execução. Qualquer atributo pode ser atribuído a qualquer valor. Na Listagem 6.7, mock é usado para criar um objeto fictício para o atributo fictício.

Listagem 6.7. Acessando o atributo mock.Mock


 >>> from unittest import mock >>> m = mock.Mock() >>> m.some_attribute = "hello world" >>> m.some_attribute "hello world" 
Também é possível criar dinamicamente um método para um objeto mutável, como na Listagem 6.8, em que você cria um método fictício que sempre retorna 42 e aceita o que deseja como argumento.

Listagem 6.8. Criando um método para o objeto fictício mock.Mock


 >>> from unittest import mock >>> m = mock.Mock() >>> m.some_method.return_value = 42 >>> m.some_method() 42 >>> m.some_method("with", "arguments") 42 
Apenas algumas linhas, e o objeto mock.Mock agora tem um método some_method (), que retorna 42. É preciso qualquer tipo de argumento, enquanto não há verificação do que é o argumento.

Os métodos gerados dinamicamente também podem ter efeitos colaterais (intencionais). Para não serem apenas métodos padronizados que retornam um valor, eles podem ser definidos para executar códigos úteis.

A Listagem 6.9 cria um método fictício que tem um efeito colateral - exibe a sequência "hello world".

Listagem 6.9. Criando um método para um objeto mock.Mock com um efeito colateral


  >>> from unittest import mock >>> m = mock.Mock() >>> def print_hello(): ... print("hello world!") ... return 43 ... ❶ >>> m.some_method.side_effect = print_hello >>> m.some_method() hello world! 43 ❷ >>> m.some_method.call_count 1 
Atribuímos uma função inteira ao atributo some_method❶. Tecnicamente, isso permite implementar um cenário mais complexo no teste, devido ao fato de poder incluir qualquer código necessário para o teste no objeto fictício. Em seguida, você precisa passar esse objeto para a função que o espera.

O atributo _ call_count é uma maneira fácil de verificar o número de vezes que um método foi chamado.

A biblioteca simulada usa o padrão "verificação de ação": isso significa que, após o teste, você precisa garantir que as ações substituídas por manequins foram executadas corretamente. A Listagem 6.10 aplica o método assert () a objetos fictícios para executar essas verificações.

Listagem 6.10. Métodos de verificação de chamadas


  >>> from unittest import mock >>> m = mock.Mock() ❶ >>> m.some_method('foo', 'bar') <Mock name='mock.some_method()' id='26144272'> ❷ >>> m.some_method.assert_called_once_with('foo', 'bar') >>> m.some_method.assert_called_once_with('foo', ❸mock.ANY) >>> m.some_method.assert_called_once_with('foo', 'baz') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python2.7/dist-packages/mock.py", line 846, in assert_cal led_once_with return self.assert_called_with(*args, **kwargs) File "/usr/lib/python2.7/dist-packages/mock.py", line 835, in assert_cal led_with raise AssertionError(msg) AssertionError: Expected call: some_method('foo', 'baz') Actual call: some_method('foo', 'bar') 
Criamos métodos com argumentos foo e bar como testes chamando o método ❶. Uma maneira fácil de testar chamadas para objetos fictícios é usar métodos assert_called (), como assert_called_once_with () ❷. Para esses métodos, você precisa passar os valores que você espera que sejam usados ​​ao chamar o método fictício. Se os valores transmitidos diferirem dos usados, o mock gera uma exceção AssertionError. Se você não souber quais argumentos podem ser passados, use mock.ANY como o valor de ❸; ele substituirá qualquer argumento passado para o método fictício.

A biblioteca simulada também pode ser usada para substituir uma função, método ou objeto de um módulo externo. Na Listagem 6.11, substituímos a função os.unlink () por nossa própria função fictícia.

Listagem 6.11. Usando mock.patch


 >>> from unittest import mock >>> import os >>> def fake_os_unlink(path): ... raise IOError("Testing!") ... >>> with mock.patch('os.unlink', fake_os_unlink): ... os.unlink('foobar') ... Traceback (most recent call last): File "<stdin>", line 2, in <module> File "<stdin>", line 2, in fake_os_unlink IOError: Testing! 
Quando usado como gerenciador de contexto, mock.patch () substitui a função de destino pela que selecionamos. Isso é necessário para que o código executado dentro do contexto use o método corrigido. Usando o método mock.patch (), é possível modificar qualquer parte do código externo, forçando-o a se comportar de maneira a testar todas as condições do aplicativo (Listagem 6.12).

Listagem 6.12. Usando mock.patch () para testar muitos comportamentos


  from unittest import mock import pytest import requests class WhereIsPythonError(Exception): passdef is_python_still_a_programming_language(): try: r = requests.get("http://python.org") except IOError: pass else: if r.status_code == 200: return 'Python is a programming language' in r.content raise WhereIsPythonError("Something bad happened") def get_fake_get(status_code, content): m = mock.Mock() m.status_code = status_code m.content = content def fake_get(url): return m return fake_get def raise_get(url): raise IOError("Unable to fetch url %s" % url) ❷ @mock.patch('requests.get', get_fake_get( 200, 'Python is a programming language for sure')) def test_python_is(): assert is_python_still_a_programming_language() is True @mock.patch('requests.get', get_fake_get( 200, 'Python is no more a programming language')) def test_python_is_not(): assert is_python_still_a_programming_language() is False @mock.patch('requests.get', get_fake_get(404, 'Whatever')) def test_bad_status_code(): with pytest.raises(WhereIsPythonError): is_python_still_a_programming_language() @mock.patch('requests.get', raise_get) def test_ioerror(): with pytest.raises(WhereIsPythonError): is_python_still_a_programming_language() 


A Listagem 6.12 implementa um caso de teste que procura todas as instâncias do Python como uma string de linguagem de programação em python.org ❶. Não há opção em que o teste não encontre nenhuma linha na página da web selecionada. Para obter um resultado negativo, você deve alterar a página, mas isso não pode ser feito. Mas com a ajuda do mock, você pode seguir o truque e alterar o comportamento da solicitação para que ela retorne uma resposta fictícia com uma página fictícia que não contenha uma determinada string. Isso permitirá que você teste um cenário negativo no qual python.org não contém uma determinada string e verifique se o programa lida com esse caso corretamente.

Este exemplo usa a versão do mock.patch () para o decorador. O comportamento do objeto fictício não muda e foi mais fácil dar um exemplo no contexto de uma função de teste.

O uso de um objeto fictício ajudará a simular qualquer problema: o servidor retorna um erro 404, um erro de E / S ou um atraso de rede. Podemos garantir que o código retorne os valores corretos ou gere a exceção correta em cada caso, o que garante o comportamento esperado do código.

Identificar código não testado com cobertura


Um ótimo complemento para o teste de unidade é a ferramenta de cobertura. [Cobertura de código é uma medida usada no teste. Mostra a porcentagem do código fonte do programa que foi executado durante o processo de teste . ], que localiza partes de código não testadas. Ele utiliza ferramentas de análise e rastreamento de código para identificar as linhas que foram executadas. Nos testes de unidade, ele pode revelar quais partes do código foram reutilizadas e quais não foram usadas. É necessário criar testes, e a capacidade de descobrir qual parte do código você esqueceu de abordar com os testes torna esse processo mais agradável.

Instale o módulo de cobertura via pip para poder usá-lo através do seu shell.

NOTA


O comando também pode ser chamado de cobertura python se o módulo for instalado através do instalador do seu sistema operacional. Um exemplo disso é o sistema operacional Debian.


Usar a cobertura offline é bastante simples. Ele mostra as partes do programa que nunca iniciam e se tornam um "peso morto" - um código que você não pode removê-lo sem alterar a funcionalidade do programa. Todas as ferramentas de teste discutidas anteriormente neste capítulo são integradas à cobertura.

Ao usar pytest, instale o plug-in pytest-cov por meio de pip install pytest-pycov e adicione algumas opções para gerar uma saída detalhada do código não testado (Listagem 6.13).

Listagem 6.13. Usando pytest e cobertura


 $ pytest --cov=gnocchiclient gnocchiclient/tests/unit ---------- coverage: platform darwin, python 3.6.4-final-0 ----------- Name Stmts Miss Branch BrPart Cover --------------------------- gnocchiclient/__init__.py 0 0 0 0 100% gnocchiclient/auth.py 51 23 6 0 49% gnocchiclient/benchmark.py 175 175 36 0 0% --snip-- --------------------------- TOTAL 2040 1868 424 6 8% === passed in 5.00 seconds === 
A opção --cov permite a saída do relatório de cobertura no final do teste. Você deve passar o nome do pacote como argumento para que o plug-in filtre corretamente o relatório. A saída conterá linhas de código que não foram executadas, o que significa que elas não foram testadas. Tudo o que resta para você é abrir o editor e escrever um teste para esse código.

O módulo de cobertura é ainda melhor - permite gerar relatórios claros no formato HTML. Basta adicionar -–cov-report-html e as páginas HTML aparecerão no diretório htmlcov de onde você executa o comando. Cada página mostrará quais partes do código-fonte estavam ou não em execução.

Se você quiser ir ainda mais longe, use –cover-fail-under-COVER_MIN_PERCENTAGE, que fará com que o conjunto de testes falhe se não cobrir a porcentagem mínima de código. Embora uma grande porcentagem de cobertura seja uma boa meta e as ferramentas de teste sejam úteis para obter informações sobre o status da cobertura de teste, a porcentagem em si não é muito informativa. A Figura 6.1 mostra um exemplo de relatório de cobertura mostrando a porcentagem de cobertura.

Por exemplo, cobrir o código com testes 100% é uma meta válida, mas isso não significa necessariamente que o código foi totalmente testado. Este valor mostra apenas que todas as linhas de código no programa foram cumpridas, mas não indica que todas as condições foram testadas.

Vale a pena usar as informações de cobertura para expandir o conjunto de testes e criá-los para o código que não é executado. Isso simplifica o suporte ao projeto e melhora a qualidade geral do código.

imagem


Sobre o autor


Julien Danju hackeia o freeware há cerca de vinte anos e desenvolve programas em Python há quase doze anos. Atualmente, ele lidera a equipe de design da plataforma de nuvem distribuída baseada no OpenStack, que possui o maior banco de dados de código aberto do Python existente, com cerca de dois milhões e meio de linhas de código. Antes de desenvolver serviços em nuvem, Julien criou o gerenciador de janelas e contribuiu para o desenvolvimento de muitos projetos, como o Debian e o GNU Emacs.

Sobre o Science Editor


Mike Driscoll programa em Python há mais de uma década. Por um longo tempo, ele escreveu sobre Python no The Mouse vs. O Python . Autor de vários livros em Python: Python 101, entrevistas em Python e ReportLab: processamento de PDF com Python. Você pode encontrar Mike no Twitter e no GitHub: @driscollis.

»Mais informações sobre o livro podem ser encontradas no site do editor
» Conteúdo
» Trecho

Cupom de 25% de desconto para vendedores ambulantes - Python

Após o pagamento da versão impressa do livro, um livro eletrônico é enviado por e-mail.

Source: https://habr.com/ru/post/pt466027/


All Articles