跳转到主要内容

测试中创建Plone对象的构建模式

项目描述

ftw.builder

使用Builder模式在测试中创建Plone对象。

构建模式简化了对象的构建过程。在测试中,我们经常需要创建Plone对象,有时是一个对象,有时是一系列对象。使用构建模式可以让我们以DRY的方式完成这些操作,从而避免重复劳动。

from ftw.builder import create
from ftw.builder import Builder

def test_foo(self):
    folder = create(Builder('folder')
                    .titled('My Folder')
                    .in_state('published'))

安装

setup.py中将ftw.builder添加为(测试)依赖项

tests_require = [
    'ftw.builder',
    ]

setup(name='my.package',
      tests_require=tests_require,
      extras_require={'tests': tests_require})

用法

在测试用例中设置构建器会话

from ftw.builder import session

class TestPerson(unittest.TestCase):

    def setUp(self):
        session.current_session = session.factory()

    def tearDown(self):
        session.current_session = None

在plone项目中,你可以使用BUILDER_LAYER,你的测试层应该基于它。因此,会话管理由BUILDER_LAYER处理。

from ftw.builder.testing import BUILDER_LAYER

class MyPackageLayer(PloneSandboxLayer):

    defaultBases = (PLONE_FIXTURE, BUILDER_LAYER)

使用构建器在测试中创建对象

from ftw.builder import Builder
from ftw.builder import create
from my.package.testing import MY_PACKAGE_INTEGRATION_TESTING
from unittest import TestCase

class TestMyFeature(TestCase)

    layer = MY_PACKAGE_INTEGRATION_TESTING

    def test_folder_is_well_titled(self):
        folder = create(Builder('folder')
                        .titled('My Folder')
                        .in_state('published'))

        self.assertEquals('My Folder', folder.Title())

会话

BuilderSession为多个构建器保留配置。它由BUILDER_LAYER设置和销毁,可以通过set_builder_session_factory配置或替换为自定义会话。

自动提交

当使用功能测试层(plone.app.testing.FunctionalTesting)并执行浏览器测试时,有必要将新对象提交到ZODB。然而,当使用IntegrationTesting时,确保不提交任何内容至关重要,因为这会破坏测试隔离。

会话提供了auto_commit选项(默认禁用),在创建对象后将其提交到ZODB。由于默认禁用,您需要在功能测试用例中启用它。

提供了一个启用自动提交功能的默认会话工厂functional_session_factory

def functional_session_factory():
    sess = BuilderSession()
    sess.auto_commit = True
    return sess

您可以使用set_builder_session_factory在功能测试中替换默认会话工厂。确保您的固定装置也基于BUILDER_LAYER固定装置。

from ftw.builder.session import BuilderSession
from ftw.builder.testing import BUILDER_LAYER
from ftw.builder.testing import functional_session_factory
from ftw.builder.testing import set_builder_session_factory
from plone.app.testing import FunctionalTesting
from plone.app.testing import IntegrationTesting
from plone.app.testing import PLONE_FIXTURE
from plone.app.testing import PloneSandboxLayer


class MyPackageLayer(PloneSandboxLayer):
    defaultBases = (PLONE_FIXTURE, BUILDER_LAYER)

MY_PACKAGE_FIXTURE = MyPackageLayer()

MY_PACKAGE_INTEGRATION_TESTING = IntegrationTesting(
    bases=(MY_PACKAGE_FIXTURE, ),
    name="MyPackage:Integration")

MY_PACKAGE_FUNCTIONAL_TESTING = FunctionalTesting(
    bases=(MY_PACKAGE_FIXTURE,
           set_builder_session_factory(functional_session_factory)),
    name="MyPackage:Integration")

Plone对象构建器

创建Plone对象(Archetypes或Dexterity)时,有一些用于设置基本选项的方法。

  • within(container) - 告诉构建器在哪里创建对象

  • titled(title) - 命名对象

  • having(field=value) - 设置对象上任何字段的值

  • in_state(review_state) - 将对象设置为工作流配置的任何审阅状态

  • providing(interface1, interface2, ...) - 允许对象提供接口

  • with_property(name, value, value_type='string') - 设置属性

默认构建器

ftw.builder自带一些用于默认Plone内容类型的构建器,但理念是可以轻松地为您的类型创建自己的构建器或扩展现有构建器。

内置构建器包括:

  • folder - 创建文件夹

  • page(或document)- 创建页面(别名文档)

  • file - 创建文件

  • image - 创建图像

  • 集合(或主题) - 创建一个集合

  • 链接 - 创建一个链接

有两种构建实现,一种是(Plone < 5)实现,另一种是< Dexterity>(Plone >= 5)实现。当使用与Plone 4时,您可能想将构建器切换到< Dexterity>

