การจำลองฟังก์ชันเพื่อเพิ่มข้อยกเว้นเพื่อทดสอบบล็อกยกเว้น


120

ฉันมีฟังก์ชัน ( foo) ซึ่งเรียกใช้ฟังก์ชันอื่น ( bar) หากเรียกใช้การbar()เพิ่มHttpErrorฉันต้องการจัดการเป็นพิเศษหากรหัสสถานะคือ 404 มิฉะนั้นจะเพิ่มอีกครั้ง

ฉันพยายามที่จะเขียนทดสอบหน่วยบางรอบนี้ฟังก์ชั่นการเยาะเย้ยออกมาเรียกร้องให้foo bar()น่าเสียดายที่ฉันไม่สามารถรับการเรียกร้องที่เยาะเย้ยbar()ให้เพิ่มข้อยกเว้นซึ่งถูกexceptบล็อกของฉันได้

นี่คือรหัสของฉันที่แสดงถึงปัญหาของฉัน:

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

ผมทำตามเอกสารจำลองที่บอกว่าคุณควรตั้งค่าside_effectของMockอินสแตนซ์ไปยังExceptionระดับที่จะมีการเพิ่มฟังก์ชั่นล้อเลียนข้อผิดพลาด

ฉันยังดู Q & As อื่น ๆ ที่เกี่ยวข้องของ StackOverflow และดูเหมือนว่าฉันกำลังทำสิ่งเดียวกับที่พวกเขาทำเพื่อก่อให้เกิดและข้อยกเว้นที่จะถูกยกขึ้นโดยการเยาะเย้ยของพวกเขา

เหตุใดจึงตั้งค่าside_effectของbarMockไม่ได้ก่อให้เกิดความคาดหวังExceptionที่จะได้รับการเลี้ยงดู? ถ้าฉันทำอะไรแปลก ๆ ฉันจะทดสอบตรรกะในexceptบล็อกของฉันได้อย่างไร


ฉันค่อนข้างแน่ใจว่ามีการเพิ่มข้อยกเว้นของคุณแต่ฉันไม่แน่ใจว่าคุณตั้งresp.statusรหัสที่นั่นอย่างไร ในกรณีที่ไม่HTTPErrorมาจากไหน?
Martijn Pieters

@MartijnPieters HttpErrorเป็นคลาสที่กำหนดไว้ในlib ของ Googleapiclientซึ่งเราใช้ใน GAE มัน__init__ถูกกำหนดด้วยพารามิเตอร์(resp, content)ดังนั้นฉันจึงพยายามสร้างอินสแตนซ์จำลองสำหรับการตอบสนองพร้อมระบุรหัสสถานะที่เหมาะสม
Jesse Webb

ขวาดังนั้นมันจึงเป็นชั้นนี้ ; แต่คุณไม่จำเป็นต้องใช้return_valueแล้ว respไม่ได้ถูกเรียกว่า
Martijn Pieters

1
ฉันลองใช้รหัสของฉันอีกครั้งโดยไม่ใช้HttpErrorและลองใช้แค่Exceptionอินสแตนซ์ทั่วไปแทน นี้ทำงานได้อย่างสมบูรณ์ ซึ่งหมายความว่าต้องมีส่วนเกี่ยวข้องกับวิธีที่ฉันกำหนดค่าHttpErrorอินสแตนซ์ซึ่งอาจเกี่ยวข้องกับวิธีที่ฉันสร้างMockอินสแตนซ์สำหรับการตอบสนอง
Jesse Webb

คำตอบ:


142

การเยาะเย้ยของคุณเพิ่มข้อยกเว้นได้ดี แต่error.resp.statusค่าหายไป แทนที่จะใช้return_valueเพียงบอกMockว่าstatusเป็นแอตทริบิวต์:

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

อาร์กิวเมนต์คำหลักเพิ่มเติมที่Mock()จะตั้งเป็นแอตทริบิวต์บนวัตถุผลลัพธ์

ฉันใส่คำจำกัดความfooและbarคำจำกัดความของคุณไว้ในmy_testsโมดูลเพิ่มในHttpErrorชั้นเรียนเพื่อที่ฉันจะได้ใช้มันด้วยจากนั้นการทดสอบของคุณก็จะประสบความสำเร็จ:

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

คุณสามารถเห็นprint '404 - %s' % error.messageเส้นวิ่งได้ แต่ฉันคิดว่าคุณต้องการใช้ที่error.contentนั่นแทน นั่นคือแอตทริบิวต์ที่HttpError()ตั้งจากอาร์กิวเมนต์ที่สองในอัตราใดก็ได้


2
side_effectเป็นส่วนที่สำคัญ
แดเนียลบัตเลอร์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.