Python 如何测试抽象工厂
我正在尝试使用Python中的抽象工厂,使用以下3个文件进行最低限度的复制: test_factory.py factory.py product.pyPython 如何测试抽象工厂,python,python-3.x,factory,abstract-factory,Python,Python 3.x,Factory,Abstract Factory,我正在尝试使用Python中的抽象工厂,使用以下3个文件进行最低限度的复制: test_factory.py factory.py product.py 我很难弄清楚的是如何模拟此代码,以验证ConcreteProduct.\uuuu init\uuuu是否使用适当的值调用。由于测试文件从未看到product.py,因此我不确定如何实现这一点,或者这是否可能。我怀疑我的设计有更根本的错误。最简单的方法是补丁工厂。具体产品模块参考 因此,您的测试可以(未测试): 如果(且仅当)concrete产品
我很难弄清楚的是如何模拟此代码,以验证
ConcreteProduct.\uuuu init\uuuu
是否使用适当的值调用。由于测试文件从未看到product.py
,因此我不确定如何实现这一点,或者这是否可能。我怀疑我的设计有更根本的错误。最简单的方法是补丁工厂。具体产品模块参考
因此,您的测试可以(未测试):
如果(且仅当)concrete产品
工厂
模块设计中的引用在您的测试环境中不存在,您可以使用create=True
补丁
的属性将其注入
我想指出,工厂
模块中的具体产品
参考已经是工厂了。Python中的类是工厂,这不是一种类型化语言,工厂概念没有java那么严格。我来自Java背景,即使在python中,我仍然使用工厂,但当您需要操纵输入以创建正确的对象时,它们会变得非常有用,如果你的工厂方法只是一个参数传递到一个类引用,考虑删除中间的人。 到目前为止,我已经得到了一个由米歇尔DaMICO的答案导致的解决方案,并且它非常接近他的。
test\u factory.py
变为:
from factory import Factory
from unittest.mock import patch
@patch('product.ConcreteProduct.__init__', return_value=None)
def test_factory(mock_init):
factory = Factory.makeFactory()
product = factory.makeProduct('Hi there')
mock_init.assert_called_with('Hi there')
if __name__ == '__main__':
test_factory()
请注意,我正在修补产品。
,而不是工厂。
,因此我基本上避开了工厂.py
,模拟我知道它将要导入的内容。我不知道我对这样破坏封装的感觉如何,但老实说,这就是我对嘲笑的感觉
我更喜欢这个答案而不是另一个答案,因为它稍微短一点,而且据我所知,它可能是危险的:
默认情况下,修补程序将无法替换不存在的属性。如果
如果传入create=True,并且该属性不存在,则修补程序将
在调用修补函数时为您创建属性,然后
以后再删除它。这对于编写针对的测试非常有用
生产代码在运行时创建的属性。它离我们不远了
默认,因为它可能是危险的。打开它,你就可以写字了
通过对实际不存在的API的测试
我当然会对进一步的讨论感兴趣,因为我仍然觉得我可以学习一些更好的设计方法,使它更干净。Factory.makeFactory
应该返回cls()
,而不是ConcreteFactory()
,为了避免对特定子类的任何依赖关系。@chepner在这种情况下,它将返回一个工厂实例,其中的代码与我的代码相同。有没有更好的方法来构造它?cls
绑定到调用该方法的任何类,而不是定义该方法的类。对,我直接从Factory
调用它,因此cls
将是Factory
。如果ConcreteFactory
是Factory
的唯一子类,那么,将基类和子类分开没有多大意义;如果除了ConcreteFactory
之外还有另一个子级,那么为什么Factory.makeFactory
应该返回一个实例而不是另一个实例?通常,将使用参数调用工厂函数,以指定用于创建实例的类;一种方法是直接从要实例化的类调用makeFactory
,这非常有用。如果您想进一步讨论或有任何建议,请参阅我的答案。@anderspitman我已更改了我的答案,以澄清对创建
属性的误解。请注意,如果具体类
派生自其他类,并且不要重写它,则修补程序\uuuu初始化
可能会很危险(python不是Java,构造函数可以继承)。此外,如果init在测试中做了一些你不想做的事情,比如启动一个线程或打开一个远程连接,那么使用补丁\uuuuuu init\uuuuuu
,就像最后一次机会一样。否则,如果可以,请避免:补丁just public方法是一个好规则,如果可以,请避免补丁private和magic方法。根据此规则,测试将更加简单而且是可维护的。
from abc import ABCMeta, abstractmethod
from product import ConcreteProduct
class Factory(metaclass=ABCMeta):
@classmethod
@abstractmethod
def makeProduct(cls):
pass
@classmethod
def makeFactory(cls):
return ConcreteFactory()
class ConcreteFactory(Factory):
@classmethod
def makeProduct(cls, message):
return ConcreteProduct(message)
class ConcreteProduct(object):
def __init__(self, message):
self._message = message
def __str__(self):
return self._message
from factory import Factory
from unittest.mock import *
@patch("factory.ConcreteProduct")
def test_factory(mock_product_factory):
mock_product = mock_product_factory.return_value
factory = Factory.makeFactory()
product = factory.makeProduct('Hi there')
self.assertIs(product, mock_product)
mock_product_factory.assert_called_with('Hi there')
if __name__ == '__main__':
test_factory()
from factory import Factory
from unittest.mock import patch
@patch('product.ConcreteProduct.__init__', return_value=None)
def test_factory(mock_init):
factory = Factory.makeFactory()
product = factory.makeProduct('Hi there')
mock_init.assert_called_with('Hi there')
if __name__ == '__main__':
test_factory()