from ftw.builder.content import at_content_builders_registered
from ftw.builder.content import dx_content_builders_registered
from ftw.builder.content import register_at_content_builders
from ftw.builder.content import register_dx_content_builders


# permanently
register_dx_content_builders(force=True)

# temporary
with dx_content_builders_registered():
    # do stuff
附加文件

默认的文件构建器允许您附加文件或创建包含虚拟内容的文件。图像构建器提供真实图像(1x1 px GIF)

file1 = create(Builder('file')
               .with_dummy_content())

file2 = create(Builder('file')
               .attach_file_containing('File content', name='filename.pdf')

image1 = create(Builder('image')
               .with_dummy_content())
用户构建器

默认情况下已注册一个“用户”构建器。

默认用户名为John Doe

john = create(Builder('user'))
john.getId() == "john.doe"
john.getProperty('fullname') == "Doe John"
john.getProperty('email') == "john@doe.com"
john.getRoles() == ['Member', 'Authenticated']

更改用户名也会更改用户ID和电子邮件地址。您还可以配置所有其他必要的事项

folder = create(Builder('folder'))
hugo = create(Builder('user')
              .named('Hugo', 'Boss')
              .with_roles('Contributor')
              .with_roles('Editor', on=folder))

hugo.getId() == 'hugo.boss'
hugo.getProperty('fullname') == 'Boss Hugo'
hugo.getProperty('email') == 'hugo@boss.com'
hugo.getRoles() == ['Contributor', 'Authenticated']
hugo.getRolesInContext(folder) == ['Contributor', 'Authenticated', 'Editor']
组构建器

“组”构建器帮助您创建组

folder = create(Builder('folder'))
user = create(Builder('user'))
group = create(Builder('group')
               .titled('Administrators')
               .with_roles('Site Administrator')
               .with_roles('Editor', on=folder)
               .with_members(user))

创建新构建器

想法是您为您的应用程序创建自己的构建器。这可能是创建单个Plone对象(Archetypes或Dexterity)的构建器,或者使用其他构建器创建一组对象的构建器。

创建Python构建器

为您的Python对象定义一个简单的构建器类,并将其注册到构建器注册表中

class PersonBuilder(object):

    def __init__(self, session):
        self.session = session
        self.children_names = []
        self.arguments = {}

    def of_age(self):
        self.arguments['age'] = 18
        return self

    def with_children(self, children_names):
        self.children_names = children_names
        return self

    def having(self, **kwargs):
        self.arguments.update(kwargs)
        return self

    def create(self, **kwargs):
        person = Person(
            self.arguments.get('name'),
            self.arguments.get('age'))

        for name in self.children_names:
            person.add_child(
                create(Builder('person').having(name=name, age=5))
            )

        return person

builder_registry.register('person', PersonBuilder)
创建Archetypes构建器

使用基类创建新的Archetypes构建器。设置和您自己的方法。

from ftw.builder.archetypes import ArchetypesBuilder
from ftw.builder import builder_registry

class NewsBuilder(ArchetypesBuilder):
    portal_type = 'News Item'

    def containing(self, text):
        self.arguments['text'] = text
        return self

builder_registry.register('news', NewsBuilder)
创建Dexterity构建器

使用基类创建新的Dexterity构建器。设置和您自己的方法。

from ftw.builder.dexterity import DexterityBuilder
from ftw.builder import builder_registry

class DocumentBuilder(DexterityBuilder):
    portal_type = 'dexterity.document'

    def with_dummy_content(self):
        self.arguments["file"] = NamedBlobFile(data='Test data', filename='test.doc')
        return self
事件

您可以在创建对象之前和之后执行某些操作

class MyBuilder(ArchetypesBuilder):

    def before_create(self):
        super(NewsBuilder, self).before_create()
        do_something()

    def after_create(self):
        do_something()
        super(NewsBuilder, self).after_create()
覆盖现有构建器

有时需要覆盖现有的构建器。要重新注册现有的构建器,可以使用标志

builder_registry.register('file', CustomFileBuilder, force=True)

在创建时向前移动冻结时钟

使用可以冻结时间

在冻结时间并创建多个对象时,它们都将具有相同的创建日期。这可能会导致排序顺序不一致。

为了解决这个问题,提供了一个,每次创建对象时都会将时钟向前移动。这意味着我们有不同的、一致的创作日期。

使用示例

from datetime import datetime
from ftw.builder import Builder
from ftw.builder import ticking_creator
from ftw.testing import freeze

with freeze(datetime(2010, 1, 1)) as clock:
    create = ticking_creator(clock, days=1)
    self.assertEquals(DateTime(2010, 1, 1),
                      create(Builder('folder')).created())
    self.assertEquals(DateTime(2010, 1, 2),
                      create(Builder('folder')).created())
    self.assertEquals(DateTime(2010, 1, 3),
                      create(Builder('folder')).created())

全局安装ticking creator很方便,这样如果构建器使用另一个构建器创建对象,它也会为嵌套构建器调用计时。这可以通过使用ticking creator作为上下文管理器来实现

from datetime import datetime
from ftw.builder import Builder
from ftw.builder import create
from ftw.builder import ticking_creator
from ftw.testing import freeze

with freeze(datetime(2010, 1, 1)) as clock:
    with ticking_creator(clock, days=1):
        self.assertEquals(DateTime(2010, 1, 1),
                          create(Builder('folder')).created())
        self.assertEquals(DateTime(2010, 1, 2),
                          create(Builder('folder')).created())
        self.assertEquals(DateTime(2010, 1, 3),
                          create(Builder('folder')).created())

其他构建器

Python包构建器

Python包构建器在文件系统上构建Python包。

  • 创建setup.py

  • 支持命名空间包

  • 构建egg-info

  • 按需创建configure.zcml

示例

>>> import tempfile
>>> tempdir = tempfile.mkdtemp()

>>> package = create(Builder('python package')
...                  .at_path(tempdir)
...                  .named('my.package')
...
...                  .with_root_directory('docs')
...                  .with_root_file('docs/HISTORY.txt', 'CHANGELOG...')
...                  .with_file('resources/print.css', 'body {}', makedirs=True)
...
...                  .with_subpackage(Builder('subpackage')
...                                   .named('browser')))
>>>
>>> with package.imported() as module:
...     print module
...
<module 'my.package' from '...../tmpcAZhM2/my/package/__init__.py'>

还可以创建/加载ZCML,您只需要一个堆叠的配置上下文。Plone的测试层提供配置上下文,但请注意,组件注册表不是隔离的。您可能想使用隔离组件注册表。

package = create(
    Builder('python package')
    .named('the.package')
    .at_path(self.layer['temp_directory'])

    .with_subpackage(
        Builder('subpackage')
        .named('browser')

        .with_file('hello_world.pt', '"Hello World"')
        .with_zcml_node('browser:page',
                        **{'name': 'hello-world.json',
                           'template': 'hello_world.pt',
                           'permission': 'zope2.View',
                           'for': '*'})))

with package.zcml_loaded(self.layer['configurationContext']):
    self.assertEqual('"Hello World"',
                     self.layer['portal'].restrictedTraverse('hello-world.json')())

通用设置配置文件构建器

“通用设置配置文件”构建器帮助在Python包内构建配置文件

create(Builder('python package')
       .named('the.package')
       .at_path(self.layer['temp_directory'])

       .with_profile(Builder('genericsetup profile')
                     .with_fs_version('3109')
                     .with_dependencies('collective.foo:default')
                     .with_file('types/MyType.xml', '<object></object>',
                                makedirs=True)))

Plone升级步骤构建器

为包构建通用设置升级步骤

create(Builder('python package')
       .named('the.package')
       .at_path(self.layer['temp_directory'])

       .with_profile(Builder('genericsetup profile')
                     .with_upgrade(Builder('plone upgrade step')
                                   .upgrading('1000', '1001')
                                   .titled('Add some actions...')
                                   .with_description('Some details...'))))

ZCML文件构建器

ZCML构建器构建ZCML文件

create(Builder('zcml')
       .at_path('/path/to/my/package/configure.zcml')
       .with_i18n_domain('my.package')

       .include('.browser')
       .include('Products.GenericSetup', file='meta.zcml')
       .include(file='profiles.zcml')

       .with_node('i18n:registerTranslations', directory='locales'))

组件构建器

附带一些Plone部件的构建器,但想法是您可以轻松地为您的部件或扩展现有的构建器创建自己的构建器。

示例

from ftw.builder import builder_registry
from ftw.builder.portlets import PlonePortletBuilder
from my.package.portlets import my_portlet

class MyPortletBuilder(PlonePortletBuilder):
    assignment_class = my_portlet.Assignment

builder_registry.register('my portlet', MyPortletBuilder)

内置构建器包括:

  • 静态部件 - 创建一个静态部件

  • 导航部件 - 创建一个导航部件

开发/测试

$ git clone https://github.com/4teamwork/ftw.builder.git
$ cd ftw.builder
$ ln -s development.cfg buildout.cfg
$ python2.7 bootstrap.py
$ ./bin/buildout
$ ./bin/test

变更日志

2.0.0 (2019-12-04)

  • 支持Plone 5.2和Python 3。[buchi]

1.12.0 (2019-11-27)

  • 添加ATLinkBuilder和DXLinkBuilder。[djowett-ftw]

1.11.1 (2017-06-20)

  • 修正错别字。[mbaechtold]

1.11.0 (2017-06-16)

  • 通过使用上下文管理器,使ticking creator可全局安装。[jone]

  • 修复测试:test_object_providing_interface_updates_catalog:询问目录而不是索引(查询惰性)[tarnap]

  • 修复dexterity图像构建器[tarnap]

1.10.0 (2017-03-01)

  • 添加“ticking_creator”,点击一个冻结的时钟。[jone]

1.9.0 (2016-09-19)

  • 向内容构建器添加“.with_property”方法。[jone]

1.8.1 (2016-06-30)

  • 修复plone.app.dexterity依赖关系,以更好地支持Plone 4.2。[jone]

1.8.0 (2016-06-29)

  • 添加内容管理器,用于切换到AT / DX内容构建器。[jone]

  • 要求dexterity。[jone]

1.7.5 (2016-05-26)

  • DX:在确定默认值时绑定zope.schema字段,以便有一个上下文(即容器)传递给IContextAwareDefaultFactory。[lgraf]

1.7.4 (2016-03-30)

  • 修复dexterity-readonly字段的值设置。[elioschmutz]

  • 修复创建AT blob时的编码问题。[jone]

1.7.3 (2015-12-11)

  • UserBuilder:添加在指定组中创建用户的方法。[deiferni]

1.7.2 (2015-09-30)

  • DX-Builder:仅使用set_field_values初始化属性。不再将所有参数传递给createContent,以避免将存储在注释中作为属性的字段设置。[deiferni]

1.7.1 (2015-08-20)

  • 由于我们并不总是有z3c.relation,因此添加对Relationvalues的条件导入。[tschanzt]

1.7.0 (2015-08-20)

  • 在设置字段值时自动从plone内容类型创建关系值。[deiferni]

  • 添加对构建小部件的支持。[mbaechtold]

1.6.3 (2015-05-28)

  • Genericsetup构建器:修复创建相同文件夹多次时的错误。[jone]

1.6.2 (2015-05-20)

  • 包构建器:使包版本可配置。[jone]

1.6.1 (2015-05-20)

  • 包构建器:在加载包时更新pkg_resources工作集。[jone]

  • 向构建器添加创建日期设置器。[mbaechtold]

1.6.0 (2014-12-31)

  • 添加更多默认构建器

    • 创建ZCML文件的“zcml”构建器

    • 在文件系统上创建python包的“python package”构建器

    • “python package”构建器内部使用的“namespace package”构建器

    • 用于扩展Python包的嵌套包的“subpackage”构建器

    • “genericsetup profile”构建器

    • 用于构建Generic Setup升级步骤的“plone upgrade step”构建器

    [jone]

1.5.2 (2014-12-06)

  • 文件构建器:修复AT的默认文件名编码。这是1.5.0中的回归,其中文件名改为unicode,因为Archetypes和Dexterity构建器的合并。[jone]

1.5.1 (2014-12-03)

  • 修复Plone <= 4.2中NamedBlobFile导入问题,其中blob是可选的。[jone]

1.5.0 (2014-12-03)

  • 通过使DX导入条件化来恢复Plone 4.1兼容性。[lgraf]

  • Plone 5支持:默认内容构建器在Plone >= 5时默认切换到dexterity实现。构建器类已从archetypes模块移动到content模块。[jone]

1.4.0 (2014-09-04)

  • 实现集合构建器。[jone]

  • 修复不同“所有者”字段的默认值设置器。[phgross]

1.3.4 (2014-08-29)

  • DxBuilder:修复为“所有者”字段填充默认值时的编码问题。[jone]

1.3.3 (2014-06-05)

  • DxBuilder:修复检查字段是否存在的问题(以确定是否应设置默认值)。[lgraf]

  • DxBuilder:确保在向容器添加内容之前设置默认值。[lgraf]

1.3.2 (2014-05-29)

  • 在调用provides()时更新目录中的object_provides。[jone]

1.3.1 (2014-03-26)

  • 为Archetypes ImageBuilder提供真正的图像(1x1 px GIF)。[mathias.leimgruber]

1.3.0 (2014-03-25)

  • 实现ATImage构建器。[jone]

  • 为设置主体本地角色后重新索引对象安全。[mathias.leimgruber]

  • 在组构建器中支持“on”关键字参数。[mathias.leimgruber]

1.2.0 (2014-01-31)

  • 向Plone构建器添加providing()方法,使对象提供接口。[jone]

  • 对于邮件,不要使用IDNormalizer。它处理Umlauts很奇怪。[tschanzt]

1.1.0 (2013-09-13)

  • 添加组构建器。[jone]

  • 添加用户构建器。[jone]

  • 为PloneObject构建器添加修改日期设置器。[phgross]

1.0.0 (2013-08-12)

  • 添加dexterity支持。[phgross]

  • 初始实现 [jone]

项目详情


下载文件

下载适合您平台的文件。如果您不确定该选择哪一个,请了解有关安装包的更多信息。

源分发

ftw.builder-2.0.0.tar.gz (50.9 kB 查看哈希值)

由以下支持