Como Executar Testes de Unidade em Python Sem Testar Sua Paciência
Mais frequentemente do que não, o software que nós escrevemos interage diretamente com o que poderíamos rotular como “sujo” de serviços. Em termos leigos: serviços que são cruciais para a nossa aplicação, mas cujas interações têm pretendido, mas não desejados, efeitos colaterais-ou seja, indesejados no contexto de um teste autônomo.,facebook facebook: por exemplo: talvez estejamos escrevendo um aplicativo social e queiramos testar nosso novo ‘post to Facebook feature’, mas não queremos realmente postar no Facebook cada vez que executamos nossa suíte de teste.
Python unittest
biblioteca inclui um sub-pacote chamado unittest.mock
—ou se declarar como uma dependência, simplesmente mock
—o que proporciona extremamente poderosa e útil para simulação e simular estes efeitos colaterais indesejados.,
Nota: mock
é recém-incluídos na biblioteca padrão de Python 3.3; antes de distribuições terá que usar a Simulação de biblioteca para download via PyPI.
as chamadas do sistema contra a zombação do Python
para lhe dar outro exemplo, e um que iremos executar para o resto do artigo, considere as chamadas do sistema., Não é difícil ver que estas são as principais candidatas para zombando: se você está escrevendo um script para ejetar um CD, uma unidade de servidor web que remove antiquada o cache de arquivos de /tmp
, ou um servidor de soquete que se liga a uma porta TCP, estas chamadas de todos apresentam efeitos colaterais indesejados no contexto de sua unidade de testes.
como desenvolvedor, você se importa mais que a sua biblioteca tenha chamado com sucesso a função de sistema para ejetar um CD (com os argumentos corretos, etc.) ao contrário de realmente experimentar a sua bandeja de CD aberto cada vez que um teste é executado. (Ou pior, várias vezes, como vários testes de referência do Código eject durante um único teste unitário!)
da mesma forma, manter a sua unidade de testes eficiente e executante significa manter tanto “código lento” fora dos testes automatizados, nomeadamente sistema de arquivos e acesso à rede.,
para o nosso primeiro exemplo, vamos refacturar um teste padrão em Python da forma original para um usando mock
. Vamos demonstrar como escrever um caso de teste com escárnio vai tornar nossos testes mais inteligentes, mais rápidos, e capazes de revelar mais sobre como o software funciona.
uma função de eliminação simples
todos nós precisamos de excluir arquivos do nosso sistema de arquivos de tempos em tempos, então vamos escrever uma função em Python que irá tornar um pouco mais fácil para os nossos scripts para fazê-lo.,
#!/usr/bin/env python# -*- coding: utf-8 -*-import osdef rm(filename): os.remove(filename)
Obviamente, o nosso rm
método, nesse momento, não oferece muito mais do que o subjacente os.remove
método, mas a nossa codebase vai melhorar, o que nos permite adicionar mais funcionalidades aqui.
let’s write a traditional test case, i.e., without mocks:
Our test case is pretty simple, but every time it is run, a temporary file is created and then deleted., Além disso, não temos nenhuma maneira de testar se o nosso método rm
passa o argumento corretamente para o os.remove
call. Podemos assumir que se baseia no teste acima, mas muito resta a desejar.
Refactoring with Python Mocks
let’s refactor our test case using mock
:
com estes refactores, nós mudamos fundamentalmente a forma como o ensaio funciona. Agora, temos um intruso, um objeto que podemos usar para verificar a funcionalidade de outro.,
Potencial Python Zombando de Armadilhas
Uma das primeiras coisas que deve ficar de fora é que estamos usando o mock.patch
método de decorador para zombar de um objeto localizado no mymodule.os
, e de injeção de que a simulação em nosso caso de teste do método. Não faria mais sentido apenas ridicularizar os
em si, ao invés da referência a ele em mymodule.os
?
well, Python is somewhat of a sneaky snake when it comes to imports and managing modules., No tempo de execução, o módulo
tem o seu próprioos
que é importado para o seu próprio âmbito local no módulo. Assim, se nós ridicularizarmosos
, não veremos os efeitos do mock no módulomymodule
.
O mantra para continuar repetindo é este:
Mock um item onde ele é usado, não de onde ele veio.,
Se você precisa de simulação tempfile
módulo para myproject.app.MyElaborateClass
, você provavelmente terá que aplicar a simulação de myproject.app.tempfile
, como cada módulo mantém a sua própria importações.com essa armadilha fora do caminho, vamos continuar a gozar.
adicionar validação a ‘ rm ‘
o método rm
definido anteriormente é bastante simplificado. Gostaríamos que validasse que um caminho existe e é um arquivo antes de apenas cegamente tentar removê-lo., Let’s refactor rm
to be a bit smarter:
#!/usr/bin/env python# -*- coding: utf-8 -*-import osimport os.pathdef rm(filename): if os.path.isfile(filename): os.remove(filename)
Great. Agora, vamos ajustar o nosso caso de teste para manter a cobertura.o nosso paradigma de teste mudou completamente. Agora podemos verificar e validar a funcionalidade interna dos métodos sem quaisquer efeitos colaterais.
remoção de Ficheiros como um serviço com Patch Mock
até agora, só temos estado a trabalhar com o fornecimento de zombies para funções, mas não para métodos em objectos ou casos em que a zombação é necessária para o envio de parâmetros. Vamos primeiro tratar dos métodos dos objectos.,
vamos começar com um refactor do id
método em uma classe de serviço. Não há realmente uma necessidade justificável, por si só, de encapsular uma função tão simples em um objeto, mas ela nos ajudará no mínimo a demonstrar conceitos chave emmock
. Vamos refactor:
Você vai notar que não mudou muito em nosso caso de teste:
grande, então agora sabemos que o RemovalService
funciona como planejado., Vamos criar outro serviço que declara-lo como dependência:
Desde já temos cobertura de teste no RemovalService
, nós não estamos indo para validar a funcionalidade interna de rm
método em nossos testes de UploadService
. Em vez disso, vamos simplesmente testar (sem efeitos colaterais, é claro) que UploadService
chama o RemovalService.rm
método, que sabemos “just works™” do nosso caso de teste anterior.
Existem duas maneiras de fazer isso:
- Mock out the
RemovalService.rm
method itself., - fornecer uma instância escarnecida no construtor de
UploadService
.
Como ambos os métodos são muitas vezes importantes em testes unitários, vamos rever ambos.
Opção 1: Zombando de Métodos de Instância
mock
biblioteca tem um método especial de decorador para zombar da instância de objecto métodos e propriedades, o @mock.patch.object
decorador:
Ótimo! Nós validamos que o métodoUploadService
chama com sucesso a nossa instância derm
método. Notaste alguma coisa interessante?, O mecanismo de patching realmente substituiu o métodorm
de todas as instânciasRemovalService
no nosso método de ensaio. Isso significa que podemos realmente inspeccionar as instâncias. Se você quiser ver mais, tente cair em um breakpoint em seu código de troça para obter uma boa sensação de como o mecanismo de patching funciona.
Mock Patch Pitfall: Decorator Order
When using multiple decorators on your test methods, order is important, and it’s kind of confusing. Basicamente, ao mapear Decoradores para os parâmetros do método, trabalhar para trás., Considere este exemplo:
nota como os nossos parâmetros são compatíveis com a ordem inversa dos decoradores? Isso deve-se, em parte, à forma como o Python funciona. Com várias método decoradores, a ordem de execução do pseudocódigo:
patch_sys(patch_os(patch_os_path(test_something)))
Desde o patch sys
é o ultraperiféricas patch, será executada pela última vez, tornando-o o último parâmetro no teste real argumentos do método. Tome nota deste poço e use um depurador ao executar seus testes para se certificar de que os parâmetros certos estão sendo injetados na ordem certa.,
Opção 2: criação de instâncias Mock
em vez de zombar do método de instância específica, poderíamos, em vez disso, apenas fornecer uma instância ridicularizada para UploadService
com o seu construtor. Prefiro a opção 1 acima, pois é muito mais precisa, mas há muitos casos em que a opção 2 pode ser eficiente ou necessária. Vamos refatorar nosso teste novamente:
neste exemplo, nós ainda não havia para correção de qualquer funcionalidade, basta criar uma auto-spec para o RemovalService
classe e, em seguida, injetar esta instância em nosso UploadService
para validar a funcionalidade.,
o método mock.create_autospec
cria uma instância funcionalmente equivalente à classe fornecida. O que isso significa, praticamente falando, é que quando a instância retornada é interagida, ela vai levantar exceções se usada de forma ilegal. Mais especificamente, se um método for chamado com o número errado de argumentos, uma exceção será levantada. Isto é extremamente importante à medida que os refactores acontecem. Como uma biblioteca muda, os testes quebram e isso é esperado. Sem usar um auto-spec, nossos testes ainda vai passar, mesmo que a implementação subjacente está quebrado.,
Armadilha: mock.Mock
e mock.MagicMock
Classes
mock
biblioteca também inclui duas classes importantes sobre a qual a maioria da funcionalidade interna é construída em cima: mock.Mock
e mock.MagicMock
. Quando é dada a opção de usar uma instância
, uma instânciamock.MagicMock
instância, ou uma auto-spec, sempre favoreça o uso de uma auto-spec, pois ajuda a manter seus testes são para futuras alterações., Isto é porquemock.Mock
emock.MagicMock
aceita todas as chamadas de método e atribuições de propriedades, independentemente da API subjacente. Considere o seguinte caso de uso:
class Target(object): def apply(value): return valuedef method(target, value): return target.apply(value)
Nós podemos testar isso com um mock.Mock
exemplo como este:
Esta lógica parece sensato, mas vamos modificar o Target.apply
método para ter mais parâmetros:
class Target(object): def apply(value, are_you_sure): if are_you_sure: return value else: return None
execute Novamente o teste e você verá que ele ainda passa. Isso é porque não foi construído contra a sua API., É por isso que você deve sempre usar o create_autospec
método autospec
parâmetro com o @patch
e @patch.object
decoradores.Facebook Facebook Mock Example: zombe de uma chamada de API do Facebook
para terminar, vamos escrever um exemplo de mock do mundo real mais aplicável do python, um que mencionamos na introdução: postando uma mensagem para o Facebook. Vamos escrever uma boa aula de papel de embrulho e um caso de teste correspondente.,
Aqui está o nosso caso de teste, que verifica se publicamos a mensagem sem realmente postar a mensagem:
Como vimos até agora, é muito simples começar a escrever testes mais inteligentes com mock
em Python.
Conclusion
Python’smock
library, if a little confusion to work with, is a game-changer for unit-testing. Nós demonstramos casos de uso comum para começar a usar mock
em testes unitários, e esperamos que este artigo ajude os desenvolvedores Python a superar os obstáculos iniciais e a escrever um código excelente e testado.,