Python 如何测试抽象工厂

Python 如何测试抽象工厂,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产品

我正在尝试使用Python中的抽象工厂,使用以下3个文件进行最低限度的复制:

test_factory.py factory.py product.py
我很难弄清楚的是如何模拟此代码,以验证
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()