跳转到主要内容

将测试用例作为zope.testing层使用

项目描述

zope.testing提供的层支持有助于减少测试驱动开发过程中消耗的时间,因为它通过共享昂贵的测试固定装置来实现,例如功能测试通常需要的。本包提供了一些经过良好测试的设施,以便更快、更轻松地编写和使用层。

在快速入门中使用的collective.testcaselayer.common.common_layer也包含一些常用测试固定装置。

  • 模拟邮件主机

  • 从错误日志忽略异常中移除‘未授权’和‘未找到’

  • 将资源注册表置于调试模式(portal_css、portal_javascripts、portal_kss)

快速入门

对于安装collective命名空间包到Zope并将它的GenericSetup配置文件安装到PloneTestCase Plone网站的简单测试层,您可以执行以下操作。

在egg的setup.py中指定对collective.testcaselayer的测试依赖关系

from setuptools import setup, find_packages
...
tests_require = ['collective.testcaselayer']
...
setup(name='collective.foo',
      ...
      install_requires=[
          'setuptools',
          # -*- Extra requirements: -*-
      ],
      tests_require=tests_require,
      extras_require={'tests': tests_require},
      ...
      entry_points="""

告诉buildout包含测试依赖关系。这仅在您将运行测试的部署中是必要的。因此,您可以将它从生产buildout配置中排除,并仅将其放在buildout的开发配置中

...
eggs +=
    collective.foo [tests]
...

定义层。层可以使用与PloneTestCase类相同的方法,例如

  • self.login(user_name)

  • self.loginAsPortalOwner()

  • self.addProduct(product)

  • self.addProfile(profile)

提供了一个额外的、用于在ZCML调试模式下加载ZCML文件的方法

  • self.loadZCML(file, package=package)

您可以使用collective.foo.testing模块如下

>>> from Products.PloneTestCase import ptc
>>>
>>> from collective.testcaselayer import ptc as tcl_ptc
>>> from collective.testcaselayer import common
>>>
>>> class Layer(tcl_ptc.BasePTCLayer):
...     """Install collective.foo"""
...
...     def afterSetUp(self):
...         ZopeTestCase.installPackage('collective.foo')
...
...         from collective.foo import tests
...         self.loadZCML('testing.zcml', package=tests)
...
...         self.addProfile('collective.foo:default')
>>>
>>> layer = Layer([common.common_layer])

要在README.txt doctest中使用此层,您可以使用collective.foo.tests模块如下

>>> import unittest
>>> import doctest
>>>
>>> from Testing import ZopeTestCase
>>> from Products.PloneTestCase import ptc
>>>
>>> from collective.foo import testing
>>>
>>> optionflags = (doctest.NORMALIZE_WHITESPACE|
...                doctest.ELLIPSIS|
...                doctest.REPORT_NDIFF)
>>>
>>> def test_suite():
...     suite = ZopeTestCase.FunctionalDocFileSuite(
...         'README.txt',
...         optionflags=optionflags,
...         test_class=ptc.FunctionalTestCase)
...     suite.layer = testing.layer
...     return suite
>>>
>>> if __name__ == '__main__':
...     unittest.main(defaultTest='test_suite')

现在编写您的README.txt doctest,并可以使用类似以下方式运行您的测试

$ bin/instance test -s collective.foo

详细文档

层作者经常最终会重复他们的测试用例类提供的功能,因为相同的函数在执行层设置或拆解时是必需的。collective.testcaselayer.ztc、collective.testcaselayer.ctc和collective.testcaselayer.ptc模块提供了层基类,分别混合了ZopeTestCase、CMFTestCase和PloneTestCase的功能。有关详细信息,请参阅下面的collective.testcaselayer.ztc和collective.testcaselayer.ptc部分(或ztc.txt和ptc.txt,如果从源中阅读)。这些层基类还包括collective.testcaselayer.layer的层基类支持以及collective.testcaselayer.sandbox中描述的沙盒ZODB层支持。此外,这些模块允许使用测试用例固定装置作为层本身。

虽然可以将类对象用作层,而不是类的实例,但是这样做意味着层不能仅为了重用功能而子类化另一个层,同时还需要依赖于该层也被设置。有关详细信息,请参阅下面的collective.testcaselayer.layer部分(或layer.txt,如果从源中阅读)。

ZODB附带DemoStorage提供了“嵌套”ZODB存储的方式,使得所有写入都将写入DemoStorage,而读取则从基本存储中获取,如果从DemoStorage中不可用。collective.testcaselayer.sandbox模块使用此功能将DemoStorage与每个沙盒层相关联,将设置更改提交到其中,并在拆解时恢复基本存储。因此,写入ZODB的兄弟层可以彼此隔离。有关详细信息,请参阅下面的collective.testcaselayer.sandbox部分(或sandbox.txt,如果从源中阅读)。

公共层

如果测试层使用collective.testcaselayer.common.common_layer作为基层,则将设置一些常用事物。

在设置层之前,错误日志中默认的异常被忽略,资源注册表不处于调试模式。

>>> portal.error_log.getProperties()['ignored_exceptions']
('Unauthorized', 'NotFound', 'Redirect')
>>> portal.portal_css.getDebugMode()
False
>>> portal.portal_javascripts.getDebugMode()
False
>>> 'portal_kss' in portal and portal.portal_kss.getDebugMode() or False
False

设置 common_layer。

>>> from zope.testing.testrunner import runner
>>> from collective.testcaselayer import common
>>> def getSetUpLayers(layer):
...     for base in layer.__bases__:
...         if base is not object:
...             for recurs in getSetUpLayers(base):
...                 yield recurs
...             yield base
>>> setup_layers = dict((layer, 1) for layer in
...                     getSetUpLayers(common.common_layer))
>>> options = runner.get_options([], [])
>>> runner.setup_layer(options, common.common_layer, setup_layers)
Set up collective.testcaselayer.common.CommonPTCLayer in ... seconds.

现在只有‘重定向’在错误日志中被忽略,资源注册表处于调试模式。

>>> from Testing import ZopeTestCase
>>> from Products.PloneTestCase import ptc as plone_ptc
>>> app = ZopeTestCase.app()
>>> portal = getattr(app, plone_ptc.portal_name)
>>> portal.error_log.getProperties()['ignored_exceptions']
('Redirect',)
>>> portal.portal_css.getDebugMode()
True
>>> portal.portal_javascripts.getDebugMode()
True
>>> 'portal_kss' in portal and portal.portal_kss.getDebugMode() or True
True

模拟邮件主机

如果测试层使用 collective.testcaselayer.mail.mockmailhost_layer 作为基础层,则使用 portal.MailHost.send 方法发送的消息将被追加到列表中进行测试检查。

从一个空的 MailHost 开始。

>>> len(portal.MailHost)
0

发送一条消息。

>>> portal.MailHost.send("""\
... From: foo@foo.com
... To: bar@foo.com
... Subject: Foo message subject
...
... Foo message body
... """)

MailHost 现在包含一条消息。

>>> len(portal.MailHost)
1

可以使用 pop 方法移除消息,此时它会从列表中移除。

>>> print portal.MailHost.pop().as_string()
From: foo@foo.com
To: bar@foo.com
Subject: Foo message subject
Date: ...
Foo message body
>>> len(portal.MailHost)
0

模拟邮件主机可以处理在野外使用的更复杂的调用签名。

>>> portal.MailHost.send(
...     """\
... From: foo@foo.com
... To: bar@foo.com
... Subject: Qux message subject
...
... Qux message body
... """, 'bar@foo.com', 'foo@foo.com',
...     subject='Qux message subject')
>>> print portal.MailHost.pop().as_string()
To: bar@foo.com...
Qux message body
>>> len(portal.MailHost)
0

collective.testcaselayer.ptc

collective.testcaselayer.ptc 模块扩展了从 collective.testcaselayer.ztc 到 PloneTestCase 的层和层基类。有关使用层和层基类的介绍,请参阅 ztc.txt。在这里,我们只演示 PloneTestCase 的特定功能,这些功能不是从 ZopeTestCase 继承的。

PloneTestCase 测试固定装置可以作为层设置和拆解。

>>> from collective.testcaselayer import ptc
>>> ptc.ptc_layer
<collective.testcaselayer.ptc.PTCLayer testMethod=layerOnly>

为了测试仅此层的效应,单独设置基础层。由于 PloneTestCase 使用层的方式,我们首先必须调用 setupPloneSite() 函数。

>>> from zope.testing.testrunner import runner
>>> from Products.PloneTestCase import ptc as plone_ptc
>>> plonesite_layer, = ptc.ptc_layer.__bases__
>>> options = runner.get_options([], [])
>>> setup_layers = {}
>>> runner.setup_layer(options, plonesite_layer, setup_layers)
Set up...Products.PloneTestCase.layer.ZCML in ... seconds.
Set up Products.PloneTestCase.layer.PloneSite in ... seconds.

PloneTestCase 测试固定装置尚未设置。

>>> from Testing import ZopeTestCase
>>> app = ZopeTestCase.app()
>>> portal = getattr(app, plone_ptc.portal_name)
>>> portal.acl_users.getUserById(plone_ptc.default_user)
>>> ZopeTestCase.close(app)

设置 PloneTestCase 层。

>>> runner.setup_layer(options, ptc.ptc_layer, setup_layers)
Set up collective.testcaselayer.ptc.PTCLayer in ... seconds.

PloneTestCase 测试固定装置已设置。

>>> app = ZopeTestCase.app()
>>> portal = getattr(app, plone_ptc.portal_name)
>>> portal.acl_users.getUserById(plone_ptc.default_user)
<PloneUser 'test_user_1_'>
>>> ZopeTestCase.close(app)

拆解 PloneTestCase 层。

>>> runner.tear_down_unneeded(
...     options,
...     [layer for layer in setup_layers
...      if layer is not ptc.ptc_layer],
...     setup_layers)
Tear down collective.testcaselayer.ptc.PTCLayer in ... seconds.

PloneTestCase 测试固定装置不再设置。

>>> app = ZopeTestCase.app()
>>> portal = getattr(app, plone_ptc.portal_name)
>>> portal.acl_users.getUserById(plone_ptc.default_user)
>>> ZopeTestCase.close(app)

层基类

PloneTestCase 类设施也可以在使用 PloneTestCase 层基类的层中使用。

>>> class FooLayer(ptc.BasePTCLayer):
...     def afterSetUp(self):
...         self.addProfile(
...             'Products.CMFDefault:sample_content')
...         self.addProduct('CollectiveTestCaseLayerTesting')
...         self.loginAsPortalOwner()

此层依赖于在仅用于测试的层中设置的配置文件和产品。

>>> from collective.testcaselayer.testing import layer
>>> foo_layer = FooLayer([layer.product_layer, ptc.ptc_layer])

FooLayer 测试固定装置尚未设置。

>>> app = ZopeTestCase.app()
>>> portal = getattr(app, plone_ptc.portal_name)
>>> hasattr(portal, 'subfolder')
False
>>> hasattr(portal, 'foo')
False
>>> from AccessControl import SecurityManagement
>>> SecurityManagement.getSecurityManager().getUser()
<SpecialUser 'Anonymous User'>
>>> ZopeTestCase.close(app)

设置 FooLayer。

>>> runner.setup_layer(options, foo_layer, setup_layers)
Set up collective.testcaselayer.testing.layer.ProductLayer
in ... seconds.
Set up FooLayer in ... seconds.

FooLayer 测试固定装置已设置。

>>> app = ZopeTestCase.app()
>>> portal = getattr(app, plone_ptc.portal_name)
>>> portal.subfolder
<ATFolder at /plone/subfolder>
>>> portal.foo
'foo'
>>> from AccessControl import SecurityManagement
>>> SecurityManagement.getSecurityManager().getUser()
<PropertiedUser 'portal_owner'>
>>> ZopeTestCase.close(app)

拆解 FooLayer。

>>> runner.tear_down_unneeded(
...     options,
...     [layer for layer in setup_layers
...      if layer is not foo_layer],
...     setup_layers)
Tear down FooLayer in ... seconds.

FooLayer 测试固定装置不再设置。

>>> app = ZopeTestCase.app()
>>> portal = getattr(app, plone_ptc.portal_name)
>>> hasattr(portal, 'subfolder')
False
>>> hasattr(portal, 'foo')
False
>>> from AccessControl import SecurityManagement
>>> SecurityManagement.getSecurityManager().getUser()
<SpecialUser 'Anonymous User'>
>>> ZopeTestCase.close(app)

完成拆解其余层的操作。

>>> runner.tear_down_unneeded(options, [], setup_layers)
Tear down collective.testcaselayer.testing.layer.ProductLayer
in ... seconds.
Tear down Products.PloneTestCase.layer.PloneSite in ... seconds.
Tear down Products.PloneTestCase.layer.ZCML in ... seconds.

collective.testcaselayer.ztc

BaseZTCLayer 和其相关类旨在作为层的基类使用,以便它们可以使用 ZopeTestCase、PortalTestCase 及其子类的功能。因此,层的 setUp 和 tearDown 方法可以使用测试用例方法和其他支持,例如:self.login()、self.logout()、self.loginAsPortalOwner()、self.setRoles()、self.setPermissions() 等。

ZTCLayer 和其相关类允许将任何测试用例设置的测试固定装置作为层本身使用。

collective.testcaselayer.ctc 和 collective.testcaselayer.ptc 模块将此支持扩展到 CMFTestCase 和 PloneTestCase,尽管 collective.testcaselayer 本身不依赖于它们。这些层基类允许使用那些测试用例的方法,如 addProfile() 和 addProduct(),有关更多详细信息,请参阅 ctc.txt 和 ptc.txt。

collective.testcaselayer.ztc 模块提供沙盒层,用于设置 ZopeTestCase 的测试固定装置。请注意,基于测试用例的层仍然像测试用例一样操作,具有特殊的 no-op layerOnly() 测试方法,以便它们具有功能的 str() 和 repr() 值。

>>> from collective.testcaselayer import ztc
>>> ztc.ztc_layer
<collective.testcaselayer.ztc.ZTCLayer testMethod=layerOnly>

在我们将 ZopeTestCase 设置为层之前,没有设置任何内容。

>>> from AccessControl import SecurityManagement
>>> SecurityManagement.getSecurityManager().getUser()
<SpecialUser 'Anonymous User'>
>>> hasattr(ztc.ztc_layer, 'app')
False
>>> from Testing import ZopeTestCase
>>> app = ZopeTestCase.app()
>>> 'test_folder_1_' in app.objectIds()
False
>>> ZopeTestCase.close(app)
>>> from Testing.ZopeTestCase import connections
>>> connections.count()
0

将 ZopeTestCase 设置为层。

>>> from zope.testing.testrunner import runner
>>> options = runner.get_options([], [])
>>> setup_layers = {}
>>> runner.setup_layer(options, ztc.ztc_layer, setup_layers)
Set up collective.testcaselayer.ztc.ZTCLayer in ... seconds.

ZopeTestCase 测试固定装置已设置,但没有登录用户。

>>> SecurityManagement.getSecurityManager().getUser()
<SpecialUser 'Anonymous User'>
>>> 'test_folder_1_' in  ztc.ztc_layer.app.objectIds()
True

还请注意,层的 app 属性代表对 ZODB 的开放连接。

>>> connections.count()
1

拆解 ZopeTestCase 层。

>>> runner.tear_down_unneeded(options, [], setup_layers)
Tear down collective.testcaselayer.ztc.ZTCLayer in ... seconds.

现在一切又回到了原来的状态。

>>> SecurityManagement.getSecurityManager().getUser()
<SpecialUser 'Anonymous User'>
>>> hasattr(ztc.ztc_layer, 'app')
False
>>> from Testing import ZopeTestCase
>>> app = ZopeTestCase.app()
>>> 'test_folder_1_' in app.objectIds()
False
>>> ZopeTestCase.close(app)
>>> connections.count()
0

层基类

collective.testcaselayer.ztc 模块还提供沙盒层的基类,这些层实际上不设置测试用例固定装置,但允许在层设置和拆解代码中使用测试用例提供的功能。

由于层可以嵌套,这些层基类不执行实际的ZopeTestCase测试环境设置,除非子类显式地将 _setup_fixture(或 PortalTestCase的_configure_portal)设置为True。最佳实践是使用ZTC测试环境设置实例化任何依赖的层,将ZTCLayer作为基层,如上所述。

创建一个继承适当基层的层类。这个层类覆盖了afterSetUp()方法,就像ZopeTestCase基于测试用例一样。这里的afterSetUp方法使用了ZopeTestCase提供的功能以及一个额外的loadZCML()方法,用于在ZCML调试模式下加载ZCML文件。

>>> from collective.testcaselayer import testing
>>> class FooLayer(ztc.BaseZTCLayer):
...     def afterSetUp(self):
...         self.login()
...         self.setRoles(['Manager'])
...         self.loadZCML('loadzcml.zcml', package=testing)
>>> foo_layer = FooLayer([ztc.ztc_layer])

为了仅测试这个层的效果,请单独设置基层。

>>> runner.setup_layer(options, ztc.ztc_layer, setup_layers)
Set up collective.testcaselayer.ztc.ZTCLayer in ... seconds.

在设置新层之前,仅设置ZopeTestCase测试环境。

>>> SecurityManagement.getSecurityManager().getUser()
<SpecialUser 'Anonymous User'>
>>> app = ZopeTestCase.app()
>>> user = getattr(app, ZopeTestCase.folder_name
...                ).acl_users.getUserById(ZopeTestCase.user_name)
>>> user.getRoles()
('test_role_1_', 'Authenticated')
>>> ZopeTestCase.close(app)

设置新层。

>>> runner.setup_layer(options, foo_layer, setup_layers)
Set up FooLayer in ... seconds.

现在afterSetUp()所做的更改得到了反映。

>>> authenticated = SecurityManagement.getSecurityManager(
...     ).getUser()
>>> authenticated
<User 'test_user_1_'>
>>> authenticated.getRoles()
('Manager', 'Authenticated')

仅拆除新层。

>>> runner.tear_down_unneeded(
...     options, [ztc.ztc_layer], setup_layers)
Tear down FooLayer in ... seconds.

一切恢复到之前的状态。

>>> SecurityManagement.getSecurityManager().getUser()
<SpecialUser 'Anonymous User'>
>>> app = ZopeTestCase.app()
>>> user = getattr(app, ZopeTestCase.folder_name
...                ).acl_users.getUserById(ZopeTestCase.user_name)
>>> user.getRoles()
('test_role_1_', 'Authenticated')
>>> ZopeTestCase.close(app)

完成拆解其余层的操作。

>>> runner.tear_down_unneeded(options, [], setup_layers)
Tear down collective.testcaselayer.ztc.ZTCLayer in ... seconds.

collective.testcaselayer.layer

在许多情况下,类本身可以作为层使用,其中基类用作基层。这意味着用于代码分解和重用的层继承层次结构,与用于确定何时以及为哪些测试设置哪些层的层设置层次结构绑定。换句话说,一个层不能仅通过重用功能来继承另一个层,而不依赖于该层也被设置。此外,当使用类作为层时,必须在具有基类的类层上定义所有层方法(setUp、tearDown、testSetUp和testTearDown),以避免意外在错误的时间运行基类/层的 方法。

collective.testcaselayer.layer模块提供了一个Layer类,旨在用作将实例用作层的类的基类。此类的实例还可以直接使用,仅用于将层组合成单个层。

>>> from collective.testcaselayer import layer

层类

使用collective.testcaselayer.layer.Layer类创建自己的层类。

>>> class FooLayer(layer.Layer):
...     def setUp(self): print 'running FooLayer.setUp'

该类的实例将是实际的zope.testing层。

>>> foo_layer = FooLayer()
>>> from zope.testing.testrunner import runner
>>> options = runner.get_options([], [])
>>> runner.setup_layer(options, foo_layer, {})
Set up FooLayer running FooLayer.setUp
in ... seconds.

请注意,Layer类本身或其子类可以自身作为层使用而不会出错,但这不是它们被设计的方式。例如,将FooLayer类作为层使用将把Layer基类本身视为层,并设置它,这是没有意义的。此外,它将尝试以类方法调用setUp方法,这会引发错误。

>>> runner.setup_layer(options, FooLayer, {})
Traceback (most recent call last):
TypeError: unbound method setUp() must be called with FooLayer instance as first argument (got nothing instead)

基本层

通过在实例化时传递它们,指定基层。

创建另一个层类。

>>> class BarLayer(layer.Layer):
...     def setUp(self): print 'running BarLayer.setUp'

创建使用foo_layer作为基层的新的层。

>>> bar_layer = BarLayer([foo_layer])

设置层。

>>> runner.setup_layer(options, bar_layer, {})
Set up FooLayer running FooLayer.setUp
in ... seconds.
Set up BarLayer running BarLayer.setUp
in ... seconds.

分组层

如果层所需的一切仅仅是将其作为基层组合其他层,则可以直接使用collective.testcaselayer.layer.Layer类。

创建另一个层。

>>> class BazLayer(layer.Layer):
...     def setUp(self): print 'running BazLayer.setUp'
>>> baz_layer = BazLayer()

使用基层、一个模块和一个名称实例化Layer类。

>>> qux_layer = layer.Layer(
...     [bar_layer, baz_layer],
...     module='QuxModule', name='QuxLayer')

设置层。

>>> runner.setup_layer(options, qux_layer, {})
Set up FooLayer running FooLayer.setUp
in ... seconds.
Set up BarLayer running BarLayer.setUp
in ... seconds.
Set up BazLayer running BazLayer.setUp
in ... seconds.
Set up QuxModule.QuxLayer in ... seconds.

默认情况下,层具有与它们的类相同的模块和名称。如果您希望层具有与类不同的模块或名称,则可以将它们都作为参数传递。这在当前情况下很有用,并且在需要使用同一层类的多个实例作为层时也很有用。

直接实例化Layer类而不传递名称会引发错误。

>>> layer.Layer([], module='QuxModule')
Traceback (most recent call last):
ValueError: The "name" argument is requied when instantiating
"Layer" directly

如果直接实例化Layer类而不传递模块,则使用调用帧的模块名称。

>>> __name__ = 'BahModule'
>>> quux_layer = layer.Layer([], name='QuuxLayer')
>>> runner.setup_layer(options, quux_layer, {})
Set up BahModule.QuuxLayer in ... seconds.

collective.testcaselayer.sandbox

沙盒层将设置时所做的更改提交到使用之前ZODB存储作为基础存储的沙盒DemoStorage。在拆除时,层将恢复基础存储。这允许层在使用并提交更改到完全功能的ZODB的同时,将层的效果与任何父层或兄弟层隔离开。

正如预期的那样,以沙盒层为基层的层将根据基层看到 ZODB。此外,沙盒层可以使用其他沙盒层作为基层,从而允许嵌套但隔离的 ZODB 沙盒。

创建一个沙盒层。继承自 Sandboxed 的层应实现 afterSetUp 方法来完成对层的任何更改。此外,此类层还可以提供 beforeTearDown 方法来撤销由层所做的更改,这些更改在恢复 ZODB 时不会被清理。

>>> from collective.testcaselayer import ztc
>>> class FooLayer(ztc.BaseZTCLayer):
...     def afterSetUp(self):
...         self.app.foo = 'foo'
>>> foo_layer = FooLayer()

在层设置之前,ZODB 不反映层的更改。

>>> from Testing import ZopeTestCase
>>> app = ZopeTestCase.app()
>>> getattr(app, 'foo', None)
>>> ZopeTestCase.close(app)

层设置之后,更改已提交到 ZODB。

>>> foo_layer.setUp()
>>> app = ZopeTestCase.app()
>>> getattr(app, 'foo', None)
'foo'
>>> ZopeTestCase.close(app)

创建一个以第一层为基层的沙盒层。

>>> class BarLayer(ztc.BaseZTCLayer):
...     def afterSetUp(self):
...         self.app.bar = 'bar'
>>> bar_layer = BarLayer([foo_layer])

在子层设置之前,ZODB 仍然反映基层的更改,但不反映子层的更改。

>>> app = ZopeTestCase.app()
>>> getattr(app, 'foo', None)
'foo'
>>> getattr(app, 'bar', None)
>>> ZopeTestCase.close(app)

在子层设置之后,ZODB 反映两个层的更改。

>>> bar_layer.setUp()
>>> app = ZopeTestCase.app()
>>> getattr(app, 'foo', None)
'foo'
>>> getattr(app, 'bar', None)
'bar'
>>> ZopeTestCase.close(app)

任何使用 Testing.ZopeTestCase.sandbox.Sandboxed 的测试用例,例如针对 Zope2 运行的 zope.testbrowser 测试,都会调用 ZopeLite.sandbox() 函数而不带任何参数。在这种情况下,结果为每个测试创建的沙盒 ZODB 仍然基于层沙盒 ZODB。

>>> app = ZopeTestCase.Zope2.app(
...     ZopeTestCase.Zope2.sandbox().open())
>>> getattr(app, 'foo', None)
'foo'
>>> getattr(app, 'bar', None)
'bar'
>>> app._p_jar.close()

在子层拆除后,ZODB 仅反映基层的更改。

>>> bar_layer.tearDown()
>>> app = ZopeTestCase.app()
>>> getattr(app, 'foo', None)
'foo'
>>> getattr(app, 'bar', None)
>>> ZopeTestCase.close(app)

在基层拆除后,ZODB 不反映任一层的更改。

>>> foo_layer.tearDown()
>>> app = ZopeTestCase.app()
>>> getattr(app, 'foo', None)
>>> getattr(app, 'bar', None)
>>> ZopeTestCase.close(app)

功能和测试浏览器测试补丁

要使用这些补丁,请包含 collective.testcaselayer configure.zcml。这些补丁解决了 Testing.ZopeTestCase 中的一些问题。

流式传输到响应的数据

由于 Testing.ZopeTestCase.zopedoctest.functional 中的一些行为,当数据直接流式传输到响应中(而不是从可调用的已发布内容返回数据)时,testbrowser.contents 为空。这使得对需要将数据流式传输到响应以进行性能测试的代码进行功能测试变得困难,例如当响应数据非常大,会消耗太多内存时。

流迭代器

还包括一个从 plone.app.blob 夺取的补丁,以便在测试环境中支持 HTTP 响应的流迭代器。这允许对使用流迭代器的代码进行功能测试。

HTTP_REFERRER

由于 问题 #98437,“TestBrowser Referer: header set to ‘localhost’”,一些 testbrowser 请求会引发 NotFound。两个例子是直接访问 Plone login_form 而不是通过链接访问,或者使用 Plone content_status_history 表单。

测试补丁

添加一个渲染 referer 的文档。

>>> folder.addDTMLDocument(
...     'index_html', file='''\
... <html><body>
... <dtml-var "REQUEST['HTTP_REFERER']">
... <form action="." method="post" id="post"></form>
... <form action="." method="get" id="get"></form>
... <a href=".">link</a>
... </html></body>
... ''')
''

打开一个浏览器。

>>> from Products.Five.testbrowser import Browser
>>> browser = Browser()
>>> browser.handleErrors = False

在修补之前,新鲜请求有一个无效的 referer。

>>> browser.open(folder.index_html.absolute_url())
>>> print browser.contents
<html><body>
localhost
<form action="." method="post" id="post"></form>
<form action="." method="get" id="get"></form>
<a href=".">link</a>
</html></body>

添加一个将内容流式传输到响应的脚本。

>>> from Products.PythonScripts import PythonScript
>>> PythonScript.manage_addPythonScript(folder, 'foo.txt')
''
>>> folder['foo.txt'].ZPythonScript_edit(params='', body='''\
... context.REQUEST.response.setHeader('Content-Type', 'text/plain')
... context.REQUEST.response.setHeader(
...     'Content-Disposition',
...     'attachment;filename=foo.txt')
... context.REQUEST.response.write('foo')''')

在修补之前,流式传输到响应的数据不在浏览器内容中。

>>> browser.open(folder['foo.txt'].absolute_url())
>>> browser.isHtml
False
>>> print browser.contents

添加一个返回流迭代器的脚本。

>>> from Products.PythonScripts import PythonScript
>>> PythonScript.manage_addPythonScript(folder, 'bar.txt')
''
>>> folder['bar.txt'].ZPythonScript_edit(params='', body='''\
... from collective.testcaselayer.testing.iterator import (
...     StreamIterator)
... context.REQUEST.response.setHeader('Content-Type', 'text/plain')
... context.REQUEST.response.setHeader(
...     'Content-Disposition',
...     'attachment;filename=bar.txt')
... return StreamIterator(['bar', 'qux'])''')
>>> from AccessControl import allow_module
>>> allow_module('collective.testcaselayer.testing.iterator')

流迭代器不受支持。

>>> browser.open(folder['bar.txt'].absolute_url())
>>> browser.isHtml
False
>>> print browser.contents
['bar', 'qux']

应用补丁。

>>> from Products.Five import zcml
>>> from Products.Five import fiveconfigure
>>> from collective import testcaselayer
>>> fiveconfigure.debug_mode = True
>>> zcml.load_config('testing.zcml', package=testcaselayer)
>>> fiveconfigure.debug_mode = False

新鲜请求应没有 referer。

>>> browser.open(folder.index_html.absolute_url())
>>> print browser.contents
<html><body>
<form action="." method="post" id="post"></form>
<form action="." method="get" id="get"></form>
<a href=".">link</a>
</html></body>

通过 post 提交表单应没有 referer。

>>> browser.getForm('post').submit()
>>> print browser.contents
<html><body>
<form action="." method="post" id="post"></form>
<form action="." method="get" id="get"></form>
<a href=".">link</a>
</html></body>

通过 get 提交表单应没有 referer。

>>> browser.getForm('get').submit()
>>> print browser.contents
<html><body>
<form action="." method="post" id="post"></form>
<form action="." method="get" id="get"></form>
<a href=".">link</a>
</html></body>

点击链接应设置 referer。

>>> browser.getLink('link').click()
>>> print browser.contents
<html><body>
http://nohost/test_folder_1_/...
<form action="." method="post" id="post"></form>
<form action="." method="get" id="get"></form>
<a href=".">link</a>
</html></body>

流式传输到响应的数据现在在浏览器内容中。

>>> browser.open(folder['foo.txt'].absolute_url())
>>> browser.isHtml
False
>>> print browser.contents
Status: 200 OK
X-Powered-By: Zope (www.zope.org), Python (www.python.org)
Content-Length: 0
Content-Type: text/plain
Content-Disposition: attachment;filename=foo.txt
foo

流迭代器现在在浏览器内容中。

>>> browser.open(folder['bar.txt'].absolute_url())
>>> browser.isHtml
False
>>> print browser.contents
barqux

变更日志

1.6.1 (2015-08-14)

  • 小清理:空格、git 忽略、setup.py。[gforcada, rnix, maurits]

1.6 (2012-10-17)

  • 如果门户没有 portal_kss 工具,不要中断。[davisagli]

1.5 - 2012-05-18

  • Plone 4.1 兼容性。[rossp]

  • 让层拆除进行清理。允许在关闭数据库连接或其他清理操作之前进行 post_mortem 调试以查看 TB 中的状态。[rossp]

1.4 - 2011-07-15

  • 将模拟邮件主机移动到实际的 GS 配置文件中,这样它就不会在依赖层运行自己的配置文件时被替换。这也使得模拟邮件主机在测试之外可用。[rossp]

  • 添加一些设置测试浏览器 AT 日历小部件的实用方法,以避免 end/beginning-of-month 间歇性失败。[rossp]

  • 避免 PloneTestCase 和 zope.testing/testrunner 之间的版本冲突。[rossp]

1.3 - 2010-02-09

  • 添加一个 loadZCML 便捷方法 [rossp]

  • 添加一个包含一些有用测试设置的通用层 [rossp]

  • 添加一个模拟邮件主机层 [rossp]

1.2.2 - 2009-11-14

  • 为流迭代器响应添加功能测试支持。借鉴自witsch的plone.app.blob测试补丁。[rossp]

  • 与Zope 2.10-2.12兼容。[rossp, witsch]

  • 修复了Zope 2.12/Plone 4的沙箱替换。[witsch]

1.2.1 - 2009-10-11

  • 将ZTC功能doctest猴子补丁移至testing.zcml,以免在自动包含时被选中。optilude报告说这会破坏调试模式。

1.2 - 2009-08-21

  • 添加补丁,以便将数据流式传输到响应中,在testbrowser.contents中可用。[rossp]

  • 添加修复HTTP_REFERER测试浏览器错误的补丁。[rossp] https://bugs.launchpad.net/bugs/98437

1.1 - 2009-07-29

  • 修复发布。由于setuptools与SVN 1.6的交互,文件丢失。

1.0 - 2009-07-29

  • 针对Plone 3.3rc4进行测试

  • 添加基本Plone测试用例层的示例代码

  • 弃用zope.testing<3.6支持

  • collective.testcaselayer.ptc模块需要调用ptc.setupPloneSite()以确保Plone站点存在

0.2 - 2008-01-08

  • 在PortalTestCase子层中使self.folder属性可用

  • 使测试与zope.testing.testrunner重构兼容

0.1 - 2008-05-23

  • 初始发布

待办事项

  • 添加加载ZCML的便利方法。(witsch)

  • 提取collective.testclasslayer.layer

collective.testclasslayer.layer模块实际上与测试用例无关,但我不想只为这一点创建一个单独的包。如果有人想在zope.testing或其他通用测试依赖项中放入这个模块,那就太好了。

  • 从Testing.ZopeTestCase.base.TestCase中提取unittest.TestCase

可能有必要重构Testing.ZopeTestCase包中ZTC特定的测试用例部分,以便有一个不继承unittest.TestCase的公共基类。有了这个,我们可以去掉collective.testcaselayer.testcase,并拥有可以作为层或测试用例使用的公共基类。

项目详情


下载文件

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

源代码分发

collective.testcaselayer-1.6.1.tar.gz (37.2 kB 查看哈希值)

上传时间 源代码

由以下支持