在 Python 中将不同的值传递给装饰器以进行单元测试

Passing different values to decorators for unit tests in Python

我有一种情况,我试图修改传递给我的 class 方法之一的装饰器的参数。代码看起来像这样:

class MyClass(object):
  @tryagain(retries=3)
  def mymethod(self, arg):
    ... do stuff ...

我的问题是我想在 运行 我的单元测试时将 "retries" 变量更改为小于 3 的值,但将其保持在“3”为生产代号。不幸的是,看起来我不能做这样的事情:

  @tryagain(retries=self.retries)
  def mymethod(self, arg):
    ... do stuff ...

  @tryagain(retries=MyClass.retries)
  def mymethod(self, arg):
    ... do stuff ...

因为 class 没有在参数传递给装饰器时定义(据我所知)。

我也试过像这样在模块中添加变量:

retries = 1
def MyClass(object):
    @tryagain(retries=retries)
    def mymethod(self, arg):
      ... do stuff ...

但是我似乎无法从我的单元测试中修改 "retries" 的值。还有其他方法可以完成我想做的事情吗?

我假设您尝试减少重试次数以提高测试速度。

如果是这样,修改重试次数变量似乎不是最好的方法。相反,您可以先在没有装饰器的情况下对函数 mymethod 进行单元测试,然后创建 mymethod 的模拟函数。我们称它为 mock_mymethod,用 @tryagain 装饰它并测试 `tryagain 的逻辑是否真的有效。

检查 mock module to see how to create a mock instance, this article 关于 mock 也值得一读。

您可以使用一个环境变量,从您的调用代码中设置(最好在此处设置一个默认值

import os
# ...
class MyClass(object):
    @tryagain(retries=int(os.environ['project_num_retries']))
    def mymethod(self, arg):
        print("mymethod")

或者使用"globals"类型的模块,例如:project_settings.py包含:

num_retries = 3

然后

import project_settings

class MyClass(object):
    @tryagain(retries=project_settings.num_retries)
    def mymethod(self, arg):
        print("mymethod")

但我不确定用测试信息装饰您的代码是您真正应该做的事 -- 怎么样:

class MyClass(object):
    def mymethod(self, arg):
        print("mymethod")

然后像 unittests.py:

DEV_TESTS = True  # Change to False for production
num_retries = 3 if not DEV_TESTS else 1

import <your class>
class UnitTests():
    def __init__(self):
        self.c = <your_class>.MyClass()

    @tryagain(retries=num_retries)
    def test_mymethod(self):
        self.c.mymethod("Foo")

t = UnitTests()
t.test_mymethod()

如果您愿意,这个 unittests.py 可以与类似 python 的 unittest package 一起使用:

DEV_TESTS = True  # Change to False for production
num_retries = 3 if not DEV_TESTS else 1

import unittest
import <your class>
class UnitTests(unittest.TestCase):
    def setUp(self):
        self.c = <your class>.MyClass()

    @tryagain(retries=num_retries)
    def test_mymethod(self):
        self.c.mymethod("Foo")

注意,我使用了以下 @tryagain 装饰器的简单示例,您的可能更复杂,需要对示例进行一些调整:

def tryagain(retries):
    def wrap(f):
        def wrapped_f(*args,**kwargs):
            for _ in xrange(retries):
                f(*args,**kwargs)
        return wrapped_f
    return wrap