Python单元测试(1) —— Mock模块

1.关于单元测试的思考

怎样对游戏进行单元测试是我在一直思考的问题,我们的游戏服务端是用Python写的,我们开发的测试方式确实很原始,服务端游戏逻辑写完后,都要借助于客户端链接上服务器,然后利用GM指令,或者其他蹩脚的方式才能测试服务端的逻辑是否正确,这个过程存在很多显而易见的弊端:

  1. 测试流程太过繁琐,并且测试点有可能不全面
  2. 没有规范的单元测试,自动化测试也很难实施
  3. 单元测试也是理解代码逻辑的一种方式,如果单元测试都没有,理解代码的渠道就少了一层

目前的困难:

  1. Python2.7 有自己的单元测试框架,可以好好的利用这个模块,怎样将此模块与游戏逻辑结合在一起也是要仔细考虑的问题
  2. 依赖于客户端的登录是因为一个模块的逻辑依赖于很多其他模块,使用客户端端登录,其他模块自然也就存在了,怎样摆脱客户端,完全在服务端进行单元测试也是一个难点
  3. 单元测试模块怎样构建,怎样可扩展

单元测试我也是最近开始研究,这篇博文不仅写给大家,也写给我自己。我也将会将自己对单元测试的理解写成一个系列,今天先来说说对Python Mock模块的理解。

2.Python Mock

Mock模块在Python3.3被加入到Python标准库中,如果Python2.x要使用Mock模块可以使用pip进行安装。

> sudo pip install mock

先罗列一下Mock模块的几个关键的内容:

  1. Mock类本身
  2. MagicMock
  3. patch修饰器

3.Mock

介绍Mock模块首先要说一下Mock类,Mock类的构造函数如下:

class Mock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, unsafe=False, **kwargs)

个人比较关注如下几个参数:

spec

spec的参数可以是一个Python类。

>>> class Foo(object):
...     val_a = 1
...     def func_a(self):
...             print "call in func_a"
...
>>> m = Mock(spec = Foo)
>>> m.val_a
<Mock name='mock.val_a' id='4501041936'>
>>> m.func_a()
<Mock name='mock.func_a()' id='4501044176'>
>>> m.func_b()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "mock/mock.py", line 717, in __getattr__
raise AttributeError("Mock object has no attribute %r" % name)
AttributeError: Mock object has no attribute 'func_b'

Foo类有一个参数val_a和方法func_a,实例m调用val_afunc_a都没有问题,但是调用Foo没有的参数或者方法则会抛出AttributeError异常。

return_value

return_value定义mock被调用时的返回值。

>>> m = Mock(return_value = 1)
>>> m
<Mock id='4501997840'>
>>> m()
1

side_effect

side_effect和return_value正好相反,side_effect赋值后,mock的调用将会是side_effect的内容。side_effect可以是一个exception,也可以是一个可迭代的实例。

>>> li = [1,2,3]
>>> m = Mock(side_effect = li)
>>> m()
1
>>> m()
2
>>> m()
3
>>> m()
Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "mock/mock.py", line 1063, in __call__
        return _mock_self._mock_call(*args, **kwargs)
      File "mock/mock.py", line 1122, in _mock_call
        result = next(effect)
      File "mock/mock.py", line 127, in next
        return _next(obj)
StopIteration

Reference

  1. 使用 Python Mock 类进行单元测试 - http://www.oschina.net/translate/unit-testing-with-the-python-mock-class
  2. Python Mock Doc - https://docs.python.org/3/library/unittest.mock.html