级别: 初级
Peter Zielczynski (PZielczynski@tact.com), 技术方案经理, A Consulting Team, Inc.
2005 年 5 月 08 日
2006 年 6 月 22 日 更新
本文阐述了一种从用例产生功能测试用例的正式方法,包括如何创建一个用例,产生所有场景,并且创建合理的测试用例,以及使用IBM Rational RequisitePro进行从用例到场景和测试用例的追踪。
一个需求被定义成 "系统必须遵从的条件或能力"。
它可以是:
- 一个顾客或用户所需要的,用以解决一个问题或达成一个目标的能力
- 一个必须被一个系统所满足和拥有的,用以满足一个合同、标准、规格、规则或其它正式强制文档的能力
- 一个被涉众所强加的限制
图1显示了带有不同需求级次的需求金字塔
图1. 需求金字塔
![Requirement Pyramid](https://i-blog.csdnimg.cn/blog_migrate/0898f00d6bcbaa2d7f4e183e0d3cc35d.png)
最高层的是涉众需求。通常,一个项目包含五到十五个这样的高层需求。较低层次的是特性,用例和补充规约。不同层次的需求有不同的细节。越低的层次需要越多的细节。例如,一个需求可以是:"数据必须是持久的"。特性可以将此需求精化为:"系统应当使用一个关系数据库"。在补充规约层次,需求会更加详细:"系统应当使用Oracle 9i数据库"。层次越低,需要越详细的需求。
追踪是这样一种技术,在系统中它能为不同层次的需求之间提供关联。这个技术帮助你确定任何需求的起源。图 2 阐述了从高层次到低层次需求是如何被追踪的。每一个需求通常映射到几个特性,然后这几个特性映射到用例和补充需求。
图2. 追踪需求金字塔
![Requirement Pyramid](https://i-blog.csdnimg.cn/blog_migrate/730de4a3fb7470a50038b54b345001d2.png)
用例描述了功能性的需求,补充规约描述了非功能性的项目。另外,每个用例映射到许多场景。映射用例到场景,是一对多的关系。 场景映射到测试用例也是多对一的关系。另一方面,在需要与特性之间,是多对多的映射。
追踪起到了几个重要的作用:
- 验证一个实现是否完成了所有的需求:用户要求的每一件事情都被实现了
- 验证应用程序只做了所要求的事情:不会去实现用户从未要求的事情
- 有助于变更管理:当一些需求变更后,我们想知道哪些测试用例应当被重新执行以测试这个变化
一个追踪项是一个项目元素,其需要从另一个元素进行追踪。按照IBM Rational RequisitePro,它是一个需求类型的实例所表示的任何事情。在RequisitePro中一些需求类型的例子是涉众需求,特性,用例,参与者,和术语条款。
在RequisitePro中,有一种按照特定视图展示追踪性的便利方法。图3 显示了将特性映射到用例的一个例子。
图3. 在RequisitePro中的追踪关系
![Traceability](https://i-blog.csdnimg.cn/blog_migrate/a610d7afed1b86d657768ea36cfc2e14.png)
这里有一些问题,这些箭头应指向哪里:是从更低的层次到更高的层次,还是从更高的层次到更低的层次。甚至在RequisitePro中的两个例子使用了两个不同的方法。答案是没有关系,只要你在项目中始终如一地使用它们就可以了。
参与者是与系统交互的某人或某事。用例是根据操作顺序的一个系统描述。它为参与者产生了一个看的见的结果或数据值。以下是用例的一些特征:
- 被参与者初始化
- 模拟参与者和系统之间的交互
- 描述操作的序列
- 获取功能需求
- 为参与者提供数据值
- 表示完整的和有意义的事件流
用例的目的是促使开发者、顾客和用户之间对系统应做些什么达成一致。用例在开发者和顾客之间达成了某种契约。它同时也是用例实现的基础,它在程序设计中起到了非常重要的作用。另外,你可以从用例中产生序列图,协作图和类图。此外,你可以从用例产生用户文档。用例可能还在计划迭代的技术内容方面有帮助,并且使系统开发者更好地了解系统的意图。最后,你可以使用它们作为测试例程的输入。
用例图显示了参与者和用例之间的关系。在本文我们使用一个在线书店作为项目的一个例子。图4 展示了这个项目的用例图。
图4. 用例图
![Diagram](https://i-blog.csdnimg.cn/blog_migrate/c7efa09a5be458754ee282af2953d0b0.png)
用例的通用格式是:
- 简要描述
- 事件流
- 基本流程
- 可选流程 1
- 可选流程 2
- 特殊需求
- 前提条件
- 后置条件
- 扩展点
- 环境图
- 活动图
基本流程包括最通常的一系列行为,此步骤发生在每件事正确运转的时候。可选流程表现了流程的变更,包括不很普遍的情况和错误条件。环境图是用例图的一部分,向参与者和其它用例显示了特殊用例之间的关联。活动图是一个解释用例的流程图。环境图和活动图不是必须的,但是可以帮助你可视化用例和它在项目中的位置。
在我们的在线书店项目中,用例的基本流程的安置顺序也许像这样:
- B1 用户在浏览器输入网站的地址。
系统显示登陆界面。
- B2 用户输入电子邮件地址和密码。
系统确认正确的登陆,显示主页,并且提示输入搜索字符串
- B3 用户输入搜索字符串—书名的一部分。
系统返回与搜索标准匹配的全部书籍。
- B4 用户选择一本书。
系统显示这本书的详细信息。
- B5 用户把这本书放在购物车中。
购物车中的货物显示给用户
- B6 用户选择"进入到结帐" 选项。
系统索要送货地址。
- B7 用户确认送货地址。
系统显示送货选项。
- B8 用户选择送货选项。
系统询问使用哪种信用卡。
- B9 用户确认储存在系统中的信用卡。
系统请求最终确认此次订购。
- B10 用户订购。
系统返回确认数量。
除了基本流程以外,还有许多可选流程。例如,第一个可选流程描述了当用户是一个新的用户时所发生的事情(不是在线书店的已注册用户)。在基本流程中,用户经常拥有一个用户ID和密码。相反,可选流程 1 描述了当第一次用用户需要注册并提供顾客数据时的情况。可选流程的另一个例子是无效的密码。用户输入了错误的密码,系统显示错误信息。
表 1 显示了"安置顺序"用例中的可选流程:
表 1: 可选流程 | |
---|---|
A1 | 未注册用户 |
A2 | 无效密码 |
A3 | 没有找到匹配搜索标准的书 |
A4 | 下架书籍 |
A5 | 在购物车中放入一本书后继续购物 |
A6 | 输入新的地址 |
A7 | 输入新的信用卡 |
A8 | 取消订单 |
下列约定用于为事件流命名:
基本流程:B
可选流程:A1,A2, A3,...
在基本流程中的步骤:B1,B2, B3, ...
在可选流程1中的步骤:A1.1, A1.2, A1.3, ...
在可选流程2中的步骤: A2.1, A2.2, A2.3, ...
为得到可选流程,使用活动图 5。图 5显示了描述用例的活动图。
图5. 活动图
![Activity](https://i-blog.csdnimg.cn/blog_migrate/d796e3b639f33c938cda2e6d58c1e46b.png)
基本流程是一条向下的直线,然而可选流程可以是向前或向后的循环线。
在创建一个测试用例之前,你需要为所给用例确定全部的场景。一个场景是用例的一个实例。它描述了一个贯穿事件流的特殊路径。图 6是一个假设的图表,它描绘了一个拥有基本流程B和可选流程A1, A2, A3, A4的用例。为了找到全部的场景,我们需要画出贯穿于此图的所有场景。
图6. 在用例中找到场景
![scenarios in a use case](https://i-blog.csdnimg.cn/blog_migrate/e04c6f277f71c22ffcc695bfe6719ac7.png)
每一个可选流程都有一个场景,并且每个结合的可选流程都有一个场景。显然,这里场景多于可选流程,因为一个场景用于A1,另一个场景用于A2,还有一个场景用于这两个的结合。
描述场景最简单的方法是提供一系列的可选流程,例如,做两遍流程A2,然后做一遍流程A6:
SC16:A2,A2,A6。
另一种描述场景的方法是列出它的所有步骤,但是这种方法既困难又未必详细。
如果你陷了无限循环(向后循环),这时该怎么办?理论上它将产生无限的场景。图 7显示了一个无限循环。
图7. 无限循环。
![Loops](https://i-blog.csdnimg.cn/blog_migrate/55f2757113dcfecc19f19cfe7ab52ac1.png)
最合理的方法是做一遍基本流程,一遍循环流程,然后再做一遍循环流程。如果程序能为这两个循环都工作的话,你能假设它能为所有的循环工作。
书籍订阅的例子中包含了一个基本流程和8个可选流程。它们中的4个向前走,另外4个向后走。如果你想描述全部可能的用例结合,你将有超过4000个场景 (这里有8个可选流程,我们想让其中4个做两次,因为他们向后循环,所以是2的(8+4)次幂,,等于4096。很明显,我们不需要把他们全部做完。
选择能代表这四千个场景的一个合理子集。通常,明智的做法是选择一个基础流程,一个覆盖了每一个可选流程的场景,以及一些合理的可选流程的结合。使用表 1的例子,做一个包含流程A1和A7的场景也许没有什么意义,因为它们在图标上分隔太远以至于不能互相影响。但是,做A1和A2是有意义的,因为他们互相紧埃,之间互相影响。
表 2 图解了选择的场景:一个代表基本流程,8个代表每一个可选流程,6个反射可一些流程的结合(特别是那些拥有两个距离很近的向后循环)。
以下15个场景值得测试:
表2. 值得测试的场景
表2:获取选择的场景 | |
---|---|
场景 1 基础流程 | 场景 9 A8 |
场景 2 A1 | changjing 10 A1,A2 |
场景 3 A2 | 场景 11 A3,A4 |
场景 4 A3 | 场景 12 A4,A5 |
场景 5 A4 | 场景 13 A3,A5 |
场景 6 A5 | 场景 14 A6, A7 |
场景 7 A6 | 场景 15 A7,A8 |
场景 8 A7 |
在RequisitePro中场景不是一个标准的需求类型,所以你需要增加它成为一个新的需求种类。为了完成它,去Project Properties,选择Requirement Types 标签,然后点击 Add。接下来,填充到适当的区域 (如图 8所示),然后点击OK。
图8. 增加一个需求类型场景
![Adding a scenario](https://i-blog.csdnimg.cn/blog_migrate/7774cf31184b978fb48d77e6a8839058.png)
创建了这个需求类型之后,我们应当输入全部的场景并设置从用例到这些场景的追踪,如图 9所示。
图9. 从用例到场景的追踪
![Traceability](https://i-blog.csdnimg.cn/blog_migrate/8fadd68d83673ef094515efd6d1bbcc1.png)
在RequisitePro中,你可以用用例的名字和一系列可选流程的名字为场景命名(例如:UC1, A6, A7)。
既然你有了所有的场景,你就需要获得测试用例。这里需要完成4个步骤:
- 为用例的每个步骤确定变量
- 为每个变量有效地确定不同的选项
- 在测试用例中测试结合选项
- 为变量赋值
以下部分描述了这些步骤的细节。
在所给场景的所有步骤中你需要确定全部输入的变量。例如,在某些步骤中,如果用户输入了用户ID和密码,这就产生了两个变量。一个变量是用户ID,另一个变量是密码。变量也可以被用户选择(例如,保存更改或取消)。
这里是书籍订购例子的全部变量:
在步骤B2中,这里有两个变量:电子邮件地址和密码。它们全是字符串。在步骤B3中,搜索一本书,这个变量是一个搜索字符串,因此它也是字符串。 在步骤B4,我们需要从系统返回的目录中选择一本书。在步骤 B8中,我们需要选择送货方式。Amazon.com 提供了4个选择。
如果它们引发不同的系统行为,选项将是"明显不同的"。例如,如果我们选择大概6到10的字符长的用户id,以下的输入显然是不同的 :
- Alex ——因为太短,我们希望显示一个错误的信息
- Alexandria ——因为它是一个有效的用户id
- Alexandrena ——因为太长,我们期待系统可以阻止我们输入如此长的id
然而,"Alexandria" 和 "JohnGordon" 却不是明显不同,因为它们都是有效的用户id,可以使系统有相同的反应。
以下的指导方针描述了一些特殊的例子。
一个选项可以认为是非常不同的,如果:
- 它引发了不同的程序流程 (通常是一个可选流程)
例如
- 输入无效的密码将会引发可选流程2
- 它引发不同的错误信息
例如
- 如果电子邮件太长,信息就会是 "电子邮件应当在50个字符以内"
- 如果电子邮件没有包含 @ 符号,信息就会是:"无效的电子邮件地址"
- 它显示了不同的用户界面
例如
- 如果付费的方式是信用卡,在区域里显示的是信用卡号输入号码,产品有效期和持卡人的姓名。
- 它使得在下框中有不同的选则可以使用
例如
顾客注册界面包含了 "国家和州/省"的下拉框。"州/省"的下拉框一般是基于国家的选择:比如美国,它包含了全部的州,加拿大是全部的省,其他国家是缺省的。它创建了3个不同的选项:
- 美国
- 加拿大
- 其它国家
- 一些商业规则的输入
例如
假设这里已有一项规则 "如果实下午6点以后下订单是,用户选择头天晚上送货,将会通知这本书将会在后天到达。",我们有两个:独立的选择
- 头天晚上发货,在下午6点之前下订单
- 头天晚上发货,在下午6点以后下订单
- 这里有一个边缘情况
例如
因为密码应当包含至少6个字符,我们因该测试:
- 5个字符的密码
- 6个字符的密码
- 改变某些事情对使用默认
例如
在信用卡支付界面,持卡人的名字通常是订货人的姓名。这就产生了2个独立的选项:
- 保持默认持卡人的姓名
- 改变持卡人的姓名
- 没有明确定义输入格式,不同的用户有不同的理解
例如
不同的人有不同的书写电话号码的方法:
- 使用括号 (973) 123 4567
- 使用破折号 973-123-4567
- 数字间使用空格 973 123 4567
- 不同的国家有不同的规则
例如
信用卡有效日期的格式在美国和加拿大就不同
如果我们测试数字,我们会考虑以下选项:
- 常规的以及从应用观点上是合理的数字
- 0
- 负数
- 带两位小数的数字
- 能输入的最大数字(99999999999999 - 输入最多的9)
你怎么知道区域内能够输入的最大和最小的长度?这个需求可以从不同的来源得到。有时,它来自于商业的分析或顾客。例如,如果我们输入Dun 和 Bradstreet 数字,这就被看成是一个公司,它总是包含9个数字。这是商业的需求。
然而,它一般不会来自于顾客和用户。如果你问客户姓名区域有多长,它们会告诉你不用担心长度,只要要合理即可。在此例中,设计步骤决定了变量的长度而不是需求步骤所决定的。
另一种情形,数据分析师或者数据库设计者会提出建议——例如,在如果公司的全部应用软件中姓名应在30个字符以内,你的申请的用户名就得依照这个标准。
不管需求的来源是何处,在我们做测试用例之前它都要被同意并且备份。
这里有一个关于上述讨论的需求应该在哪里进行文档化的问题。在用例中一个增加这个需求的地方是被称为Special Requirements的地方。另外一个可以放置这种需求的地方是术语表或者数据字典。另外,你可以详细说明一个独立的文档类型,在那里你可以描述全部应用程序中的所有变量。在许多用例中如果同样的变量出现在许多界面中时,这就变得特别有意义,因此你可以在一个文档里说的所有名字截止在30个字符以内,全部的地址截至在100个字符以内。然而,如它们对一个用例是特殊的,最好把它们加到那个用例的特殊需求中。
表 3显示了在示例项目的基础数据流程中为变量确定的选项:
步骤 | 变量 | 测试选项 | ||||||
---|---|---|---|---|---|---|---|---|
B1 | 网站 | 实际URL | ||||||
B2 | 电子邮件 | 正常 | 空白 | 最少允许字符数(1 字符) | 最多允许字符数(50 字符) | 比允许最多字符多一个字符(51 字符) | 非常长 (257 字符) | 无效 (缺少 @ 字符) |
B2 | 密码 | 正常 | 空白 | 太短(5 字符) | 最少允许字符数(6 字符) | 最多允许字符数 (10 字符) | 比允许最多字符数多一个字符(11 字符) | 非常长 (257 字符) |
B3 | 搜索字符串 | 正常 | 空白 | 最少允许字符数 (1 字符) | 最多允许字符数 (300 字符) | 比允许最多字符数多一个字符 (301 字符) | ||
B4 | 选择 | 第一次选择 | 最后一次选择 | |||||
B5 | 行为选择 | 加入购物车 | ||||||
B6 | 行为选择 | 进行结帐 | ||||||
B7 | 送货地址 | 确认地址 | ||||||
B8 | 发货方式 | 5 天 | 3 天 | 2 天 | 头天晚上 | |||
B9 | 支付方式 | 确认信用卡 | ||||||
B10 | 行为选择 | 下订单 |
在前面的步骤,你确定了所有的选项。在此步骤,你需要在一系列的测试用例中使它们结合在一起。
图 10用图描述了测试的选项。每一个纵队都有一个需要测试的变量,每一行是一个选项:R 是正常, E 是空, 然后是一个字符,50个字符,51个字符,等等。 "L" 代表非常大, "I" 代表非法的。
图 10:每个步骤的待测试选项
![options to be tested](https://i-blog.csdnimg.cn/blog_migrate/3a2b6f6c113a12251baf375196d84022.png)
后面有妨碍的选项把用户从基本流程中抛离出去:它们表示在可选流程中描述的一些错误。因为你一般为第一个场景设计特殊用例,所以你可以移动它们(在一些其它的场景中测试它们)。 无论剩下了什么,你需要创建最小数量的覆盖全部情形的测试用例。
通过连接圈创建测试用例,如图 11所示。
图 11:合并选项以创建测试用例
![Combine options](https://i-blog.csdnimg.cn/blog_migrate/2766aafec44c1efc3f8c9cd46bb8327f.png)
为了创建第一个测试用例,你可以选择并连接任何选项。当你创建第二个测试用例时,跳出第一个用例没有使用的选项。继续增加测试用例直到全部图的节点(如图 11所示)被覆盖。通常你需要从4到6测试用例去覆盖全部需要测试的选项。然而,一些特殊的情形需要的更多。
测试用例的分配可以在测试用例分配表格中描绘,如表 4所示。
步骤号 | 变量或者选择 | TC1 | TC2 | TC3 | TC4 |
---|---|---|---|---|---|
B1 | 网站 | 实际 URL | 实际 URL | 实际 URL | 实际 URL |
B2 | 电子邮件 | 正常 | 最少允许的字符数(1 字符) | 最多允许的字符数 (50 字符) | 正常 |
B2 | 密码 | 正常 | 最少允许的字符数 (6 字符) | 最多允许的字符数 (10 ) | 最少允许的字符数 (6 字符) |
B3 | 搜索字符串 | 正常 | 最少允许的字符数 (1 字符) | 最多允许的字符数(300 字符) | 正常 |
B4 | 选择 | 第一次选择 | 最后一次选择 | 第一次选择 | 最后一次选择 |
B5 | 行为选择 | 添加到购物车中 | 添加到购物车中 | 添加到购物车中 | 添加到购物车中 |
B6 | 行为选择 | 进行结帐 | 进行结帐 | 进行结帐 | 进行结帐 |
B7 | 送货地址 | 确认地址信息 | 确认地址信息 | 确认地址信息 | 确认地址信息 |
B8 | 送货方式 | 5 天 | 3 天 | 2 天 | 头天晚上 |
B9 | 支付方式 | 确认信用卡信息 | 确认信用卡信息 | 确认信用卡信息 | 确认信用卡信息 |
B10 | 行为选择 | 下订单 | 下订单 | 下订单 | 下订单 |
表 4 描述了图11中每列包含不同的测试用例。每一行相应的是用户输入的变量。
在此步中,你替换如"一个非常长的名字" 或"扩展很长的电话号码"这样的占位符为像"Georgiamitsopolis" 和 "011-48 (242) 425-3456 ext. 1234"这样实际有价值的东西。在此步,你还可以分裂表 4所示的表格中的测试用例,为每个测试用例创建独立的表格。
如书籍订购使用用例的测试用例 1,你就可以创建一个像表 5这样的表格。这就给你的测试者创建一个文档。测试者将跟随总队2和3的方向,并且记录纵队5,6,7的结果。
表 5:最终的测试用例
步骤号 | 变量或选择 | 值 | 预期结果 | 实际结果 | 通过/失败 | 注解 |
---|---|---|---|---|---|---|
B1 | 网站 | www.amazon.com | 登陆界面 | |||
B2 | 电子邮件地址 | jsmith@hotmail.com | ||||
B2 | 密码 | Johnsm | 主界面 | |||
B3 | 搜索字符串 | “Rational” | 书本列表 | |||
B4 | 书本选择 | 第一次选择 | 书本详情 | |||
B5 | 行为选择 | 添加到购物车中 | 购物车的内容 | |||
B6 | 行为选择 | 进行结帐 | 地址提示 | |||
B7 | 送货地址 | 确认地址信息 | 送货提示 | |||
B8 | 购物方式 | 5 天 | 支付提示 | |||
B9 | 支付方法 | 确认信用卡信息 | 确认提示 | |||
B10 | 行为选择 | 下订单 | 订单号 |
RequisitePro再一次帮助你创建追踪关系。在产生了全部的测试用例以后,你可以设置从场景到测试用例的追踪。
图 12显示了全部的场景:从不同的可选流程合并中产生21个场景。
图12 :追踪矩阵
![matrix](https://i-blog.csdnimg.cn/blog_migrate/b0d7b1249960f7c96a311dedc91fa0f5.png)
在设置了场景和测试用例之间的追踪关系后,我们可以创建一个显示从用例到测试用例全部追踪方法的追踪树。
这里有两个选项。第一个选项——如图 13显示——用于追踪到用例外,用来显示上层的用例并且追踪场景和测试用例。
图 13:用例的追踪树
![Traceability tree](https://i-blog.csdnimg.cn/blog_migrate/7e3bca90e05e6454d8a1db234167b113.png)
第二个方法是测试用例中的追踪,如图14所示。在此种情况下,追踪树看起来会有不同:你从测试用例开始,然后追踪场景和用例。
图 14:测试用例的追踪树
![Traceability tree](https://i-blog.csdnimg.cn/blog_migrate/f5eee67d5a83555264753f941b018f4f.png)
进行这种追踪的一个最主要的原因--并且花费时间将其加入到RequisitePro中--是为了让你知道当一些事情发生变更时需要重新测试什么。追踪和所谓的可疑关联,如图 15所示,为你显示了由于先前的场景和用例发生了变化,哪些测试用例也要随之改变。
图 15:可疑关联
![Suspect relationships](https://i-blog.csdnimg.cn/blog_migrate/67acc6ad138727a150309c98e0d48990.png)
如何映射到IBM Rational统一过程(RUP)呢?它们大多数发生在过程中的先启和精化阶段。只要你有了用例之后,我们就可以开始建立场景和测试用例。图 16描述了这些活动对应于RUP方法论中的哪些地方。
图 16:映射到RUP阶段的追踪活动
![RUP phases](https://i-blog.csdnimg.cn/blog_migrate/bde772947439a6eca3b827fccf482739.png)
当建立场景和测试用例时,你可以给用例设计者反馈并且精炼一下需求。这有助于在过程中尽早改变一些任务,并且最终为团队尽快完成任务做出贡献。在整个精化阶段以及几乎是整个构造阶段中都会使用测试用例。
本文介绍了一种从用例中产生功能测试用例的方法。这是使用此方法的一些益处:
- 用更自动化的方法产生测试用例
- 避免重复测试
- 更好的测试覆盖
- 简化了测试进度的监控
- 简化了测试人员之间的工作负载平衡
- 简化了回归测试
- 通过把一些任务从构造阶段移到精化阶段来缩短项目时间
- 能够帮助尽早地发现缺失的需求
你创建的测试用例能够用于人工测试,也可以用于像IBM Rational Robot这样的自动化测试。这种方法已经在许多项目中获得了成功。
- 您可以参阅本文在 developerWorks 全球网站上的英文原文。
-
点击 这里 查看本文最初的RUC介绍。
-
参考文献:
- Jim Heumann, "From Use Cases to Test Cases - Ensuring Quality from the Beginning." RUC 2001.
- Jim Heumann, "Using Use Cases to Create Test Cases." The Rational Edge, June 2001.
- Dean Leffingwell and Don Widrig, "Managing Software Requirements: A Unified Approach". Addison-Wesley, 1999.
- Dean Leffingwell and Don Widrig, "The Role of Requirements Traceability in System Development", The Rational Edge, September 2002.
- Rational Unified Process. Rational Software Corporation, 2001.
![]() | ||
![]() | 技术方案经理,A Consulting Team, Inc. |
原文:http://www.ibm.com/developerworks/cn/rational/04/r-3217/