基于 plone.testing 的 Plone-the-application 测试工具。
项目描述
介绍
<nav class="contents" id="table-of-contents">目录
plone.app.testing提供了为在 Plone 上运行的代码编写集成和功能测试的工具。它基于plone.testing。如果您不熟悉plone.testing、层的概念或 zope.testing 测试运行程序,请查看plone.testing 文档。事实上,即使您只使用 Plone,您也可能希望使用它的一些功能进行单元测试。
简而言之,plone.app.testing包括:
设置包含 Plone 站点的固定装置的一组层,用于编写集成和功能测试。
一组辅助函数,一些对编写您自己的层有用,一些适用于测试本身。
一个方便的层基类,扩展plone.testing.Layer,这使得编写扩展 Plone 站点夹具的自定义层变得更容易,并具有适当的隔离和拆卸。
zope.testing.cleanup的清理挂钩,用于清理在 Plone 安装中发现的全局状态。这对于单元测试很有用。
兼容性
plone.app.testing 5.x 适用于 Plone 5。plone.app.testing 4.x 适用于 Plone 4 和 Zope 2.12。它可能适用于较新的版本。它不适用于早期版本。对 Plone 3 和 Zope 2.10使用 plone.app.testing 3.x。
安装使用
要在您自己的包中使用plone.app.testing,您需要将其添加为依赖项。大多数人更喜欢将仅测试依赖项分开,这样就不需要将它们安装在不会运行测试的场景(例如在生产服务器上)。这可以使用 额外的测试来实现。
在setup.py中,添加或修改extras_require选项,如下所示:
extras_require = { 'test': [ 'plone.app.testing', ] },
这还将包括plone.testing,以及[z2]、[zca]和 [zodb]附加功能(plone.app.testing本身依赖)。
请参阅plone.testing文档以获取有关如何将测试运行器添加到构建中以及如何编写和运行测试的更多详细信息。
图层参考
这个包包含一个图层类 plone.app.testing.layers.PloneFixture,它设置了一个 Plone 站点夹具。它与plone.testing中的其他层相结合,以提供许多层实例。重要的是要意识到这些层都具有相同的基本夹具:它们只是以不同的方式管理测试设置和拆卸。
设置后,夹具将:
通过堆叠的DemoStorage创建一个 ZODB 沙箱。这可确保在层设置期间进行的持久更改可以被彻底拆除。
配置全局组件注册表沙箱。这确保了全局组件注册(例如,作为加载 ZCML 配置的结果)可以被彻底拆除。
使用disable-autoinclude功能集创建配置上下文。这具有阻止 Plone 通过z3c.autoinclude自动加载使用 z3c.autoinclude.plugin:plone入口点的任何已安装包的配置的效果。(这是为了避免意外污染测试夹具 - 如果需要,自定义层应显式加载包的 ZCML 配置)。
安装许多 Plone 所依赖的 Zope 2 风格的产品。
为这些产品和Products.CMFPlone加载 ZCML ,这反过来会拉入 Plone 核心的配置。
创建一个默认的 Plone 站点,启用默认主题,但没有默认内容。
将用户添加到具有Manager角色的根用户文件夹。
使用Member角色向此实例添加一个测试用户。
对于每个测试:
测试用户已登录
本地组件站点设置
清理各种全局缓存
plone.app.testing.interfaces模块中的各种常量被定义来描述这个环境:
持续的 |
目的 |
PLONE_SITE_ID |
Zope 应用程序根目录中 Plone 站点对象的 id。 |
PLONE_SITE_TITLE |
克隆网站的标题 |
DEFAULT_LANGUAGE |
Plone 网站的默认语言 ('en') |
TEST_USER_ID |
测试用户的id |
TEST_USER_NAME |
测试用户的用户名 |
TEST_USER_PASSWORD |
测试用户密码 |
TEST_USER_ROLES |
测试用户的默认全局角色 - ('Member',) |
SITE_OWNER_NAME |
拥有 Plone 站点的用户的用户名。 |
SITE_OWNER_PASSWORD |
拥有 Plone 站点的用户的密码。 |
除了基础层的资源之外,所有层还公开了一个资源,在测试期间可用:
- 门户网站
Plone 站点根目录。
克隆站点夹具
层: |
plone.app.testing.PLONE_FIXTURE |
班级: |
plone.app.testing.layers.PloneFixture |
基地: |
plone.testing.z2.STARTUP |
资源: |
该层在z2.STARTUP固定装置的顶部设置了 Plone 站点固定 装置。
您不应直接使用此层,因为它不提供任何测试生命周期或事务管理。相反,您应该使用使用IntegrationTesting或FunctionalTesting 类创建的层,如下所述。
模拟邮件主机
层: |
plone.app.testing.MOCK_MAILHOST_FIXTURE |
班级: |
plone.app.testing.layers.MockMailHostLayer |
基地: |
plone.app.testing.layers.PLONE_FIXTURE |
资源: |
该层建立在PLONE_FIXTURE之上,用于修补 Plone 的 MailHost 实现。
有了它,任何发送电子邮件的尝试都会将它们中的每一个作为字符串存储在portal.MailHost.messages的列表中。
您不应直接使用此层,因为它不提供任何测试生命周期或事务管理。相反,您应该使用使用IntegrationTesting或FunctionalTesting 类创建的层,例如:
from plone.app.testing import MOCK_MAILHOST_FIXTURE MY_INTEGRATION_TESTING = IntegrationTesting( bases=( MY_FIXTURE, MOCK_MAILHOST_FIXTURE, ), name="MyFixture:Integration" )
PloneWithPackageLayer 类
大多数附加组件只需要加载 ZCML 文件并运行 GenericSetup 配置文件即可。
使用这个帮助类,可以很容易地实例化一个夹具:
from plone.app.testing import PloneWithPackageLayer import my.addon FIXTURE = PloneWithPackageLayer( zcml_package=my.addon, zcml_filename='configure.zcml', gs_profile_id='my.addon:default', name="MyAddonFixture" )
PloneWithPackageLayer 构造函数接受另外两个关键字参数: bases和additional_z2_products。
bases参数采用一系列基础层装置。除其他原因外,传递一个对 plone.app.testing API 进行其他调用的夹具很有用。在开发过程中可能会出现这种需求。
additional_z2_products参数采用一系列需要作为 Zope2 产品安装的包名称,并且是测试附加组件的依赖项。
集成和功能测试测试生命周期
plone.app.testing带有两个层类,IntegrationTesting 和FunctionalTesting,它们派生自plone.testing.z2中相应的层类。
这些类设置应用程序、请求和门户资源,并在每次测试运行之间重置夹具(包括各种全局缓存)。
与plone.testing中的类一样,IntegrationTesting类将为每个测试创建一个新事务并在测试拆除时回滚,这对于集成测试非常有效,而FunctionalTesting 将为每个测试创建一个堆叠的DemoStorage并弹出它在测试拆卸时,可以执行执行显式提交的代码(例如,通过使用zope.testbrowser的测试)。
创建自定义夹具时,通常的模式是创建一个以PLONE_FIXTURE作为其默认基础的新层类,将其实例化为单独的“夹具”层。该层不能直接用于测试,因为它没有测试/事务生命周期管理,但代表一个共享夹具,可能用于功能和集成测试。它也是遵循相同模式的其他层的延伸点。
一旦定义了这个fixture,就可以使用IntegrationTesting和FunctionalTesting类定义“最终用户”层。例如:
from plone.testing import Layer from plone.app.testing import PLONE_FIXTURE from plone.app.testing import IntegrationTesting, FunctionalTesting class MyFixture(Layer): defaultBases = (PLONE_FIXTURE,) ... MY_FIXTURE = MyFixture() MY_INTEGRATION_TESTING = IntegrationTesting(bases=(MY_FIXTURE,), name="MyFixture:Integration") MY_FUNCTIONAL_TESTING = FunctionalTesting(bases=(MY_FIXTURE,), name="MyFixture:Functional")
有关更全面的示例,请参阅下面的PloneSandboxLayer层。
克隆集成测试
层: |
plone.app.testing.PLONE_INTEGRATION_TESTING |
班级: |
plone.app.testing.layers.IntegrationTesting |
基地: |
plone.app.testing.PLONE_FIXTURE |
资源: |
门户(仅限测试设置) |
该层可用于针对基本 PLONE_FIXTURE层的集成测试。
如果您不需要设置任何其他共享夹具,则可以直接在测试中使用它。
但是,您通常不会扩展此层 - 见上文。
克隆功能测试
层: |
plone.app.testing.PLONE_FUNCTIONAL_TESTING |
班级: |
plone.app.testing.layers.FunctionalTesting |
基地: |
plone.app.testing.PLONE_FIXTURE |
资源: |
门户(仅限测试设置) |
该层可用于针对基本 PLONE_FIXTURE层的功能测试,例如使用zope.testbrowser。
如果您不需要设置任何其他共享夹具,则可以直接在测试中使用它。
同样,您通常不会扩展此层 - 见上文。
克隆 ZServer
层: |
plone.app.testing.PLONE_ZSERVER |
班级: |
plone.testing.z2.ZServer |
基地: |
plone.app.testing.PLONE_FUNCTIONAL_TESTING |
资源: |
门户(仅限测试设置) |
该层用于使用实时运行的 HTTP 服务器进行功能测试,例如使用 Selenium 或 Windmill。
同样,您通常不会扩展此层。要创建一个运行 ZServer 的自定义层,您可以使用与此相同的模式,例如:
from plone.testing import Layer from plone.testing import z2 from plone.app.testing import PLONE_FIXTURE from plone.app.testing import FunctionalTesting class MyFixture(Layer): defaultBases = (PLONE_FIXTURE,) ... MY_FIXTURE = MyFixture() MY_ZSERVER = FunctionalTesting(bases=(MY_FIXTURE, z2.ZSERVER_FIXTURE), name='MyFixture:ZServer')
有关详细信息,请参阅plone.testing中z2.ZSERVER层的描述 。
克隆 FTP 服务器
层: |
plone.app.testing.PLONE_FTP_SERVER |
班级: |
plone.app.testing.layers.FunctionalTesting |
基地: |
plone.app.testing.PL ONE_FIXTURE plone.testing.z2.ZSERVER_FIXTURE |
资源: |
门户(仅限测试设置) |
该层旨在使用实时 FTP 服务器进行功能测试。
它在语义上等同于PLONE_ZSERVER层。
有关详细信息,请参阅plone.testing中z2.FTP_SERVER层的描述 。
辅助函数
提供了许多帮助函数用于测试和自定义层。
克隆站点上下文管理器
- ploneSite(数据库=无,连接=无,环境=无)
在图层设置期间,使用此上下文管理器访问和更改 Plone 站点。在大多数情况下,您将不带参数地使用它,但如果您有特殊需要,可以将其绑定到特定的数据库实例。有关详细信息,请参阅plone.testing(此上下文管理器在内部使用)中对zopeApp()上下文管理器 的描述。
通常的模式是在您自己的层中的setUp()或tearDown()期间调用它:
from plone.testing import Layer from plone.app.testing import ploneSite class MyLayer(Layer): def setUp(self): ... with ploneSite() as portal: # perform operations on the portal, e.g. portal.title = u"New title"
在这里,portal是 Plone 站点根目录。事务在进入with块之前开始,并且将在退出块时提交,除非引发异常,在这种情况下它将被回滚。
在该块内,本地组件站点设置为 Plone 站点根目录,以便本地组件查找应该工作。
警告:不要尝试在ploneSite 块中加载 ZCML 文件。由于本地站点设置为 Plone 站点,因此您最终可能会在本地站点管理器中意外注册组件,这可能会导致以后出现酸洗错误。
注意:您不应该在测试中使用它,或者在基于此包中某一层的层的testSetUp()或 testTearDown()方法中使用它。请改用门户资源。
另请注意:如果您正在编写一个设置 Plone 站点夹具的层,您可能希望使用PloneSandboxLayer层基类,并实现setUpZope()、setUpPloneSite()、tearDownZope() 和/或tearDownPloneSite()方法。见下文。
用户管理
- 登录(门户,用户名)
以给定用户模拟登录。这基于 plone.testing 中的z2.login () 帮助程序,但不是传递特定的用户文件夹,而是传递门户(例如,通过门户层资源获得)。
例如:
import unittest2 as unittest from plone.app.testing import PLONE_INTEGRATION_TESTING from plone.app.testing import TEST_USER_NAME from plone.app.testing import login ... class MyTest(unittest.TestCase): layer = PLONE_INTEGRATION_TESTING def test_something(self): portal = self.layer['portal'] login(portal, TEST_USER_NAME) ...
- 登出()
模拟注销,即成为匿名用户。这相当于 plone.testing 中的z2.logout ()助手。
例如:
import unittest2 as unittest from plone.app.testing import PLONE_INTEGRATION_TESTING from plone.app.testing import logout ... class MyTest(unittest.TestCase): layer = PLONE_INTEGRATION_TESTING def test_something(self): portal = self.layer['portal'] logout() ...
- setRoles(门户、用户 ID、角色)
设置给定用户的角色。角色是角色列表。
例如:
import unittest2 as unittest from plone.app.testing import PLONE_INTEGRATION_TESTING from plone.app.testing import TEST_USER_ID from plone.app.testing import setRoles ... class MyTest(unittest.TestCase): layer = PLONE_INTEGRATION_TESTING def test_something(self): portal = self.layer['portal'] setRoles(portal, TEST_USER_ID, ['Manager'])
产品和配置文件安装
- applyProfile(portal, profileName, blacklisted_steps=None)
使用portal_setup工具按名称安装 GenericSetup 配置文件(通常是扩展配置文件) 。该名称通常由包名称和配置文件名称组成。不要使用profile-前缀。
例如:
from plone.testing import Layer from plone.app.testing import ploneSite from plone.app.testing import applyProfile ... class MyLayer(Layer): ... def setUp(self): ... with ploneSite() as portal: applyProfile(portal, 'my.product:default') ...
- quickInstallProduct(门户,产品名称,重新安装=假)
使用此功能将特定产品安装到给定的 Plone 站点,使用附加控制面板代码(门户设置)。如果reinstall为False并且产品已安装,则不会发生任何事情。如果reinstall为 true,则执行卸载并安装(如果已安装该产品)。productName应该是一个完整的点名称,例如Products.MyProduct或my.product。
例如:
from plone.testing import Layer from plone.app.testing import ploneSite from plone.app.testing import quickInstallProduct ... class MyLayer(Layer): ... def setUp(self): ... with ploneSite() as portal: quickInstallProduct(portal, 'my.product') ...
组件架构沙盒
- pushGlobalRegistry(门户,新=无,名称=无)
创建或获取全局组件注册表堆栈,并将新的注册表推送到堆栈顶部。这允许 Zope 组件架构注册(例如通过 ZCML 加载)被有效地拆除。
如果你打算使用这个函数,请阅读plone.testing中zca.pushGlobalRegistry ()的相应文档。特别要注意,您必须相互调用popGlobalRegistry() (见下文)。
该帮助程序基于zca.pushGlobalRegistry(),但还将修复 Plone 站点门户中的本地组件注册表,使其具有正确的基础。
例如:
from plone.testing import Layer from plone.app.testing import ploneSite from plone.app.testing import pushGlobalRegistry from plone.app.testing import popGlobalRegistry ... class MyLayer(Layer): ... def setUp(self): ... with ploneSite() as portal: pushGlobalRegistry(portal) ...
- popGlobalRegistry(门户)
拆除组件架构堆栈的顶部,如使用 pushGlobalRegistry()创建的那样
例如:
... def tearDown(self): with ploneSite() as portal: popGlobalRegistry(portal)
全局状态清理
- tearDownMultiPluginRegistration(pluginName)
PluggableAuthService “MultiPlugins” 保存在一个全局注册表中。如果你已经注册了一个插件,例如使用registerMultiPlugin() API,你应该在你层的tearDown() 方法中取消注册。你可以使用这个助手,传递一个插件名称。
例如:
from plone.testing import Layer from plone.app.testing import ploneSite from plone.app.testing import tearDownMultiPluginRegistration ... class MyLayer(Layer): ... def tearDown(self): tearDownMultiPluginRegistration('MyPlugin') ...
图层基类
如果您正在编写自定义层来测试您自己的 Plone 附加产品,您通常需要在设置时执行以下操作:
将新的DemoStorage堆叠在来自基础层的 DemoStorage 之上。这确保了在层设置期间执行的任何持久更改都可以完全删除,只需弹出演示存储即可。
堆叠一个新的 ZCML 配置上下文。这将有关加载了哪些 ZCML 文件的信息分开,以防其他独立层在该层被拆除后想要加载相同的文件。
推送一个新的全局组件注册表。这允许您注册组件(例如,通过加载 ZCML 或使用 zope.component的测试 API )并通过弹出组件注册表轻松取消这些注册。
加载产品的 ZCML 配置
将产品安装到测试夹具 Plone 站点
当然,您也可能希望进行其他更改,例如创建一些基本内容或更改一些设置。
在拆卸时,您将需要:
删除在安装过程中添加到全局注册表的任何可插入身份验证服务“多插件”。
弹出全局组件注册表以取消注册通过 ZCML 加载的组件。
弹出配置上下文资源以恢复其状态。
弹出DemoStorage以撤消任何持久更改。
如果您对设置进行了此广泛拆卸未涵盖的其他更改,您还需要在此处明确删除这些更改。
堆叠演示存储和组件注册表是避免测试之间的固定装置流血的最安全方法。但是,要确保一切都以正确的顺序发生可能会很棘手。
为了让事情变得更简单,您可以使用PloneSandboxLayer图层基类。这扩展了 plone.testing.Layer并为您实现了setUp()和 tearDown()。您只需覆盖以下一种或多种方法:
- setUpZope(self, app, configurationContext)
这在安装过程中被调用。app是 Zope 应用程序的根。 configurationContext是一个新堆叠的 ZCML 配置上下文。使用它来加载 ZCML,使用帮助 程序 plone.testing.z2.installProduct()安装产品,或操作其他全局状态。
- setUpPloneSite(自我,门户)
这在安装过程中被调用。portal是ploneSite()上下文管理器配置的 Plone 站点根目录。使用它在 Plone 站点内进行持久更改,例如使用applyProfile()或quickInstallProduct()帮助程序安装产品,或设置默认内容。
- tearDownZope(自我,应用程序)
这在拆卸期间调用,在全局组件注册表和堆叠的DemoStorage弹出之前。使用它来拆除任何额外的全局状态。
注意:全局组件注册 PAS 多插件注册会自动拆除。产品安装不是,因此如果在setUpZope()期间安装了任何产品, 则应使用uninstallProduct()帮助程序。
- tearDownPloneSite(自我,门户)
这在拆卸期间调用,在全局组件注册表和堆叠的DemoStorage弹出之前。在此方法中,设置了本地组件站点挂钩,使您可以访问本地组件。
注意:对 ZODB 的持久更改会通过堆叠的DemoStorage自动删除。因此,与此处描述的其他方法相比,此方法不太常用。
让我们展示一个更全面的示例,说明这样的层可能是什么样子。想象一下我们有一个产品my.product。它有一个configure.zcml文件,用于加载一些组件并注册一个GenericSetup配置文件,使其可安装在 Plone 站点中。在层设置上,我们要加载产品的配置并将其安装到 Plone 站点中。
该层通常位于包根目录的模块testing.py中,即my.product.testing:
from plone.app.testing import PloneSandboxLayer from plone.app.testing import PLONE_FIXTURE from plone.app.testing import IntegrationTesting from plone.testing import z2 class MyProduct(PloneSandboxLayer): defaultBases = (PLONE_FIXTURE,) def setUpZope(self, app, configurationContext): # Load ZCML import my.product self.loadZCML(package=my.product) # Install product and call its initialize() function z2.installProduct(app, 'my.product') # Note: you can skip this if my.product is not a Zope 2-style # product, i.e. it is not in the Products.* namespace and it # does not have a <five:registerPackage /> directive in its # configure.zcml. def setUpPloneSite(self, portal): # Install into Plone site using portal_setup self.applyProfile(portal, 'my.product:default') def tearDownZope(self, app): # Uninstall product z2.uninstallProduct(app, 'my.product') # Note: Again, you can skip this if my.product is not a Zope 2- # style product MY_PRODUCT_FIXTURE = MyProduct() MY_PRODUCT_INTEGRATION_TESTING = IntegrationTesting(bases=(MY_PRODUCT_FIXTURE,), name="MyProduct:Integration")
这里,MY_PRODUCT_FIXTURE是“fixture”基础层。如果其他层想要在此夹具上构建,则可以将其用作基础,但不会直接在测试中使用。为此,我们创建了一个IntegrationTesting 实例MY_PRODUCT_INTEGRATION_TESTING。
当然,我们也可以创建一个FunctionalTesting实例,例如:
MY_PRODUCT_FUNCTIONAL_TESTING = FunctionalTesting(bases=(MY_PRODUCT_FIXTURE,), name="MyProduct:Functional")
当然,我们可以在图层设置中做更多的事情。例如,假设产品的内容类型为“my.product.page”,我们想创建一些测试内容。我们可以这样做:
from plone.app.testing import TEST_USER_ID from plone.app.testing import TEST_USER_NAME from plone.app.testing import login from plone.app.testing import setRoles ... def setUpPloneSite(self, portal): ... setRoles(portal, TEST_USER_ID, ['Manager']) login(portal, TEST_USER_NAME) portal.invokeFactory('my.product.page', 'page-1', title=u"Page 1") setRoles(portal, TEST_USER_ID, ['Member']) ...
请注意,与测试不同,在层设置时没有用户登录,因此我们必须以测试用户的身份显式登录。在这里,我们还临时授予测试用户Manager角色,以允许对象构造(执行显式权限检查)。
注意:自动拆卸足以满足上述所有测试设置。如果在层设置期间所做的唯一更改是持久性、ZODB 内数据或全局组件注册表,则不需要额外的拆卸。对于任何其他被管理的全局状态,您应该编写一个 tearDownPloneSite()方法来执行必要的清理。
给定这一层,我们可以编写一个测试(例如在tests.py中),例如:
import unittest2 as unittest from my.product.testing import MY_PRODUCT_INTEGRATION_TESTING class IntegrationTest(unittest.TestCase): layer = MY_PRODUCT_INTEGRATION_TESTING def test_page_dublin_core_title(self): portal = self.layer['portal'] page1 = portal['page-1'] page1.title = u"Some title" self.assertEqual(page1.Title(), u"Some title")
有关如何编写和运行测试和断言的更多信息,请参阅plone.testing 。
常见的测试模式
plone.testing的文档包含有关编写各种测试的基本技术的详细信息。然而,在 Plone 环境中,一些模式往往会一次又一次地出现。下面,我们将尝试通过简短的代码示例对一些更常用的模式进行分类。
本节中的示例均旨在用于测试。有些在层设置/拆除中也可能有用。我们在这里使用了unittest语法,尽管这些示例中的大多数同样可以用于 doctest。
我们将假设您正在使用以PLONE_FIXTURE作为基础的层(无论是直接还是间接),并使用如上所示的IntegrationTesting或 FunctionalTesting类。
我们还将假设变量app、portal和request是从相关层资源中定义的,例如:
app = self.layer['app'] portal = self.layer['portal'] request = self.layer['request']
请注意,在使用plone.testing中的layered()函数 设置的 doctest 中,layer位于全局命名空间中,因此您可以执行例如 portal = layer['portal']。
在需要导入的地方,它们与代码示例一起显示。如果给定的导入或变量在同一部分中多次使用,则只会显示一次。
基本内容管理
要在门户的根目录中创建 ID 为“f1”的“文件夹”类型的内容项:
portal.invokeFactory('Folder', 'f1', title=u"Folder 1")
标题参数是可选的。也可以设置其他基本属性,例如 description 。
请注意,这可能会因Unauthorized异常而失败,因为测试用户通常无权在门户根目录中添加内容,并且invokeFactory()方法会执行显式安全检查。您可以设置测试用户的角色以确保他具有必要的权限:
从 plone.app.testing 导入 setRoles 从 plone.app.testing 导入 TEST_USER_ID setRoles(门户,TEST_USER_ID,['经理']) portal.invokeFactory('Folder', 'f1', title=u"Folder 1")
要获取此对象,请在其父对象中获取包装:
f1 = portal['f1']
对该对象的属性或方法进行断言:
self.assertEqual(f1.Title(), u"Folder 1")
修改对象:
f1.setTitle(u"Some title")
要在文件夹 f1 中添加另一个项目:
f1.invokeFactory('Document', 'd1', title=u"Document 1") d1 = f1['d1']
检查对象是否在容器中:
self.assertTrue('f1' in portal)
要从容器中删除对象:
德尔门户['f1']
默认情况下没有安装任何内容或工作流。您可以启用工作流:
portal.portal_workflow.setDefaultChain("simple_publication_workflow")
搜索
要获取portal_catalog工具:
from Products.CMFCore.utils import getToolByName catalog = getToolByName(portal, 'portal_catalog')
要搜索目录:
results = catalog(portal_type="Document")
关键字参数是搜索参数。结果是一个惰性列表。您可以在其上调用len()以获取搜索结果的数量,或对其进行迭代。列表中的项目是目录大脑。它们具有对应于为目录配置的“元数据”列的属性,例如 Title、Description等。请注意,这些是简单的属性(不是方法),并且包含来自源对象的相应属性或方法的值对象被编目的时间(即它们不一定是最新的)。
对搜索结果进行断言:
self.assertEqual(len(results), 1) # Copy the list into memory so that we can use [] notation results = list(results) # Check the first (and in this case only) result in the list self.assertEqual(results[0].Title, u"Document 1")
要在搜索结果中获取给定项目的路径:
self.assertEqual(resuls[0].getPath(), portal.absolute_url_path() + '/f1/d1')
要获取绝对 URL:
self.assertEqual(resuls[0].getURL(), portal.absolute_url() + '/f1/d1')
获取原始对象:
obj = results[0].getObject()
重新索引对象 d1 以使其目录信息是最新的:
d1.reindexObject()
用户管理
创建新用户:
from Products.CMFCore.utils import getToolByName acl_users = getToolByName(portal, 'acl_users') acl_users.userFolderAddUser('user1', 'secret', ['Member'], [])
参数是用户名(也将是用户 ID)、密码、角色列表和域列表(很少使用)。
要使特定用户在集成测试环境中处于活动状态(“登录”),请使用login方法并将用户名传递给它:
from plone.app.testing import login login(portal, 'user1')
注销(匿名):
from plone.app.testing import logout logout()
获取当前用户:
from AccessControl import getSecurityManager user = getSecurityManager().getUser()
按名称获取用户:
user = acl_users.getUser('user1')
或者通过用户 id(id 和用户名通常相同,但在实际场景中可能不同):
user = acl_users.getUserById('user1')
获取用户的用户名:
userName = user.getUserName()
获取用户ID:
userId = user.getId()
权限和角色
要在特定上下文中获取用户的角色(考虑本地角色):
from AccessControl import getSecurityManager user = getSecurityManager().getUser() self.assertEqual(user.getRolesInContext(portal), ['Member'])
要更改测试用户的角色:
from plone.app.testing import setRoles from plone.app.testing import TEST_USER_ID setRoles(portal, TEST_USER_ID, ['Member', 'Manager'])
传递不同的用户名以更改另一个用户的角色。
要将本地角色授予文件夹 f1 中的用户:
f1.manage_setLocalRoles(TEST_USER_ID, ('Reviewer',))
要检查文件夹“f1”中给定用户的本地角色:
self.assertEqual(f1.get_local_roles_for_userid(TEST_USER_ID), ('Reviewer',))
要在门户根目录中向角色“成员”和“经理”授予“查看”权限,而无需从其父级获取其他角色:
portal.manage_permission('View', ['Member', 'Manager'], acquire=False)
也可以在文件夹或单个内容项上调用此方法。
要在门户的上下文中断言哪些角色具有“查看”权限:
roles = [r['name'] for r in portal.rolesOfPermission('View') if r['selected']] self.assertEqual(roles, ['Member', 'Manager'])
要在门户的上下文中断言哪些权限已授予“审阅者”角色:
permissions = [p['name'] for p in portal.permissionsOfRole('Reviewer') if p['selected']] self.assertTrue('Review portal content' in permissions)
添加新角色:
portal._addRole('Tester')
现在可以将其分配给全局用户(使用setRoles助手)或本地用户(使用manage_setLocalRoles())。
要断言在给定上下文中哪些角色可用:
self.assertTrue('Tester' in portal.valid_roles())
工作流程
设置默认工作流链:
from Products.CMFCore.utils import getToolByName workflowTool = getToolByName(portal, 'portal_workflow') workflowTool.setDefaultChain('my_workflow')
在 Plone 中,大多数链只包含一个工作流,但portal_workflow 工具支持更长的链,其中一个项目同时受制于多个工作流。
要设置多工作流链,请用逗号分隔工作流名称。
获取默认工作流链:
self.assertEqual(workflowTool.getDefaultChain(), ('my_workflow',))
为“文档”类型设置工作流链:
workflowTool.setChainForPortalTypes(('Document',), 'my_workflow')
您可以传递多个类型名称来一次设置多个链。要设置多工作流链,请用逗号分隔工作流名称。要指示一个类型应该使用默认工作流,请使用特殊的链名称“(默认)”。
要获取门户类型“文档”的工作流链:
chains = dict(workflowTool.listChainOverrides()) defaultChain = workflowTool.getDefaultChain() documentChain = chains.get('Document', defaultChain) self.assertEqual(documentChain, ('my_other_workflow',))
要获取内容对象 f1 的当前工作流链:
self.assertEqual(workflowTool.getChainFor(f1), ('my_workflow',))
要在更改工作流程后更新所有权限:
workflowTool.updateRoleMappings()
通过调用事务“发布”来更改内容对象 f1 的工作流状态:
workflowTool.doActionFor(f1, 'publish')
请注意,这会执行显式权限检查,因此如果当前用户没有执行此工作流操作的权限,您可能会收到指示该操作不可用的错误。如果是这样,请使用login()或 setRoles()确保当前用户能够更改工作流状态。
检查内容对象 f1 的当前工作流状态:
self.assertEqual(workflowTool.getInfoFor(f1, 'review_state'), 'published')
特性
要设置门户根目录上的属性值:
portal._setPropValue('title', u"My title")
要在门户根目录上声明属性的值:
self.assertEqual(portal.getProperty('title'), u"My title")
要在 portal_properties工具中更改属性表中的属性值:
from Products.CMFCore.utils import getToolByName propertiesTool = getToolByName(portal, 'portal_properties') siteProperties = propertiesTool['site_properties'] siteProperties._setPropValue('many_users', True)
要在portal_properties工具的属性表中声明属性值 :
self.assertEqual(siteProperties.getProperty('many_users'), True)
安装产品和扩展配置文件
要应用特定的扩展配置文件:
from plone.app.testing import applyProfile applyProfile(portal, 'my.product:default')
这是安装产品配置的首选方法。
使用插件控制面板将插件产品安装到 Plone 站点:
from plone.app.testing import quickInstallProduct quickInstallProduct(portal, 'my.product')
要使用插件控制面板卸载和安装产品:
quickInstallProduct(portal, 'my.product', reinstall=True)
请注意,这两个都假设产品的 ZCML 已加载,这通常在层设置期间完成。有关如何执行此操作的更多详细信息,请参阅上面的图层示例。
在编写具有安装扩展配置文件的产品时,通常需要编写测试以在应用配置文件后检查站点的状态。一些更常见的此类测试如下所示。
要验证产品是否已安装(例如,通过 metadata.xml作为依赖项):
from Products.CMFPlone.utils import get_installer qi = get_installer(portal) self.assertTrue(qi.is_product_installed('my.product'))
要验证是否已安装特定的内容类型(例如,通过 types.xml):
typesTool = getToolByName(portal, 'portal_types') self.assertNotEqual(typesTool.getTypeInfo('mytype'), None)
要验证是否已安装新目录索引(例如通过 catalog.xml):
catalog = getToolByName(portal, 'portal_catalog') self.assertTrue('myindex' in catalog.indexes())
要验证是否已添加新的目录元数据列(例如通过 catalog.xml):
self.assertTrue('myattr' in catalog.schema())
要验证是否已安装新工作流(例如,通过 workflows.xml):
workflowTool = getToolByName(portal, 'portal_workflow') self.assertNotEqual(workflowTool.getWorkflowById('my_workflow'), None)
要验证新工作流是否已分配给某个类型(例如,通过 workflows.xml):
self.assertEqual(dict(workflowTool.listChainOverrides())['mytype'], ('my_workflow',))
要验证是否已将新工作流设置为默认值(例如,通过 workflows.xml):
self.assertEqual(workflowTool.getDefaultChain(), ('my_workflow',))
要测试portal_properties工具中的属性值(例如通过propertiestool.xml设置):
propertiesTool = getToolByName(portal, 'portal_properties') siteProperties = propertiesTool['site_properties'] self.assertEqual(siteProperties.getProperty('some_property'), "some value")
要验证是否已在portal_css工具中安装了样式表(例如,通过cssregistry.xml):
cssRegistry = getToolByName(portal, 'portal_css') self.assertTrue('mystyles.css' in cssRegistry.getResourceIds())
要验证是否已在 portal_javascripts工具中安装了 JavaScript 资源(例如,通过jsregistry.xml):
jsRegistry = getToolByName(portal, 'portal_javascripts') self.assertTrue('myscript.js' in jsRegistry.getResourceIds())
要验证是否已添加新角色(例如通过rolemap.xml):
self.assertTrue('NewRole' in portal.valid_roles())
要验证是否已将权限授予给定的一组角色(例如,通过 rolemap.xml):
roles = [r['name'] for r in portal.rolesOfPermission('My Permission') if r['selected']] self.assertEqual(roles, ['Member', 'Manager'])
遍历
要遍历视图、页面模板或其他资源,请使用 带相对路径的restrictedTraverse() :
resource = portal.restrictedTraverse('f1/@@folder_contents')
返回值是视图对象、页面模板对象或其他资源。可以调用它来获得实际响应(见下文)。
restrictedTraverse()执行显式安全检查,因此如果当前测试用户无权查看给定资源,则可能会引发Unauthorized 。如果你不想这样,你可以使用:
resource = portal.unrestrictedTraverse('f1/@@folder_contents')
您也可以在文件夹或其他内容项上调用它,以从该起点遍历,例如,这等效于上面的第一个示例:
f1 = portal['f1'] 资源 = f1.restrictedTraverse('@@folder_contents')
请注意,此遍历不会考虑IPublishTraverse适配器,并且您不能传递查询字符串参数。实际上, restrictedTraverse()和unrestrictedTraverse()实现了 TAL 中路径表达式发生的遍历类型,这与 URL 遍历相似但不完全相同。
手动查找视图:
from zope.component import getMultiAdapter view = getMultiAdapter((f1, request), name=u"folder_contents")
请注意,此处的名称不应包含@@前缀。
为了模拟IPublishTraverse适配器调用,假设视图实现了 IPublishTraverse:
next = view.IPublishTraverse(request, u"some-name")
或者,如果IPublishTraverse适配器与视图分离:
from zope.publisher.interfaces import IPublishTraverse publishTraverse = getMultiAdapter((f1, request), IPublishTraverse) next = view.IPublishTraverse(request, u"some-name")
模拟表单提交或查询字符串参数:
request.form.update({ 'name': "John Smith", 'age': 23 })
表单字典包含编组的请求。也就是说,如果您正在模拟使用:int之类的编组器的查询字符串参数或发布的表单变量(例如上例中的age:int),则应编组表单字典中的值(使用 int 而不是字符串,在上面的示例中),并且名称应该是基本名称(age而不是age:int)。
调用视图并以字符串形式获取响应正文:
view = f1.restrictedTraverse('@@folder_contents') body = view() self.assertFalse(u"An unexpected error occurred" in body)
请注意,这种方法并不完美。特别是,请求将没有正确的 URL 或路径信息。如果您的观点依赖于此,您可以通过在请求中设置相关键来伪造它,例如:
request.set('URL', f1.absolute_url() + '/@@folder_contents') request.set('ACTUAL_URL', f1.absolute_url() + '/@@folder_contents')
检查请求的状态(例如,在调用视图之后):
self.assertEqual(request.get('disable_border'), True)
到ins