调用 python Mock 时如何运行函数(以获得副作用)?

int*_*ris 6 python unit-testing mocking

我正在模拟(使用 python Mock)一个函数,我想在其中返回一个值列表,但是在列表中的某些项目中,我希望也发生副作用(在调用模拟函数的地方)。这怎么做最容易?我正在尝试这样的事情:

import mock
import socket

def oddConnect():
  result = mock.MagicMock()  # this is where the return value would go
  raise socket.error  # I want it assigned but also this raised

socket.create_connection = mock(spec=socket.create_connection,
  side_effect=[oddConnect, oddConnect, mock.MagicMock(),])
# what I want: call my function twice, and on the third time return normally
# what I get: two function objects returned and then the normal return

for _ in xrange(3):
  result = None
  try:
    # this is the context in which I want the oddConnect function call
    # to be called (not above when creating the list)
    result = socket.create_connection()
  except socket.error:
    if result is not None:
      # I should get here twice
      result.close()
      result = None
  if result is not None:
    # happy days we have a connection
    # I should get here the third time
    pass
Run Code Online (Sandbox Code Playgroud)

我从套接字内部复制的 except 子句(并且它是内部 if),并希望通过我的代码副本来验证我“测试”了该路径。(我不明白套接字如何获得该代码(在仍然引发异常的同时设置目标,但这不是我关心的问题,只有我验证我可以复制该代码路径。)这就是我想要的原因调用模拟时发生的效果,而不是我构建列表时发生的效果。

cal*_*531 7

根据unittest.mock文档side_effect

如果您传入一个可迭代对象,它将用于检索一个迭代器,该迭代器必须在每次调用时产生一个值。该值可以是要引发的异常实例,也可以是要从对模拟的调用返回的值(DEFAULT处理与函数案例相同)。

因此,您的socket.create_connection模拟将返回前两次调用的函数 oddConnect,然后返回Mock最后一次调用的对象。据我了解,您想要模拟create_connection对象来实际调用这些函数作为副作用而不是返回它们。

我发现这种行为很奇怪,因为您希望side_effect,side_effect在任何情况下都意味着,而不是 return_value。我想这是因为该return_value财产的价值必须按原样解释。举例来说,如果你的模拟了return_value=[1, 2, 3],你会选择模拟回报[1, 2, 3]每一个呼叫,或将其返回1的第一个电话?

解决方案

幸运的是,这个问题有一个解决方案。根据文档,如果您将单个函数传递给side_effect,那么每次调用模拟都会调用(不返回)该函数。

如果你传入一个函数,它将使用与模拟相同的参数被调用,除非函数返回DEFAULT单例,否则对模拟的调用将返回函数返回的任何内容。如果函数返回,DEFAULT则模拟将返回其正常值(来自 return_value)。

因此,为了达到预期的效果,你的side_effect函数每次被调用时都必须做一些不同的事情。您可以使用计数器和函数中的一些条件逻辑轻松实现这一点。请注意,为了使其工作,您的计数器必须存在于函数范围之外,因此在函数退出时计数器不会重置。

import mock
import socket

# You may wish to encapsulate times_called and oddConnect in a class
times_called = 0
def oddConnect():
  times_called += 1
  # We only do something special the first two times oddConnect is called
  if times_called <= 2:
    result = mock.MagicMock()  # this is where the return value would go
    raise socket.error  # I want it assigned but also this raised  

socket.create_connection = mock(spec=socket.create_connection,
  side_effect=oddConnect)
# what I want: call my function twice, and on the third time return normally
# what I get: two function objects returned and then the normal return

for _ in xrange(3):
  result = None
  try:
    # this is the context in which I want the oddConnect function call
    # to be called (not above when creating the list)
    result = socket.create_connection()
  except socket.error:
    if result is not None:
      # I should get here twice
      result.close()
      result = None
  if result is not None:
    # happy days we have a connection
    # I should get here the third time
    pass
Run Code Online (Sandbox Code Playgroud)