Python mengejek beberapa nilai balik

168

Saya menggunakan pythons mock.patch dan ingin mengubah nilai balik untuk setiap panggilan. Inilah peringatannya: fungsi yang ditambal tidak memiliki input, jadi saya tidak bisa mengubah nilai kembali berdasarkan input.

Ini kode saya untuk referensi.

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')

Kode Tes saya:

@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.prompthanyalah versi "input" platform independen (python 2 dan 3). Jadi pada akhirnya saya mencoba untuk mengolok-olok input pengguna. Saya telah mencoba menggunakan daftar untuk nilai pengembalian, tetapi itu tidak berhasil.

Anda dapat melihat bahwa jika nilai kembali adalah sesuatu yang tidak valid, saya hanya akan mendapatkan loop tak terbatas di sini. Jadi saya perlu cara untuk akhirnya mengubah nilai kembali, sehingga tes saya benar-benar selesai.

(cara lain yang mungkin untuk menjawab pertanyaan ini bisa dengan menjelaskan bagaimana saya bisa meniru input pengguna dalam unit-test)


Bukan dup dari pertanyaan ini terutama karena saya tidak memiliki kemampuan untuk memvariasikan input.

Salah satu komentar dari Jawaban atas pertanyaan ini sejalan, tetapi tidak ada jawaban / komentar yang diberikan.

Nick Humrich
sumber
3
response is not 'y' or 'n' or 'yes' or 'no'dalam tidak melakukan apa yang Anda pikirkan. Lihat Bagaimana cara menguji satu variabel terhadap beberapa nilai? dan Anda tidak boleh menggunakan isuntuk membandingkan nilai string, gunakan ==untuk membandingkan nilai , bukan identitas objek.
Martijn Pieters
Hati-hati juga di sini. Tampaknya Anda mencoba menggunakan isuntuk membandingkan string literal. Jangan lakukan itu. Fakta bahwa ia berfungsi (kadang-kadang) hanyalah detail implementasi dalam CPython. Juga, response is not 'y' or 'n' or 'yes' or 'no'mungkin tidak melakukan apa yang Anda pikirkan ...
mgilson

Jawaban:

300

Anda dapat menetapkan iterable ke side_effect, dan mock akan mengembalikan nilai berikutnya dalam urutan setiap kali dipanggil:

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

Mengutip Mock()dokumentasi :

Jika side_effect adalah iterable maka setiap panggilan ke mock akan mengembalikan nilai berikutnya dari iterable.

Selain itu, tes tidakresponse is not 'y' or 'n' or 'yes' or 'no' akan berhasil; Anda bertanya apakah ekspresi itu benar, atau benar (selalu demikian, string yang tidak kosong selalu benar), dll. Berbagai ekspresi di kedua sisi operator independen . Lihat Bagaimana cara menguji satu variabel terhadap beberapa nilai?(response is not 'y')'y'or

Anda juga sebaiknya tidak menggunakan isuntuk menguji terhadap string. Penerjemah CPython dapat menggunakan kembali objek string dalam keadaan tertentu , tetapi ini bukan perilaku yang harus Anda andalkan.

Karena itu, gunakan:

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

sebagai gantinya; ini akan menggunakan tes kesetaraan ( ==) untuk menentukan apakah responsereferensi string dengan konten (nilai) yang sama.

Hal yang sama berlaku untuk response == 'y' or 'yes'; gunakan response in ('y', 'yes')saja.

Martijn Pieters
sumber
Apakah ada cara untuk melakukan ini dengan standar mock? Apakah ada cara untuk menggunakan tambalan dengan MagicMock seperti yang saya lakukan dengan mock standar?
Nick Humrich
@Humdinger: Ini adalah fitur Mockkelas stardard .
Martijn Pieters
17
Menetapkan daftar tampaknya hanya berfungsi dengan python 3. Pengujian dengan python 2.7 Saya harus menggunakan iterator sebagai gantinya ( m.side_effect = iter(['foo', 'bar', 'baz'])).
user686249
1
@ user686249: Saya memang dapat mereproduksi ini, karena menspesifikasi dari suatu metode menghasilkan lambda(fungsi), bukan a MagicMock. Objek fungsi tidak dapat memiliki properti, jadi side_effectatributnya harus dapat diulang. Anda seharusnya tidak menentukan metode seperti itu. Penggunaan yang lebih baik mock.patch.object(requests.Session, 'post'); yang menghasilkan objek patcher yang secara otomatis menentukan spesifikasi pada metode, dan mendukung side_effectdengan benar.
Martijn Pieters
3
@ JoeMjr2: Ketika iterator habis, StopIterationdinaikkan. Anda dapat menggunakan iterator apa saja, sehingga Anda bisa menggunakannya itertools.chain(['Foo'], itertools.repeat('Bar'))untuk memproduksi Foosekali, lalu selamanya menghasilkan Bar.
Martijn Pieters