Post

unittest.mock

unittest.mock

mock是python中的测试库,主要用于提供测试桩。

  • 单元测试时,如果依赖其他服务,那么如何隔离进行测试呢。
  • 此时会建立一个测试桩,模拟所依赖的服务,从而避免依赖服务的干扰,专注测试所需的模块。
  • 而mock库就可以帮助我们很方便地建立测试桩,在python2中,mock是独立的库。
  • 在python3中,mock就被加入unittest单元测试库中了,足见mock库在单元测试中的不可或缺性。

基础概念

mock库中有两个基础的概念

  • 一个是Mock类,
    • Mock是一个灵活而且功能强大的类,用于模拟依赖的模块。
  • 一个是patch
    • patch用于在特定作用域内执行模拟。

Mock类

  • 直接创建Mock对象,
  • 简单情况下只需要通过return_value 指定返回值
1
2
3
4
m = Mock(return_value=3)
assert m() == 3
# 创建了Mock对象,这个对象会在执行之后始终返回3
# 在测试中如果有模块需要返回特定值,就可以使用Mock类进行替换
  • 有了Mock类作为模拟对象,接着使用patch指定被模拟对象
  • 成功使用Mock类模拟了特定的方法,在with作用域内,调用被模拟方法product_func() 时,实际调用的都是Mock类的方法。
1
2
3
4
5
6
with patch(product_func_path) as mock_method:
    mock_method.return_value = 3
    assert product_func() == 3
# 将需要模拟的方法`product_func()` 的路径`product_func_path` 作为参数传递给patch方法,并创建Mock对象`mock_method` 进行模拟。
# 可以看到指定`mock_method` 的返回值为3
# 然后执行`product_func()` 方法,最终返回值为3。

Mock 类

Mock类的初始化方法:

1
2
3
4
5
6
class unittest.mock.Mock(spec=None, \
                         side_effect=None, \
                         return_value=DEFAULT,\
                         wraps=None, \
                         name=None, \
                         spec_set=None, unsafe=False, **kwargs)

创建参数中最重要的两个参数是side_effectreturn_value

  • return_value
    • 用于表示此Mock对象的不变的返回值。
    • 此返回值在第一次调用Mock对象时生成,后面多次调用对象,都返回此相同的返回值
  • side_effect
    • 用于指定可变的返回值或抛出特定的异常。
    • 此参数可以设置为下面三种类型:
      • 如果side_effect 设置为异常类,那么执行Mock对象时就会抛出此异常
      • 如果side_effect 设置为特定的方法,那么执行Mock对象时,就会将Mock对象执行的参数传递给side_effect 指定的方法,并将此方法返回的值作为Mock对象的返回值
      • 如果side_effect 设置为可迭代的对象,那么执行Mock对象就会从这个可迭代的对象中获取一个值进行返回

side_effect 参数的使用:

1
2
3
4
5
6
7
8
9
10
11
m = Mock(side_effect=Exception('boom'))
with pytest.raises(Exception):
    m()
# Mock类执行时,会抛出Exception,适用于测试会抛出特定异常的场景


m = Mock(side_effect=[3, 4, 5])
    assert m() == 3
    assert m() == 4
    assert m() == 5
# 多次调用Mock会返回不同的值,每次的值是从可迭代对象`[3, 4, 5]` 中返回一个值。适用于方法执行后会返回了一系列特定的值的情况

还有一种情况,可以设置Mock为特定的方法,每次调用Mock对象就是执行此方法。


Mock方法与属性

