对我的python函数的异常测试感到困惑

问题描述

我有一个函数process_payment,我想对其进行异常测试:

def process_payment(event,context):

    try:
        payDictionary= json.loads(event["body"])
        if "payment_method_id" in payDictionary:
            intent = stripe.PaymentIntent.create(
                payment_method = payDictionary['payment_method_id'],amount = 1000,currency = 'usd',payment_method_types=["card"]
                )
            print(intent.status)
            if intent.status == 'succeeded':
                return {
                        "statusCode": 200,"body": json.dumps({
                            'message': 'Payment succeeded',}),}          
    except Exception as e:
        return {
                "body": json.dumps({
                    "message": 'Payment Failed. '+ str(e),}

我想对上述功能进行异常测试,所以我使用unittests框架编写了以下代码进行测试:

import unittest
from unittest import mock
from unittest.mock import patch,Mock
import json
import stripe
from stripe.util import convert_to_stripe_object
from . import app

       def test_process_payment_exception(self):
            event = {
                'httpMethod': 'POST','body': '{"payment_method_id":"pm_1HGTb2GPqNNATumTCzrTXZ9e"}'
                }
           
            response = {}            
    
            with mock.patch('stripe.PaymentIntent.create',side_effect= Exception) as mock_process_payment:
                stripe_obj = convert_to_stripe_object(response)
                mock_process_payment.return_value= stripe_obj
                self.assertRaises(Exception,app.process_payment,event,"")

此测试代码产生以下响应:

======================================================================
FAIL: test_process_payment_exception (hello_world.test_app.TestStudent)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\...\sam-app\hello_world\test_app.py",line 524,in test_process_payment_exception
    app.process_payment(event,"")
AssertionError: Exception not raised

----------------------------------------------------------------------
Ran 2 tests in 0.024s

Failed (failures=1)

我正在努力找出如何对此功能进行异常测试而无需对我的原始代码进行其他更改

编辑:

我将代码更改如下:

def test_process_payment_exception(self):
    event = {
        'httpMethod': 'POST','body': '{"payment_method_id":"pm_1HGTb2GPqNNATumTCzrTXZ9e"}'
    }
            
    def mock_method():
        raise Exception("someMessage")

    with mock.patch('stripe.PaymentIntent.create') as mock_process_payment:
        stripe_obj = convert_to_stripe_object(response)
        mock_process_payment.side_effect= mock_method
        ret= app.process_payment(event,"")
        print(ret)
        getmessage= json.loads(ret['body'])
        getmessageFinal= getmessage["message"]

        self.assertEqual("someMessage",getmessageFinal)

然后产生以下响应:

Traceback (most recent call last):
  File "C:\Users\...\sam-app\hello_world\test_app.py",line 536,in test_process_payment_exception
    ....
......
+ Payment Failed. mock_method() got an unexpected keyword argument 'payment_method'

-------------------- >> begin captured stdout << ---------------------
Payment Failed. mock_method() got an unexpected keyword argument 'payment_method'

--------------------- >> end captured stdout << ----------------------

----------------------------------------------------------------------
Ran 2 tests in 0.024s

Failed (failures=1)

我不明白为什么会看到mock_method() got an unexpected keyword argument 'payment_method'

解决方法

您的第二个变体几乎是正确的。问题在于副作用的调用方式与原始方法(create)相同,例如就像这样:

mock_method(payment_method,amount,currency,...)

(所有这些都是关键字参数)。由于mock_method没有关键字 定义参数后,您会收到该错误消息(仅提及第一个缺少的参数)。

摘自documentation(强调相关部分):

side_effect :每当调用Mock时都要调用的函数。请参阅side_effect属性。对于引发异常或动态更改返回值很有用。 使用与模拟相同的参数调用该函数,除非返回DEFAULT,否则将此函数的返回值用作返回值。

因此,您必须考虑这些参数。如果您对实际参数不感兴趣,则可以使用通用符号*args,**kwargs,其中args表示所有位置参数(作为列表),kwargs表示所有kwyword参数(作为字典) )。

您的情况就足够了:

def test_process_payment_exception(self):
    event = {
        'httpMethod': 'POST','body': '{"payment_method_id":"pm_1HGTb2GPqNNATumTCzrTXZ9e"}'
    }
            
    def mock_method(*args,**kwargs):
        raise Exception("The provided PaymentMethod ...")

    with mock.patch('stripe.PaymentIntent.create') as mock_process_payment:
        stripe_obj = convert_to_stripe_object(response)
        ...

旁注:“付款失败:”已经由调用代码添加了前缀,因此您不应将其置于异常中。

话虽如此,在您的情况下,将异常直接分配给side_effect更容易:

with mock.patch('stripe.PaymentIntent.create') as mock_process_payment:
    mock_process_payment = Exception("someMessage")
    stripe_obj = convert_to_stripe_object(response)

除了分配函数(如上所示)或值列表之外,还可以直接为side_effect分配一个异常(不是异常类)。