Mengolok-olok fungsi untuk memunculkan Exception guna menguji blok pengecualian

119

Saya memiliki fungsi ( foo) yang memanggil fungsi lain ( bar). Jika pemanggilan bar()memunculkan sebuah HttpError, saya ingin menanganinya secara khusus jika kode statusnya adalah 404, jika tidak, naikkan kembali.

Saya mencoba untuk menulis beberapa pengujian unit seputar foofungsi ini , mengejek panggilan ke bar(). Sayangnya, saya tidak bisa mendapatkan panggilan yang diejek bar()untuk mengajukan Pengecualian yang tertangkap oleh exceptblokir saya .

Inilah kode saya yang menggambarkan masalah saya:

import unittest
import mock
from apiclient.errors import HttpError


class FooTests(unittest.TestCase):
    @mock.patch('my_tests.bar')
    def test_foo_shouldReturnResultOfBar_whenBarSucceeds(self, barMock):
        barMock.return_value = True
        result = foo()
        self.assertTrue(result)  # passes

    @mock.patch('my_tests.bar')
    def test_foo_shouldReturnNone_whenBarRaiseHttpError404(self, barMock):
        barMock.side_effect = HttpError(mock.Mock(return_value={'status': 404}), 'not found')
        result = foo()
        self.assertIsNone(result)  # fails, test raises HttpError

    @mock.patch('my_tests.bar')
    def test_foo_shouldRaiseHttpError_whenBarRaiseHttpErrorNot404(self, barMock):
        barMock.side_effect = HttpError(mock.Mock(return_value={'status': 500}), 'error')
        with self.assertRaises(HttpError):  # passes
            foo()

def foo():
    try:
        result = bar()
        return result
    except HttpError as error:
        if error.resp.status == 404:
            print '404 - %s' % error.message
            return None
        raise

def bar():
    raise NotImplementedError()

Aku mengikuti docs Mock yang mengatakan bahwa Anda harus mengatur side_effectdari Mockcontoh ke Exceptionkelas untuk memiliki fungsi mengejek kenaikan gaji kesalahan.

Saya juga melihat beberapa Tanya Jawab StackOverflow terkait lainnya, dan sepertinya saya melakukan hal yang sama yang mereka lakukan untuk menyebabkan dan Pengecualian dimunculkan oleh tiruan mereka.

Mengapa pengaturan side_effectdari barMocktidak menyebabkan diharapkan Exceptionakan dibangkitkan? Jika saya melakukan sesuatu yang aneh, bagaimana saya harus menguji logika di exceptblok saya ?

Jesse Webb
sumber
Aku cukup yakin pengecualian Anda adalah dibesarkan, tapi aku tidak yakin bagaimana Anda menetapkan resp.statuskode di sana. Dari mana HTTPErrorasalnya
Martijn Pieters
@MartijnPieters HttpErroradalah kelas yang ditentukan di lib Googleapiclient yang kami gunakan di GAE. Ini __init__didefinisikan dengan params (resp, content)jadi saya mencoba membuat contoh tiruan untuk respons, dengan kode status yang sesuai ditentukan.
Jesse Webb
Benar, jadi kelas ini ; tetapi Anda tidak perlu menggunakan return_value; resptidak dipanggil .
Martijn Pieters
1
Saya mencoba kode saya lagi tanpa menggunakan HttpErrordan sebaliknya mencoba hanya menggunakan Exceptioncontoh biasa . Ini bekerja dengan sempurna. Artinya, ini pasti ada hubungannya dengan cara saya mengonfigurasi HttpErrorinstance, mungkin terkait dengan cara saya membuat Mockinstance untuk respons.
Jesse Webb

Jawaban:

141

Tiruan Anda meningkatkan pengecualian dengan baik, tetapi error.resp.statusnilainya hilang. Daripada menggunakan return_value, cukup beri tahu Mockbahwa itu statusadalah atribut:

barMock.side_effect = HttpError(mock.Mock(status=404), 'not found')

Argumen kata kunci tambahan untuk Mock()ditetapkan sebagai atribut pada objek yang dihasilkan.

Saya meletakkan definisi foodan Anda bardalam sebuah my_testsmodul, ditambahkan di HttpErrorkelas sehingga saya dapat menggunakannya juga, dan pengujian Anda kemudian dapat dijalankan hingga berhasil:

>>> from my_tests import foo, HttpError
>>> import mock
>>> with mock.patch('my_tests.bar') as barMock:
...     barMock.side_effect = HttpError(mock.Mock(status=404), 'not found')
...     result = my_test.foo()
... 
404 - 
>>> result is None
True

Anda bahkan dapat melihat print '404 - %s' % error.messagebaris tersebut dijalankan, tetapi saya pikir Anda ingin menggunakannya di error.contentsana; itulah HttpError()set atribut dari argumen kedua, bagaimanapun juga.

Martijn Pieters
sumber
2
Ini side_effectadalah bagian kuncinya
Daniel Butler