Python class 单元测试时未正确模拟方法

Python class method not getting mocked properly while unittesting

我已经使用 flask_restful 创建了我的 API 之一。名为 TotalUserResponse 的资源或 API class 有一个名为 process_get_request 的方法,我正在尝试对其进行单元测试。

def process_get_request(self):
    params = parser_get.parse_args()
    user_id = params.get('user_id')
    if not user_id:
        raise ValueError("User id is empty")

    user = session.query(User).get(user_id)
    if not user:
        raise MyValidationError("User not found")

    # total applied, favourited and archived
    aggregated_actions = self.get_aggregated_actions(user_id)
    response = dict(applied=aggregated_actions[0],
                    favourited=aggregated_actions[1],
                    archived=aggregated_actions[2])

    return response

单元测试-

@mock.patch('application.resources.user_response.TotalUserResponse', autospec=True)
@mock.patch('application.models.session.query', autospec=True)
@mock.patch('flask_restful.reqparse.RequestParser.parse_args', autospec=True)
def test_process_get_request(self, parse_args_mock, query_mock, total_user_response_mock):

    parse_args_mock.return_value = dict(user_id='xxxxxx')
    query_mock.return_value.get.return_value = 'something non empty'
    expected_applied, expected_favourited, expected_archived = 5, 10, 20
    total_user_response_mock.return_value.get_aggregated_actions.return_value = (expected_applied, expected_favourited, expected_archived)

    expected_response = dict(applied=expected_applied,
                             favourited=expected_favourited, archived=expected_archived)

    self.assertEqual(self.total_user_response.process_get_request(), expected_response)

我的单元测试失败说 -

AssertionError: {'applied': <MagicMock name='query().filter().first().__getitem__()' id='1543892 [truncated]... != {'applied': 5, 'archived': 20, 'favourited': 10}

从上面的错误消息中,我了解到 get_aggregated_user_actions 没有被嘲笑。当我调试它时,我看到调试器也将我带入了函数内部,如果它被正确地模拟就不会发生这种情况。

怎么了?请帮帮我。

在你的测试中你使用 self.total_user_response.process_get_request():你调用 process_get_request() 它是 self.total_user_response 的方法而不是你之前嘲笑的方法。恕我直言,修补静态引用而不是对象引用更好更简单。修补对象使测试更复杂,更难understand/maintain,将其用作最后一个资源。

要减少此类问题,最好像这样重写您的测试:

@mock.patch('application.resources.user_response.TotalUserResponse.get_aggregated_actions', autospec=True)
@mock.patch('application.models.session.query.get', return_value='something non empty', autospec=True)
@mock.patch('flask_restful.reqparse.RequestParser.parse_args', autospec=True, return_value=dict(user_id='xxxxxx'))
def test_process_get_request(self, parse_args_mock, get_mock, get_aggregated_mock):
    expected_applied, expected_favourited, expected_archived = 5, 10, 20
    get_aggregated_mock.return_value = (expected_applied, expected_favourited, expected_archived)

    expected_response = dict(applied=expected_applied,
                             favourited=expected_favourited, archived=expected_archived)

    self.assertEqual(self.total_user_response.process_get_request(), expected_response)

可能有些错误,但我希望思路清晰:

  • 以最精确的方式模拟您真正需要模拟的内容。
  • 将装饰器中与测试无关的所有数据都放到测试方法中。