Mock类提供了一些方法与属性,方便进行测试

  • assert_called(*args, **kwargs)
    • 断言mock至少被调用一次
  • assert_called_once(*args, **kwargs)
    • 断言mock调用仅一次
  • assert_called_with(*args, **kwargs)
    • 断言mock以某种参数至少调用一次
  • assert_called_once_with(*args, **kwargs)
    • 断言mock以某种参数调用仅一次
  • assert_any_called(*args, **kwargs)
    • 断言mock以某种参数曾经被调用过,
    • 区别与上面的assert_called_with()assert_called_once_with()必须是最近的那次调用符合断言
  • assert_has_calls(calls, any_order=False)
    • 断言mock被按照的特定一组调用的方式调用过。
    • 如果any_order是False,那么必须满足calls中的调用,而且必须是连续的,
    • 如果any_order是True,那么就只需要执行了calls中的调用就可以了
  • assert_not_called()
    • 断言没有被调用
  • reset_mock(*args, return_value=False, side_effect=False)
    • 重置mock对象的所有调用
  • attach_mock()
    • 将mock附加在mock对象的属性上
  • called
    • mock是否被调用的值
  • call_count
    • 调用次数
  • return_value
    • 设置mock的返回值
  • call_args
    • 最后调用的参数
  • __class__
    • 指定mock的类型,支持isinstance()判断
  • NonCallableMock
    • 是不可调用的Mock类,
    • 适合模拟模拟不可调用的类对象
  • NonCallableMagicMock
    • 是不可调用的MagicMock类
  • MagicMock
    • 是Mock类的子类,
    • 与Mock相比,默认实现了大部分的魔术方法
    • Mock and MagicMock objects create all attributes and methods as you access them and store details of how they have been used.
    • You can configure them, to specify return values or limit what attributes are available, and then make assertions about how they have been used:

      1
      2
      3
      4
      5
      6
      7
      8
      
      from unittest.mock import MagicMock
      thing = ProductionClass()
      thing.method = MagicMock(return_value=3)
      thing.method(3, 4, 5, key='value')
      # 3
      
      thing.method.assert_called_with(3, 4, 5, key='value')
      
      
  • side_effect
    • 可以设置为特定的方法,迭代器或者一个异常。
    • 设置为None可以取消side_effect的影响
    • side_effect allows you to perform side effects, including raising an exception when a mock is called:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mock = Mock(side_effect=KeyError('foo'))
mock()
# Traceback (most recent call last):
#  ...
# KeyError: 'foo'

values = {'a': 1, 'b': 2, 'c': 3}
def side_effect(arg):
  return values[arg]

mock.side_effect = side_effect
mock('a'), mock('b'), mock('c')
# (1, 2, 3)

mock.side_effect = [5, 4, 3, 2, 1]
mock(), mock(), mock()
# (5, 4, 3)

patch

patch用于在特定范围内执行模拟。

  • patch有多种使用方法,
  • 可以使用装饰器方法,
  • 也可以使用with语句,
  • 也可以通过start()stop() 方法指定模拟的开始和结束。

首先查看patch的初始化方法:

1
unittest.mock.patch(target, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)

target

  • 用于指定被模拟的对象,是一个类似package.module.className 格式的字符串
  • 其次参数new_callable 可以用于指定最终创建的模拟对象类型,
  • 默认情况下是MagicMock类型的Mock对象

patch 使用方法

  1. 装饰器的方式
    • 通过patch装饰器,通过target指定参数指定被模拟的对象
    • 而创建的模拟对象会通过参数传递给方法
      • 代码模拟了 someClass 类,
      • 创建的模拟对象通过参数 mock_class 传递给方法
      • 在方法中可以看到mock_classsomeClass 类型一致。
      • 通过此方式指定模拟的范围是函数内部,当函数执行结束,模拟就不再生效
1
2
3
@patch('__main__.someClass')
def function(normal_arguments, mock_class)
    assert mock_class is someClass
  1. with语句的使用方式
    • 通过上下文管理的方式设置模拟范围。
    • 使用此方式创建的Mock对象通过as 语句传递给后面使用
      • 模拟了someClass 类,模拟对象为mock_class
      • 在方法中,可以看到mock_classsomeClass 类型一致,
      • 而被模拟对象someClass 执行后的结果与mock_class 的返回值,即通过return_value 指定的值一致
1
2
3
4
with patch('__main__.someClass') as mock_class:
    ret = mock_class.return_value
    assert mock_class is someClass
    assert someClass() is ret
  1. 手动指定模拟的范围
    • 使用mock_class 模拟了someClass
    • 最终通过patch.start()patch.stop() 指定模拟的开始和结束
1
2
3
4
5
patcher = patch('__main__.someClass')

mock_class = patcher.start()
assert someClass is mock_class
patcher.stop()
  1. patch.object()
    • 用于模拟对象的属性,使用Mock对象模拟对象的属性。
    • 初始化时使用参数target 指定对象,使用参数attribute 设置模拟的属性。
  2. patch.dict()
    • 用于模拟dict类型的对象,在模拟结束时恢复被模拟对象的数据
  3. patch.multiple()
    • 用于同时模拟多个对象

Python mock Patch os.environ and return value

1
2
3
4
5
6
7
8
9
10
11
12
def func():
    print os.environ["mytemp"]


def test_func():
    k = mock.patch.dict(os.environ, {"mytemp": "mytemp"})
    k.start()
    func()
    k.stop()


test_func()

.

This post is licensed under CC BY 4.0 by the author.

Comments powered by Disqus.