我想在生产代码中模拟某些类的任何实例上的方法,以便于测试。 Python中有没有可以促进这个的库?
基本上,我想要执行以下操作,但是在Python中(以下代码是Ruby,使用Mocha库):
def test_stubbing_an_instance_method_on_all_instances_of_a_class
Product.any_instance.stubs(:name).returns('stubbed_name')
assert_equal 'stubbed_name', SomeClassThatUsesProduct.get_new_product_name
end
从上面要注意的重要一点是我需要在类级别上模拟它,因为我实际上需要在我正在测试的事物创建的实例上模拟方法。
用例:
我有一个类QueryMaker
,它在RemoteAPI
的实例上调用方法。我想模拟RemoteAPI.get_data_from_remote_server
方法来返回一些常量。如何在测试中执行此操作,而无需在RemoteAPI
代码中放置特殊情况以检查其运行的环境。
我想要的实例:
# a.py
class A(object):
def foo(self):
return "A's foo"
# b.py
from a import A
class B(object):
def bar(self):
x = A()
return x.foo()
# test.py
from a import A
from b import B
def new_foo(self):
return "New foo"
A.foo = new_foo
y = B()
if y.bar() == "New foo":
print "Success!"
答案 0 :(得分:47)
在测试时需要模拟方法很常见,并且有很多工具可以帮助你在Python中使用它。像这样的“猴子修补”类的危险在于,如果之后撤消,那么在整个测试过程中,类已被修改用于所有其他用途。
我的库mock是最受欢迎的Python模拟库之一,它包含一个名为“patch”的帮助程序,可帮助您在测试期间安全地修补对象和类的方法或属性。
模拟模块可从以下网址获得:
http://pypi.python.org/pypi/mock
补丁装饰器可以用作上下文管理器或测试装饰器。您既可以使用它自己修补功能,也可以使用它自动修补可配置的Mock对象。
from a import A
from b import B
from mock import patch
def new_foo(self):
return "New foo"
with patch.object(A, 'foo', new_foo):
y = B()
if y.bar() == "New foo":
print "Success!"
这会自动处理你的取消修补。你可以在没有自己定义模拟功能的情况下逃脱:
from mock import patch
with patch.object(A, 'foo') as mock_foo:
mock_foo.return_value = "New Foo"
y = B()
if y.bar() == "New foo":
print "Success!"
答案 1 :(得分:2)
最简单的方法可能是使用类方法。你真的应该使用一个实例方法,但是创建它们很痛苦,而有一个内置函数可以创建一个类方法。使用类方法,您的存根将获得对类(而不是实例)的引用作为第一个参数,但由于它是存根,这可能无关紧要。所以:
Product.name = classmethod(lambda cls: "stubbed_name")
请注意,lambda的签名必须与您要替换的方法的签名匹配。当然,由于Python(如Ruby)是一种动态语言,因此无法保证在你开始实际操作之前,有人不会为你的其他东西切换你的存根方法,尽管我希望你能很快知道如果发生这种情况。
编辑:进一步调查后,您可以省略classmethod()
:
Product.name = lambda self: "stubbed_name"
我试图尽可能地保留原始方法的行为,但看起来它实际上并不是必需的(并且不会保留我希望的行为,无论如何)。
答案 2 :(得分:1)
我不太了解Ruby,无法确切地告诉您要做什么,但请查看__getattr__
方法。如果您在类中定义它,Python将在代码尝试访问未定义的类的任何属性时调用它。由于您希望它是一个方法,因此需要动态创建它返回的方法。
>>> class Product:
... def __init__(self, number):
... self.number = number
... def get_number(self):
... print "My number is %d" % self.number
... def __getattr__(self, attr_name):
... return lambda:"stubbed_"+attr_name
...
>>> p = Product(172)
>>> p.number
172
>>> p.name()
'stubbed_name'
>>> p.get_number()
My number is 172
>>> p.other_method()
'stubbed_other_method'
另请注意,__getattr__
需要不使用您的类的任何其他未定义属性,否则它将无限递归,调用__getattr__
以获取不属于该属性的属性存在。
... def __getattr__(self, attr_name):
... return self.x
>>> p.y
Traceback (most recent call last):
#clipped
RuntimeError: maximum recursion depth exceeded while calling a Python object
如果您只想从测试代码而不是生产代码那里做,那么将您的普通类定义放在生产代码文件中,然后在测试代码中定义__getattr__
方法(未绑定) ,然后将其绑定到您想要的类:
#production code
>>> class Product:
... def __init__(self, number):
... self.number = number
... def get_number(self):
... print "My number is %d" % self.number
...
#test code
>>> def __getattr__(self, attr):
... return lambda:"stubbed_"+attr_name
...
>>> p = Product(172)
>>> p.number
172
>>> p.name()
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
AttributeError: Product instance has no attribute 'name'
>>> Product.__getattr__ = __getattr__
>>> p.name()
'stubbed_name'
我不确定这会如何对已使用__getattribute__
的类做出反应(与__getattr__
相反,__getattribute__
会为所有属性调用,无论它们是否存在)
如果您只想对已经存在的特定方法执行此操作,那么您可以执行以下操作:
#production code
>>> class Product:
... def __init__(self, number):
... self.number = number
... def get_number(self):
... return self.number
...
>>> p = Product(172)
>>> p.get_number()
172
#test code
>>> def get_number(self):
... return "stub_get_number"
...
>>> Product.get_number = get_number
>>> p.get_number()
'stub_get_number'
或者如果你真的想要优雅,你可以创建一个包装函数来简化多种方法:
#test code
>>> import functools
>>> def stubber(fn):
... return functools.wraps(fn)(lambda self:"stub_"+fn.__name__)
...
>>> Product.get_number = stubber(Product.get_number)
>>> p.get_number()
'stub_get_number'
答案 3 :(得分:1)
Mock是这样做的方式,好吧。 确保您在从类创建的任何实例上修补实例方法可能有点棘手。
# a.py
class A(object):
def foo(self):
return "A's foo"
# b.py
from a import A
class B(object):
def bar(self):
x = A()
return x.foo()
# test.py
from a import A
from b import B
import mock
mocked_a_class = mock.Mock()
mocked_a_instance = mocked_a_class.return_value
mocked_a_instance.foo.return_value = 'New foo'
with mock.patch('b.A', mocked_a_class): # Note b.A not a.A
y = B()
if y.bar() == "New foo":
print "Success!"
在docs中引用,在para开始&#34;为补丁类上的实例方法配置返回值...&#34;
答案 4 :(得分:0)
#Orignal Class definition - path "module.Product"
class Product:
def method_A(self):
# do something
pass
def method_B(self):
self.random_attr = 1
#Test case
from module import Product
class MockedProduct(Product):
def method_B(self):
self.random_attr = 2
with mock.patch('module.Product', new=MockedProduct):
#Write test case logic here
#Now method_B function call on product class instance should return 2
#instead of 1