Dexterity开发手册:第七章 测试内容类型
Dexterity开发手册:第七章 测试内容类型
http://www.315ok.org/blogfolder/402
http://www.315ok.org/logo.png
Dexterity开发手册:第七章 测试内容类型
Dexterity开发手册:第七章 测试内容类型
1.单元测试
2.集成测试
3.模拟测试
1.单元测试
编写简单的单元测试
正如所有优秀的开发人员都知道,自动化测试是非常重要的!如果你处在不适应自动化测试和测试驱动开发开发中,你应该阅读Plone testing tutorial.
在本节中,我们将假定您熟悉Plone的基础测试,将会向您展现出一些与示例类型相关的测试方法.首先,我们将添加一些单元测试,回想一下,单元测试是一个特定的函数或方法的测试,不依赖建立的外部环境.如果可以用简单的单元测试进行测试,就先使用简单单元测试,这样做是因为:
1.单元测试是可以快速写入的.
2.单元测试是可以快速运行的.
3.因为单元测试是独立的,更少没通过的可能是由于不正确的假设或凭运气测试造成的.
4.单元测试比集成测试更彻底,更全面.
您通常会进行大量的单元测试和少量的集成测试,以确保您的开发程序正确运行和工作.
通常需要用少量的集成测试配合大量的单元测试,来确保应用的正常工作.这是相关的理论.当我们确立内容类型时,经常更注重集成测试,因为一个模式类型和FTI相对于命令式程序设计,更像是Plone和dexterity框架的配置信息.我们不能用模式接口做单元测试,但是我们能够并且应该检测在模式类型进行安装时,对应的模式是否被载入和运行.我们经常用单元测试(在需要时,用模拟测试配合)进行事件处理器定制功能、默认值计算和其他程序代码.
本着这种理念,让我们给默认值处理查询和program.py不变的部分写一些单元测试.我们将用__int__.py和test_program.py文件添加目录测试,如下所示:
2. 每个测试都是独立的.没有测试层或测试样例需要安装/卸载.
3. 我们使用defaultTestLoader自动加载模块中的所有测试类.测试运行test_suite()方法在测试包中自动匹配寻找名称以test开头的模块.
运行单元测试后,我们可以输入:
通过配置buildout.cfg文件中一部分[test]来运行测试.这将提供更好的测试报告和一些高级的选项(如输出着色)在实例脚本中,我们也可以使用内置的测试运行,如:
./bin/test example.conference -t TestProgramUnit
当我们想让一些测试不被运行,可以是使用这条语句.例如他们使用集成测试时,需要长时间的进行设置.
获取测试覆盖率报告,可以运行:
2.集成测试
写关于Plone TestCase 集成测试
我们现在添加一些类型的集成测试.我们自定义类型addable至少应该能够确定包完全安装到正确的位置,并拥有正确的模式图.
为了帮助管理测试设置,我们将使用Zope的测试引擎概念层,它允许普通的(如Plone站点配置和安装产品配置)测试运行一次和被多个测试用例复用.
那些测试用例仍然可以改变运行环境,但是这些改变将被卸载,运行环境也将恢复到每个测试用例改变前的状态,便于测试隔离.
顾名思义,层,erp层.从一个层可以扩展到另一层.如果同样测试环境中的两个测试样例使用了两个拥有共同祖先的不同层,祖先层只需进行一次创建和一次卸载.
我们将使用collective.testcaselayer编写和管理层,我们需要它,所以我们在setup.py:
在tests/layer.py中添加我们自己的层:
它在Plone启动时建立了一个基层,然后为我们的包添加一些定制的层,在这个例子中,是安装 example.conference扩展文件.我们同样可以执行其他的,像创建原始内容或者给测试环境设置默认的角色.可以在collective.testcaselayer的文档中看到更详细的内容.为了使用层,我们可以创建一个基于PloneTestCase的新测试用例.首先我们将添加到test_program.py.(在下面的代码片段中,我们之前创建的单元测试已经被移到conserve空间中.)
我们有更多的东西可以测试(比如,我们可以更彻底测试添加的权限,我们需要在视图中检测sessions()的当前内容.)
但即使小型的集成测试结果是我们的产品已安装,内容类型也是可添加的,测试也有正确的环境,实例的类型提供正确的模式界面.
下面是需要注意的地方:
1.我们使用PloneTestCase,这意味着我们得到一个完整的Plone集成测试环境.测试教程了解更多详情.
2.我们给定制层设置了层属性,这意味着所有的测试在我们的测试用例都有例子.附:默认安装文件.
3.我们测试的内容是可添加的(这里,作为成员文件夹中的一个普通成员,因为这是默认的安全测试环境,
使用self.setRoles(['Manager'])来得到管理员权限和self.portal来进入根目录),FTI可以被安装和被定位,
并且FTI和实例类型know about正确的模式类型.
4.我们可以通过查阅视图和使用正确的方法进行测试.我们不必使用一个完整的功能测试或其它前端的测试.
如果需要那些,可以查看测试教程.
5.我们可以通过创建一个合适的对象和重新搜索来测试正在工作的特定索引.注意,在修改后我们需要重新索引来使目录更新到最新的.
defaultTestLoader会找到测试并进行加载,正如找到TestProgramUnit 测试用例一样.
在运行测试用例,可以输入:
与其他测试相似的.我们有tests/test_session.py来测试session类型.
这里需要注意的是如何检测session类型不能直接添加到文件夹中,但是可以添加到程序中.我们同样可以添加一个测试用例给possible_tracks() 方法,以及检测track的安装和数据队列和其工作流.
在tests/test_presenter.py中,我们测试Presenter类型
你也许注意到单元测试比集成测试要快上许多.那是因为集成测试需要运行整个zope环境,并启动一个Plone站点.幸运的是,有一个工具可以进行加速,假如你一直按照教程进行操作,你已经将这个工具加载到buildout中去了.这个命令会替代./bin/instance test,对ZOPE环境进行预载,并使测试速度更快
用roadrunner运行测试用例,可以执行:
按回车或者在command中输入test -s example.conference的命令,这将花费更少的时间,重新运行测试用例.
Roadrunner在添加和调试测试用例时性能最好.比如,得到pdb提示一个非常快捷的方法是:在测试中设置一个断点,执行import pdb,pdb.set_trace() ,在roadrunner中重新启动.可以进入测试代码和要进行测试的代码.
Roadrunner应该自动找到测试用例中改变的地方.然而,它可能能找不到应用代码中的变化,像grokked部件或者ZCML文件.假如没有找到,需要退出Roadrunner命令并重启.
模拟测试
使用一个模拟对象框架来写模拟基础测试
模拟测试是一个强大的测试方法,它可以分析测试的代码怎么与其他模块进行交互的.当需要对正在执行的部分进行测试时,不能简单的的通过查看返回值来进行判断,这时模拟测试就会大派用场了.在我们的例子中,我们可以处理:
假如想进行集成测试,我们可以使用PloneTestCase来进行事件处理,比如手动启动事件,如果让错误值通过测试,提出一个虚拟的测试实例(self.portal),并临时代替MailHost对象对象测试.
然而,在上述的方法,这样的集成测试会十分沉重,有时很难保证它顾全所有的情况下.例如,我们可能会错过没有发送邮件的情况.
输入模拟对象.模拟对象是一种“双测试“知道如何以及何时应该被调用.典型的方法如下:
1.创建模拟对象.
2.在"记录"的模式,模拟对象
3.记录您所操作的测试代码执行下的模拟对象.
您可以认定有关类型和值的参数,调用顺序,或调用一个方法或属性检索或设置的次数.
4.你可以模拟对象的行为.如指定返回值或在某些情况下引发异常.
5.初始化运行环境和测试代码,因此,它将使用模拟对象,而不是真正的对象环境,有时候可能是"patching"环境
6.放入"replay"模式的模拟框架
7.运行测试代码
8.适用于任何断言你通常会.
9.如果模拟对象被调用不正确(如错误的参数或次数过多)或不足(如没有调用预期方式),将引发模拟框架的异常
有几个Python模拟对象框架,Dexterity本身有一个强大的模拟功能,来自于plone.mocktestcase集成包,我们鼓励您阅读这两个包的文件,以便更好的了解模拟测试是如何工作的,可以有哪些选择.
如果你需要查看更多plone.dexterity测试例子可以在plone.mocketestcase集成包中寻找.
使用模拟测试框架,我们首先需要依靠plone.mocktestcase集成包.通常,我们把它添加到setup.py中并重新运行buildout.
如果您不熟悉模拟测试,它可能需要一点时间,让我们了解在测试运行这里发生了什么事:
1.首先,我们使用MockTestCase基类的create_dummp()方法创建一个虚拟演示对象.这不是一个模拟对象,
它只是类与设置属性最低的要求.因为我们没兴趣使用这种虚拟演示对象:它用来作为“输入”.
2.接下来,我们创建虚拟的事件.在这里我们选择来自zope.app.container标准库.
3.然后,我们定义几个变量,我们将使用在各种声明和模拟返回值.用户的数据,将形成我们虚拟用户的搜索结果,而电子邮件的数据传递给邮件主机.
4.接下来,我们为我们的代码中每一个需要查询和调用的工具创建mocks,为每一个mock我们用继承自MockTestCase的expect()方法来设置某些断言。例如,我们期望这个getPortalObject() 方法在Portal_url工具上将被调用一次,调用后将返回另外一个mock对象,即Portal_mock。self.expect(portal_mock.getProperty('email_from_address')).result('test@example.org')这一句中,我们期望(断言)portal_mock带参数"email_from_address"调用 getProperty()方法,所返回的即是 "test@example.org"。 为了解能设置的其他类型的断言,请查看 mocker 和plone.mocktestcase 的相关文档。
5.最重要的模拟声明self.expect(mail_host_mack.secureSend(message,email,sender,subject)).
这声明secureSend()方法被调用所需的信息,收件人地址,发件人地址和主题.
6.我们放入模拟重复模块,使用self.replay().
7.最后,我们用虚拟的presenter 和 event对象来调用测试代码。
8.在这种情况下,我们不会有任何"标准",如果需要的话,平时的单元测试认定方法是所有可用的.例如:来测试下的返回值的方法.在这种情况下都是来自模拟对象.而tearDown()方法的MockTestCase类会检查,其实所有的各种方法,被称为完全按预期.
使测试正常运行,需要运行如下:
模拟测试注意事项
模拟测试是一个有争议的话题.一方面,让你编写难以测试东西,模拟框架却可以,一旦你熟悉它,让往往是很费力的测试,可以提高1倍效率.另一方面,在试验阶段执行的代码也不可避免地模拟基础测试,有时,严谨的耦合测试是有意义的.使用模拟对象通常也意味着你需要一个很好了解外部API接口.否则,你的模拟未必代表了这些系统在现实世界中的行为.例如Martin Fowler这这里进行写入.
一如往常.如果你发现,你不能写一个模拟测试,测试方法下不读的每一行代码和逆向工程的模拟,然后集成测试可能更合适.事实上,它很谨慎,至少有一些集成测试.在任何情况下,你永远不能100%肯定有效的表示正在模拟目标.
另一方面,如果您正在测试的代码是在一个相对可预见的方式使用定义良好的API,模拟对象可以是一个有价值的方法来测试你的代码的“副作用”,和一个有用的工具来模拟像例外的东西可能难以产生,否则和输入值.
也请记住mock对象不一定是一个“全有或全无”的命题.在大多数情况下,可以使用简单的虚拟对象或“真实”的实例,并增加他们与几个mock对象为那些难以复制的测试用例.
英文原版
《《第六章 高级配置
[align=right]第八章 Reference》》
[/align]
2.集成测试
3.模拟测试
1.单元测试
编写简单的单元测试
正如所有优秀的开发人员都知道,自动化测试是非常重要的!如果你处在不适应自动化测试和测试驱动开发开发中,你应该阅读Plone testing tutorial.
在本节中,我们将假定您熟悉Plone的基础测试,将会向您展现出一些与示例类型相关的测试方法.首先,我们将添加一些单元测试,回想一下,单元测试是一个特定的函数或方法的测试,不依赖建立的外部环境.如果可以用简单的单元测试进行测试,就先使用简单单元测试,这样做是因为:
1.单元测试是可以快速写入的.
2.单元测试是可以快速运行的.
3.因为单元测试是独立的,更少没通过的可能是由于不正确的假设或凭运气测试造成的.
4.单元测试比集成测试更彻底,更全面.
您通常会进行大量的单元测试和少量的集成测试,以确保您的开发程序正确运行和工作.
通常需要用少量的集成测试配合大量的单元测试,来确保应用的正常工作.这是相关的理论.当我们确立内容类型时,经常更注重集成测试,因为一个模式类型和FTI相对于命令式程序设计,更像是Plone和dexterity框架的配置信息.我们不能用模式接口做单元测试,但是我们能够并且应该检测在模式类型进行安装时,对应的模式是否被载入和运行.我们经常用单元测试(在需要时,用模拟测试配合)进行事件处理器定制功能、默认值计算和其他程序代码.
本着这种理念,让我们给默认值处理查询和program.py不变的部分写一些单元测试.我们将用__int__.py和test_program.py文件添加目录测试,如下所示:
import unittest
import datetime
from example.conference.program import startDefaultValue
from example.conference.program import endDefaultValue
from example.conference.program import IProgram
from example.conference.program import StartBeforeEnd
class MockProgram(object):
pass
class TestProgramUnit(unittest.TestCase):
"""Unit test for the Program type
"""
def test_start_defaults(self):
data = MockProgram()
default_value = startDefaultValue(data)
today = datetime.datetime.today()
delta = default_value - today
self.assertEquals(6, delta.days)
def test_end_default(self):
data = MockProgram()
default_value = endDefaultValue(data)
today = datetime.datetime.today()
delta = default_value - today
self.assertEquals(9, delta.days)
def test_validate_invariants_ok(self):
data = MockProgram()
data.start = datetime.datetime(2009, 1, 1)
data.end = datetime.datetime(2009, 1, 2)
try:
IProgram.validateInvariants(data)
except:
self.fail()
def test_validate_invariants_fail(self):
data = MockProgram()
data.start = datetime.datetime(2009, 1, 2)
data.end = datetime.datetime(2009, 1, 1)
try:
IProgram.validateInvariants(data)
self.fail()
except StartBeforeEnd:
pass
def test_validate_invariants_edge(self):
data = MockProgram()
data.start = datetime.datetime(2009, 1, 2)
data.end = datetime.datetime(2009, 1, 2)
try:
IProgram.validateInvariants(data)
except:
self.fail()
def test_suite():
return unittest.defaultTestLoader.loadTestsFromName(__name__)这是一个使用python标准库的单元测试模块的简单实例.在这里需要注意几件事情: 1. 我们创建了一个虚拟的class来模拟一个程序的实例.里面没有任何东西,但是我们为了测试可以给它设置一些属性.这是一个非常简单的模拟测试方法.虽然有更复杂的模拟测试的方法,但现在用简单方法就行了. 2. 每个测试都是独立的.没有测试层或测试样例需要安装/卸载.
3. 我们使用defaultTestLoader自动加载模块中的所有测试类.测试运行test_suite()方法在测试包中自动匹配寻找名称以test开头的模块.
运行单元测试后,我们可以输入:
./bin/text example.conference显示5条测试通过结果通过配置buildout.cfg文件中一部分[test]来运行测试.这将提供更好的测试报告和一些高级的选项(如输出着色)在实例脚本中,我们也可以使用内置的测试运行,如:
./bin/instance test -s examplec.conference运行测试组件,我们可以:./bin/test example.conference -t TestProgramUnit
当我们想让一些测试不被运行,可以是使用这条语句.例如他们使用集成测试时,需要长时间的进行设置.
获取测试覆盖率报告,可以运行:
./bin/test example.conference --coverage测试覆盖率报告是很重要的,当你的模块测试覆盖率较低时,意味着你的测试没有覆盖到模块中的大部分代码路径,对检测程序bug或者防止未来出现错误没有那么有效了,测试覆盖率目标应是100%的.2.集成测试
写关于Plone TestCase 集成测试
我们现在添加一些类型的集成测试.我们自定义类型addable至少应该能够确定包完全安装到正确的位置,并拥有正确的模式图.
为了帮助管理测试设置,我们将使用Zope的测试引擎概念层,它允许普通的(如Plone站点配置和安装产品配置)测试运行一次和被多个测试用例复用.
那些测试用例仍然可以改变运行环境,但是这些改变将被卸载,运行环境也将恢复到每个测试用例改变前的状态,便于测试隔离.
顾名思义,层,erp层.从一个层可以扩展到另一层.如果同样测试环境中的两个测试样例使用了两个拥有共同祖先的不同层,祖先层只需进行一次创建和一次卸载.
我们将使用collective.testcaselayer编写和管理层,我们需要它,所以我们在setup.py:
install_requires=[
...
'collective.testcaselayer',
],修改setup.py,不要忘记重新运行buildout在tests/layer.py中添加我们自己的层:
from Products.PloneTestCase import ptc
import collective.testcaselayer.ptc
ptc.setupPloneSite()
class IntegrationTestLayer(collective.testcaselayer.ptc.BasePTCLayer):
def afterSetUp(self):
# Install the example.conference product
self.addProfile('example.conference:default')
Layer = IntegrationTestLayer([collective.testcaselayer.ptc.ptc_layer]) 它在Plone启动时建立了一个基层,然后为我们的包添加一些定制的层,在这个例子中,是安装 example.conference扩展文件.我们同样可以执行其他的,像创建原始内容或者给测试环境设置默认的角色.可以在collective.testcaselayer的文档中看到更详细的内容.为了使用层,我们可以创建一个基于PloneTestCase的新测试用例.首先我们将添加到test_program.py.(在下面的代码片段中,我们之前创建的单元测试已经被移到conserve空间中.)
import unittest
from zope.component import createObject
from zope.component import queryUtility
from plone.dexterity.interfaces import IDexterityFTI
from Products.PloneTestCase.ptc import PloneTestCase
from example.conference.tests.layer import Layer
from example.conference.program import IProgram
class TestProgramIntegration(PloneTestCase):
layer = Layer
def test_adding(self):
self.folder.invokeFactory('example.conference.program', 'program1')
p1 = self.folder['program1']
self.failUnless(IProgram.providedBy(p1))
def test_fti(self):
fti = queryUtility(IDexterityFTI, name='example.conference.program')
self.assertNotEquals(None, fti)
def test_schema(self):
fti = queryUtility(IDexterityFTI, name='example.conference.program')
schema = fti.lookupSchema()
self.assertEquals(IProgram, schema)
def test_factory(self):
fti = queryUtility(IDexterityFTI, name='example.conference.program')
factory = fti.factory
new_object = createObject(factory)
self.failUnless(IProgram.providedBy(new_object))
def test_view(self):
self.folder.invokeFactory('example.conference.program', 'program1')
p1 = self.folder['program1']
view = p1.restrictedTraverse('@@view')
sessions = view.sessions()
self.assertEquals(0, len(sessions))
def test_start_end_dates_indexed(self):
self.folder.invokeFactory('example.conference.program', 'program1')
p1 = self.folder['program1']
p1.start = datetime.datetime(2009, 1, 1, 14, 01)
p1.end = datetime.datetime(2009, 1, 2, 15, 02)
p1.reindexObject()
result = self.portal.portal_catalog(path='/'.join(p1.getPhysicalPath()))
self.assertEquals(1, len(result))
self.assertEquals(result[0].start, DateTime('2009-01-01T14:01:00'))
self.assertEquals(result[0].end, DateTime('2009-01-02T15:02:00'))
def test_tracks_indexed(self):
self.folder.invokeFactory('example.conference.program', 'program1')
p1 = self.folder['program1']
p1.tracks = ['Track 1', 'Track 2']
p1.reindexObject()
result = self.portal.portal_catalog(Subject='Track 2')
self.assertEquals(1, len(result))
self.assertEquals(result[0].getURL(), p1.absolute_url())
def test_suite():
return unittest.defaultTestLoader.loadTestsFromName(__name__) 这是对大多数内容类型进行测试的一个基本套路.我们有更多的东西可以测试(比如,我们可以更彻底测试添加的权限,我们需要在视图中检测sessions()的当前内容.)
但即使小型的集成测试结果是我们的产品已安装,内容类型也是可添加的,测试也有正确的环境,实例的类型提供正确的模式界面.
下面是需要注意的地方:
1.我们使用PloneTestCase,这意味着我们得到一个完整的Plone集成测试环境.测试教程了解更多详情.
2.我们给定制层设置了层属性,这意味着所有的测试在我们的测试用例都有例子.附:默认安装文件.
3.我们测试的内容是可添加的(这里,作为成员文件夹中的一个普通成员,因为这是默认的安全测试环境,
使用self.setRoles(['Manager'])来得到管理员权限和self.portal来进入根目录),FTI可以被安装和被定位,
并且FTI和实例类型know about正确的模式类型.
4.我们可以通过查阅视图和使用正确的方法进行测试.我们不必使用一个完整的功能测试或其它前端的测试.
如果需要那些,可以查看测试教程.
5.我们可以通过创建一个合适的对象和重新搜索来测试正在工作的特定索引.注意,在修改后我们需要重新索引来使目录更新到最新的.
defaultTestLoader会找到测试并进行加载,正如找到TestProgramUnit 测试用例一样.
在运行测试用例,可以输入:
./bin/test example.conference 现在你应该注意到层的建立与卸载.再次,利用-t参数来运行一个特定的测试用例(或测试方法).与其他测试相似的.我们有tests/test_session.py来测试session类型.
import unittest
from zope.component import createObject
from zope.component import queryUtility
from plone.dexterity.interfaces import IDexterityFTI
from Products.PloneTestCase.ptc import PloneTestCase
from example.conference.tests.layer import Layer
from example.conference.session import ISession
from example.conference.session import possible_tracks
class TestSessionIntegration(PloneTestCase):
layer = Layer
def test_adding(self):
# We can't add this directly
self.assertRaises(ValueError, self.folder.invokeFactory, 'example.conference.session', 'session1')
self.folder.invokeFactory('example.conference.program', 'program1')
p1 = self.folder['program1']
p1.invokeFactory('example.conference.session', 'session1')
s1 = p1['session1']
self.failUnless(ISession.providedBy(s1))
def test_fti(self):
fti = queryUtility(IDexterityFTI, name='example.conference.session')
self.assertNotEquals(None, fti)
def test_schema(self):
fti = queryUtility(IDexterityFTI, name='example.conference.session')
schema = fti.lookupSchema()
self.assertEquals(ISession, schema)
def test_factory(self):
fti = queryUtility(IDexterityFTI, name='example.conference.session')
factory = fti.factory
new_object = createObject(factory)
self.failUnless(ISession.providedBy(new_object))
def test_tracks_vocabulary(self):
self.folder.invokeFactory('example.conference.program', 'program1')
p1 = self.folder['program1']
p1.tracks = ['T1', 'T2', 'T3']
p1.invokeFactory('example.conference.session', 'session1')
s1 = p1['session1']
vocab = possible_tracks(s1)
self.assertEquals(['T1', 'T2', 'T3'], [t.value for t in vocab])
self.assertEquals(['T1', 'T2', 'T3'], [t.token for t in vocab])
def test_catalog_index_metadata(self):
self.failUnless('track' in self.portal.portal_catalog.indexes())
self.failUnless('track' in self.portal.portal_catalog.schema())
def test_workflow_installed(self):
self.folder.invokeFactory('example.conference.program', 'program1')
p1 = self.folder['program1']
p1.invokeFactory('example.conference.session', 'session1')
s1 = p1['session1']
chain = self.portal.portal_workflow.getChainFor(s1)
self.assertEquals(('example.conference.session_workflow',), chain)
def test_suite():
return unittest.defaultTestLoader.loadTestsFromName(__name__)这里需要注意的是如何检测session类型不能直接添加到文件夹中,但是可以添加到程序中.我们同样可以添加一个测试用例给possible_tracks() 方法,以及检测track的安装和数据队列和其工作流.
在tests/test_presenter.py中,我们测试Presenter类型
import unittest
from zope.component import createObject
from zope.component import queryUtility
from plone.dexterity.interfaces import IDexterityFTI
from Products.PloneTestCase.ptc import PloneTestCase
from example.conference.tests.layer import Layer
from example.conference.presenter import IPresenter
class TestPresenterIntegration(PloneTestCase):
layer = Layer
def test_adding(self):
self.folder.invokeFactory('example.conference.presenter', 'presenter1')
p1 = self.folder['presenter1']
self.failUnless(IPresenter.providedBy(p1))
def test_fti(self):
fti = queryUtility(IDexterityFTI, name='example.conference.presenter')
self.assertNotEquals(None, fti)
def test_schema(self):
fti = queryUtility(IDexterityFTI, name='example.conference.presenter')
schema = fti.lookupSchema()
self.assertEquals(IPresenter, schema)
def test_factory(self):
fti = queryUtility(IDexterityFTI, name='example.conference.presenter')
factory = fti.factory
new_object = createObject(factory)
self.failUnless(IPresenter.providedBy(new_object))
def test_suite():
return unittest.defaultTestLoader.loadTestsFromName(__name__)使用Roadrunner快速测试你也许注意到单元测试比集成测试要快上许多.那是因为集成测试需要运行整个zope环境,并启动一个Plone站点.幸运的是,有一个工具可以进行加速,假如你一直按照教程进行操作,你已经将这个工具加载到buildout中去了.这个命令会替代./bin/instance test,对ZOPE环境进行预载,并使测试速度更快
用roadrunner运行测试用例,可以执行:
./bin/roadrunner -s example.conference在运行测试用例后,会返回一个roadrunner提示.按回车或者在command中输入test -s example.conference的命令,这将花费更少的时间,重新运行测试用例.
Roadrunner在添加和调试测试用例时性能最好.比如,得到pdb提示一个非常快捷的方法是:在测试中设置一个断点,执行import pdb,pdb.set_trace() ,在roadrunner中重新启动.可以进入测试代码和要进行测试的代码.
Roadrunner应该自动找到测试用例中改变的地方.然而,它可能能找不到应用代码中的变化,像grokked部件或者ZCML文件.假如没有找到,需要退出Roadrunner命令并重启.
模拟测试
使用一个模拟对象框架来写模拟基础测试
模拟测试是一个强大的测试方法,它可以分析测试的代码怎么与其他模块进行交互的.当需要对正在执行的部分进行测试时,不能简单的的通过查看返回值来进行判断,这时模拟测试就会大派用场了.在我们的例子中,我们可以处理:
@grok.subscribe(IPresenter, IObjectAddedEvent)
def notifyUser(presenter, event):
acl_users = getToolByName(presenter, 'acl_users')
mail_host = getToolByName(presenter, 'MailHost')
portal_url = getToolByName(presenter, 'portal_url')
portal = portal_url.getPortalObject()
sender = portal.getProperty('email_from_address')
if not sender:
return
subject = "Is this you?"
message = "A presenter called %s was added here %s" % (presenter.title, presenter.absolute_url(),)
matching_users = acl_users.searchUsers(fullname=presenter.title)
for user_info in matching_users:
email = user_info.get('email', None)
if email is not None:
mail_host.secureSend(message, email, sender, subject)假如我们想测试是否发送了正确的邮件信息.我们需要用一些方法来查看secureSend()中传送的内容.唯一的办法是用需要判断的内容替换MailHost对象中在调用getToolByName(presenter,'MailHost')时获得的的内容.假如想进行集成测试,我们可以使用PloneTestCase来进行事件处理,比如手动启动事件,如果让错误值通过测试,提出一个虚拟的测试实例(self.portal),并临时代替MailHost对象对象测试.
然而,在上述的方法,这样的集成测试会十分沉重,有时很难保证它顾全所有的情况下.例如,我们可能会错过没有发送邮件的情况.
输入模拟对象.模拟对象是一种“双测试“知道如何以及何时应该被调用.典型的方法如下:
1.创建模拟对象.
2.在"记录"的模式,模拟对象
3.记录您所操作的测试代码执行下的模拟对象.
您可以认定有关类型和值的参数,调用顺序,或调用一个方法或属性检索或设置的次数.
4.你可以模拟对象的行为.如指定返回值或在某些情况下引发异常.
5.初始化运行环境和测试代码,因此,它将使用模拟对象,而不是真正的对象环境,有时候可能是"patching"环境
6.放入"replay"模式的模拟框架
7.运行测试代码
8.适用于任何断言你通常会.
9.如果模拟对象被调用不正确(如错误的参数或次数过多)或不足(如没有调用预期方式),将引发模拟框架的异常
有几个Python模拟对象框架,Dexterity本身有一个强大的模拟功能,来自于plone.mocktestcase集成包,我们鼓励您阅读这两个包的文件,以便更好的了解模拟测试是如何工作的,可以有哪些选择.
如果你需要查看更多plone.dexterity测试例子可以在plone.mocketestcase集成包中寻找.
使用模拟测试框架,我们首先需要依靠plone.mocktestcase集成包.通常,我们把它添加到setup.py中并重新运行buildout.
install_requires=[
...
'plone.mocktestcase',
],在test_presenter.py列举一个测试案例的类:import unittest
...
from plone.mocktestcase import MockTestCase
from zope.app.container.contained import ObjectAddedEvent
from example.conference.presenter import notifyUser
class TestPresenterUnit(MockTestCase):
def test_notify_user(self):
# dummy presenter
presenter = self.create_dummy(
__parent__=None,
__name__=None,
title="Jim",
absolute_url = lambda: 'http://example.org/presenter',
)
# dummy event
event = ObjectAddedEvent(presenter)
# search result for acl_users
user_info = [{'email': 'jim@example.org', 'id': 'jim'}]
# email data
message = "A presenter called Jim was added here http://example.org/presenter"
email = "jim@example.org"
sender = "test@example.org"
subject = "Is this you?"
# mock tools/portal
portal_mock = self.mocker.mock()
self.expect(portal_mock.getProperty('email_from_address')).result('test@example.org')
portal_url_mock = self.mocker.mock()
self.mock_tool(portal_url_mock, 'portal_url')
self.expect(portal_url_mock.getPortalObject()).result(portal_mock)
acl_users_mock = self.mocker.mock()
self.mock_tool(acl_users_mock, 'acl_users')
self.expect(acl_users_mock.searchUsers(fullname='Jim')).result(user_info)
mail_host_mock = self.mocker.mock()
self.mock_tool(mail_host_mock, 'MailHost')
self.expect(mail_host_mock.secureSend(message, email, sender, subject))
# put mock framework into replay mode
self.replay()
# call the method under test
notifyUser(presenter, event)
# we could make additional assertions here, e.g. if the function
# returned something. The mock framework will verify the assertions
# about expected call sequences.
...
def test_suite():
return unittest.defaultTestLoader.loadTestsFromName(__name__)需要注意的是为了简洁起见已移除其他的测试模块如果您不熟悉模拟测试,它可能需要一点时间,让我们了解在测试运行这里发生了什么事:
1.首先,我们使用MockTestCase基类的create_dummp()方法创建一个虚拟演示对象.这不是一个模拟对象,
它只是类与设置属性最低的要求.因为我们没兴趣使用这种虚拟演示对象:它用来作为“输入”.
2.接下来,我们创建虚拟的事件.在这里我们选择来自zope.app.container标准库.
3.然后,我们定义几个变量,我们将使用在各种声明和模拟返回值.用户的数据,将形成我们虚拟用户的搜索结果,而电子邮件的数据传递给邮件主机.
4.接下来,我们为我们的代码中每一个需要查询和调用的工具创建mocks,为每一个mock我们用继承自MockTestCase的expect()方法来设置某些断言。例如,我们期望这个getPortalObject() 方法在Portal_url工具上将被调用一次,调用后将返回另外一个mock对象,即Portal_mock。self.expect(portal_mock.getProperty('email_from_address')).result('test@example.org')这一句中,我们期望(断言)portal_mock带参数"email_from_address"调用 getProperty()方法,所返回的即是 "test@example.org"。 为了解能设置的其他类型的断言,请查看 mocker 和plone.mocktestcase 的相关文档。
5.最重要的模拟声明self.expect(mail_host_mack.secureSend(message,email,sender,subject)).
这声明secureSend()方法被调用所需的信息,收件人地址,发件人地址和主题.
6.我们放入模拟重复模块,使用self.replay().
7.最后,我们用虚拟的presenter 和 event对象来调用测试代码。
8.在这种情况下,我们不会有任何"标准",如果需要的话,平时的单元测试认定方法是所有可用的.例如:来测试下的返回值的方法.在这种情况下都是来自模拟对象.而tearDown()方法的MockTestCase类会检查,其实所有的各种方法,被称为完全按预期.
使测试正常运行,需要运行如下:
./bin/test example.conference -t TestPressenterMock请注意模拟测试的速度就像单元测试一样,所以通过是没必要rpadrunner的.模拟测试注意事项
模拟测试是一个有争议的话题.一方面,让你编写难以测试东西,模拟框架却可以,一旦你熟悉它,让往往是很费力的测试,可以提高1倍效率.另一方面,在试验阶段执行的代码也不可避免地模拟基础测试,有时,严谨的耦合测试是有意义的.使用模拟对象通常也意味着你需要一个很好了解外部API接口.否则,你的模拟未必代表了这些系统在现实世界中的行为.例如Martin Fowler这这里进行写入.
一如往常.如果你发现,你不能写一个模拟测试,测试方法下不读的每一行代码和逆向工程的模拟,然后集成测试可能更合适.事实上,它很谨慎,至少有一些集成测试.在任何情况下,你永远不能100%肯定有效的表示正在模拟目标.
另一方面,如果您正在测试的代码是在一个相对可预见的方式使用定义良好的API,模拟对象可以是一个有价值的方法来测试你的代码的“副作用”,和一个有用的工具来模拟像例外的东西可能难以产生,否则和输入值.
也请记住mock对象不一定是一个“全有或全无”的命题.在大多数情况下,可以使用简单的虚拟对象或“真实”的实例,并增加他们与几个mock对象为那些难以复制的测试用例.
英文原版
《《第六章 高级配置
[align=right]第八章 Reference》》
[/align]