Python simula vários valores de retorno

168

Estou usando pythons mock.patch e gostaria de alterar o valor de retorno para cada chamada. Aqui está a ressalva: a função que está sendo corrigida não possui entradas, portanto, não posso alterar o valor de retorno com base na entrada.

Aqui está o meu código para referência.

def get_boolean_response():
    response = io.prompt('y/n').lower()
    while response not in ('y', 'n', 'yes', 'no'):
        io.echo('Not a valid input. Try again'])
        response = io.prompt('y/n').lower()

    return response in ('y', 'yes')

Meu código de teste:

@mock.patch('io')
def test_get_boolean_response(self, mock_io):
    #setup
    mock_io.prompt.return_value = ['x','y']
    result = operations.get_boolean_response()

    #test
    self.assertTrue(result)
    self.assertEqual(mock_io.prompt.call_count, 2)

io.prompté apenas uma versão independente da plataforma (python 2 e 3) de "input". Então, finalmente, estou tentando zombar da entrada dos usuários. Eu tentei usar uma lista para o valor de retorno, mas isso não parece funcionar.

Você pode ver que, se o valor de retorno for algo inválido, receberei apenas um loop infinito aqui. Então, preciso de uma maneira de alterar o valor de retorno, para que meu teste realmente termine.

(outra maneira possível de responder a essa pergunta poderia ser explicar como eu poderia imitar a entrada do usuário em um teste de unidade)


Não estou enganado com esta questão principalmente porque não tenho a capacidade de variar as entradas.

Um dos comentários da resposta sobre esta pergunta está na mesma linha, mas nenhuma resposta / comentário foi fornecido.

Nick Humrich
fonte
3
response is not 'y' or 'n' or 'yes' or 'no'em não fazer o que você pensa que faz. Consulte Como faço para testar uma variável em relação a vários valores? e você não deve usar ispara comparar valores de cadeia, use ==para comparar valores , não para identidades de objetos.
Martijn Pieters
Também tenha cuidado aqui. Parece que você está tentando usar ispara comparar literais de strings. Não faça isso. O fato de funcionar (algumas vezes) é apenas um detalhe de implementação no CPython. Além disso, response is not 'y' or 'n' or 'yes' or 'no'provavelmente não está fazendo o que você pensa que é ...
mgilson

Respostas:

300

Você pode atribuir um iterável a side_effect, e a simulação retornará o próximo valor na sequência sempre que for chamado:

>>> from unittest.mock import Mock
>>> m = Mock()
>>> m.side_effect = ['foo', 'bar', 'baz']
>>> m()
'foo'
>>> m()
'bar'
>>> m()
'baz'

Citando a Mock()documentação :

Se side_effect for iterável, cada chamada para a simulação retornará o próximo valor da iterável.

Como um aparte, o teste response is not 'y' or 'n' or 'yes' or 'no'vai não trabalhar; você está perguntando se a expressão (response is not 'y')é verdadeira ou 'y'verdadeira (sempre é o caso, uma seqüência de caracteres não vazia é sempre verdadeira) etc. As várias expressões de ambos os lados dos oroperadores são independentes . Consulte Como faço para testar uma variável em relação a vários valores?

Você também não deve usar ispara testar uma string. O intérprete CPython pode reutilizar objetos de string em determinadas circunstâncias , mas não é com esse comportamento que você deve contar.

Como tal, use:

response not in ('y', 'n', 'yes', 'no')

em vez de; isso usará testes de igualdade ( ==) para determinar se faz responsereferência a uma sequência com o mesmo conteúdo (valor).

O mesmo se aplica a response == 'y' or 'yes'; use em response in ('y', 'yes')vez disso.

Martijn Pieters
fonte
Existe uma maneira de fazer isso com o padrão mock? Existe uma maneira de usar o patch com MagicMock como eu estou fazendo com o mock padrão?
21414 Nick Humrich
@ Humdinger: Este é um recurso da Mockclasse stardard .
Martijn Pieters
17
A atribuição de uma lista parece funcionar apenas com o python 3. Testando com python 2.7, preciso usar um iterador ( m.side_effect = iter(['foo', 'bar', 'baz'])).
User686249
1
@ user686249: Eu realmente posso reproduzir isso, porque especificar a partir de um método produz a lambda(a), não a MagicMock. Um objeto de função não pode ter propriedades, portanto, o side_effectatributo deve ser iterável. Você não deve especificar o método assim. Melhor uso mock.patch.object(requests.Session, 'post'); isso resulta em um objeto patcher que especifica automaticamente automaticamente o método e suporta side_effectcorretamente.
Martijn Pieters
3
@ JoeMjr2: Quando o iterador está esgotado, StopIterationé gerado. Você pode usar qualquer iterador itertools.chain(['Foo'], itertools.repeat('Bar'))para produzir Foouma vez e depois produzir para sempre Bar.
Martijn Pieters