使用模拟在python中实现干运行模式

时间:2014-05-15 17:52:52

标签: python mocking

假设您有一个Python模块,它在某些时候会调用具有副作用的方法,比如写入文件。假设您想要一个干运行模式,它只假装做它应该做的事情,但实际上并没有这样做。应该如何实现这种以不同方法呈现的干运行方面。 e.g。

import os.path

class Foo

  def ReadSomething(self):
    with open('/some/path', 'r') as f:
      print f.read()

  def WriteSomething(self, content):
    if not os.path.isfile('/some/path'):
      with open('/some/path', 'w') as f:
        f.write(content)

所以我想到使用unittest.mock库来基于像dry_run这样的变量动态修补所有外部API。 这就是:

@DryRunPatcher('open')
@DryRunPatcher(os.path, 'isfile', return_value=True)
def WriteSomething(self, content, mock_isfile, mock_open):

然后DryRunPatcher会是这样的:

def DryRunPatcher(*patch_args, **patch_kwargs):
  def Decorator(func):
    def PatchIfDrunRun(*args, **kwargs):
      self_arg = args[0]  # assuming it's a method
      if self_arg._dry_run_mode:
        with mock.patch.object(*patch_args, **patch_kwargs):
          return func(*args, **kwargs)
      else:
        return func(*args, **kwargs)
    return PatchIfDryRun
  return Decorator

上面的代码可能不起作用,但你明白了。然而问题是我猜模拟只能用于单元测试。还有什么可以用来修补外部API来实现干运行模式?我更喜欢这种方法本身对干运行模式不可知,并且不必自己包含每个具有副作用的调用。

2 个答案:

答案 0 :(得分:0)

我不确定我是否理解你在寻找什么,但让我们试一试:

<强> CODE:

def fake(state=False):
    def wrapper(function):
        def fakefunc(self, *args, **kwargs):
            print("I'm doing nothing!")
        if state:
            return fakefunc
        return function
    return wrapper


class Foo:

    @fake(True)
    def write_something(self, content):
        if not os.path.isfile('/some/path'):
            with open('/some/path', 'w') as f:
                f.write(content)

<强> 样本:

foo = Foo()
foo.write_something('hello fake!')

<强> 输出:

I'm doing nothing!

答案 1 :(得分:0)

最近我遇到了类似的必要性:我为某些服务获得了REST API客户端,它有一些CRUD操作。我希望我的脚本可以在干运行模式下执行,因此用户可以检查日志并全面了解如果他将使用某些参数执行程序会发生什么。更重要的是,有些方法没有害处,应该执行(对某些对象进行递归读取),但有些方法应该避免。   阅读关于模拟方法的建议会让我(我认为)非常好的解决方案:你可以创建一个代理类,在那里你可以决定应该执行哪些方法以及哪些方法应该被嘲笑&#39;。通过模拟,您可以决定要做什么:只需打印消息跳过或在安全的地方(临时目录)执行类似的操作。   这是它的样子:

#!/usr/bin/env python


class Foo(object):
    def __init__(self, some_parameter):
        self._some_parameter = some_parameter

    def read_something(self):
        print 'Method `read_something` from class Foo: {}'.format(self._some_parameter)

    def write_something(self, content):
        print 'Method `write_something` from class Foo: {} {}'.format(self._some_parameter, content)


class FooDryRunProxy(Foo):
    def __init__(self, some_parameter, dry_run=False):
        super(FooDryRunProxy, self).__init__(some_parameter=some_parameter)
        self._dry_run = dry_run

    def __getattribute__(self, name):
        attr = object.__getattribute__(self, name)
        is_dry = object.__getattribute__(self, '_dry_run')
        if is_dry and hasattr(attr, '__call__') and name.startswith('write_'):
            def dry_run_method(*args, **kwargs):
                func_args = list()
                if args:
                    func_args.extend(str(a) for a in args)
                if kwargs:
                    func_args.extend('%s=%s' % (k, v) for k, v in kwargs.items())

                print("[DRY-RUN] {name}({func_args})".format(name=name, func_args=', '.join(func_args)))

            return dry_run_method
        else:
            return attr


if __name__ == '__main__':
    foo_normal = FooDryRunProxy(some_parameter='bar')
    foo_normal.read_something()
    foo_normal.write_something('got-something-?')

    foo_dry_run = FooDryRunProxy(some_parameter='bar', dry_run=True)
    foo_dry_run.read_something()
    foo_dry_run.write_something('got-something-?')

以下是它的输出:

Method `read_something` from class Foo: bar
Method `write_something` from class Foo: bar got-something-?

Method `read_something` from class Foo: bar
[DRY-RUN] write_something(got-something-?)

当然,这不是100%可靠的解决方案,特别是如果您打算包装的代码是第三方并且可能以某种方式改变。但至少它可以帮助你,如果你有一个庞大的课程,你不想覆盖所有的方法。 希望它会对某人有所帮助。