敏捷开发和测试中重现缺陷和验证缺陷的解决方案,第 3 部分: 验证缺陷

在作为系列的最后一篇覆盖的部分是缺陷生命周期的最后一个环节,缺陷的验证。本文主要描述了如何通过 Rational Team Concert(RTC)、Rational Quality Manager(RQM)及 IBM Workload Deployer(IWD)实现缺陷验证的自动化,而且笔者通过一个 RTC web 插件来展现自动化页面。

章 岩, 高级软件工程师, IBM

章岩,IBM Rational ClearQuest 测试工程师。有 7 年软件测试经验,主要关注软件测试自动化,测试服务和云计算等领域。



吴 江丽, 软件工程师, IBM

吴江丽,IBM Rational ClearQuest 开发工程师,有着丰富的开发经验,对云计算领域有着浓厚的兴趣。



王 建秋, 高级软件工程师, IBM

王建秋王建秋,IBM Rational ClearQuest 测试工程师,一直从事 ClearCase和ClearQuest产品的系统测试,特别专注的性能测试方面。



2013 年 3 月 29 日

免费下载:IBM® Rational® Team Concert 试用版 | IBM® Rational® Quality Manager 试用版
下载更多的 IBM 软件试用版,并加入 IBM 软件下载与技术交流群组,参与在线交流。

背景

系列前两篇中我们描述了如何用 IBM 产品帮助开发人员快速重现缺陷问题,下面本文主要描述一下我们是如何使用 IBM 产品加速缺陷验证过程的。

在缺陷验证的过程中,测试人员需要完成一下几个任务:

  • 定位可验证的缺陷 (Verifiable Defect)
    • 可验证的缺陷是指缺陷对应的产品代码变动已经被开发人员提交并包含在产品最新版本中。
    • 定位的范围一般包括
      • 测试人员自己提交的缺陷(Created by)
      • 由其他人员提交的,需要测试人员关注的缺陷(Subscribed by)
    • 测试人员一般需要针对每个关注的缺陷查看是否相应的代码变更已经包括在最新产品版本中。
  • 快速部署缺陷验证的测试环境
    • 环境准备一直是比较耗时的部分,不过这里我们可以借助在第一篇中我们创建的重现缺陷用的 IWD 中的虚拟系统模式 (Virtual System Pattern)。
  • 调用对应的测试用例执行
    • 测试人员需要找到对应的测试用例,在最新产品版本中重现执行。
  • 更新缺陷状态和信息
    • 根据最新的测试执行结果,可以判断缺陷是否还存在,从而更新缺陷状态和信息记录。

本文主要解决的问题

  • 如何快速定位可验证的缺陷
  • 把缺陷验证环节自动化,自动化内容包括:
    • 准备测试环境
    • 执行测试用例
    • 更新缺陷状态和信息
  • 更好的用户体验
    • 现在每个团队都会在不同程度上通过自动化工具来解决上述问题。由于上述工作涉及存储不同信息的软件系统,测试人员就需要登陆不同的系统采取相应的操作,例如:
      • 缺陷管理系统:开发和测试人员在这里可以更改缺陷的内容和状态。
      • 产品 build 管理系统:每个产品在正式发布前,内部的研发解决都会采用某个软件或系统对产品代码进行管理并且对内部发布产品非正式版本 (Build) 用于开发和测试。产品 Build 管理系统会记录每个 Build 所包含的代码变动,也会同时记录这一代码变动是由哪个缺陷引起的。
      • IT 环境管理系统:这一系统通常会为开发测试人员提供可用的环境。在某些团队中会通过不同的软件来调用 IT 环境管理系统来实现不同程度的自动化环境部署。
      • 测试管理系统:存储测试用例,测试结果和测试计划等内容。
    • 所以开发和测试人员在完成缺陷验证工作的过程中,需要登录各种不同的软件系统,而没有统一入口的使用方式会带来不好的用户体验。

实现框架

图 1. 实现框架
图 1. 实现框架

框架中有四个主要的部分:

  1. 搜索:这个部分负责跟缺陷管理系统和产品 Build 管理系统交互获得跟用户有关的可验证缺陷信息、对应的产品 Build 信息和测试用例的信息。
  2. 部署环境:负责接收搜索部分提供的结果,准备测试环境。框架中给出了两个部署环境的方案,第一个是创建一个新的测试环境;第二个是查找跟这个用户有关的已经存在的测试环境,并把产品的最新 Build 部署到这个环境中。这里第二个方案也是为了能够更好地重用测试资源,以免造成浪费。
  3. 测试执行:根据搜索部分提供测试用例,调用对应的自动化测试用例脚本进行验证。
  4. 状态更新:根据测试执行的结果自动更新缺陷的状态和内容。

框架中提到的不同软件系统,每个团队可以针对自己的实例完成实现。

实现部分 1:搜索可验证的缺陷

在 RTC 中我们的实现是开始于 RTC 中的一个缺陷查询 (Query),用户可以自己定义一个缺陷查询包括那些状态时 Resolved 的缺陷。

图 2. RTC 中的缺陷查询
图 2. RTC 中的缺陷查询

清单 1. 引用 RTC 中用户自定义的缺陷查询

搜索条件包括三个方面,

  • 产品– 由于 RTC 同一个项目中可能会涉及多个产品的缺陷记录,而一个用户又有可能会对多个产品的缺陷负责,所以产品作为第一个筛选条件。
  • 产品版本– 由于 RTC 同一个项目中可能会涉及同一个产品的不同版本的开发以及缺陷记录,而一个用户又有可能会工作于不同的产品版本。
  • 环境 -- 在 RTC 中可以为不同的项目缺陷记录定义不同的字段,这里可以定义环境的信息。( 如图 3)
图 3. RTC 缺陷中的环境字段
图 3. RTC 缺陷中的环境字段

根据用户自定义的缺陷查询和选定的三个筛选条件,我们可以完成第一次筛选,得到待验证的缺陷列表。下面就需要去版本控制系统中查找当前产品版本中包含哪些待验证的缺陷。

图 4. 筛选可验证的缺陷
图 4. 筛选可验证的缺陷

由于本文作者没有直接使用 RTC 作为版本控制软件,而是采用其他软件进行版本控制,所以这里没有具体介绍图 4 中代码的实现。

实现部分 2:缺陷验证环境的部署

由于我们在系列第二篇中曾经提到对 RTC 中存储的缺陷添加"IWD Pattern"字段,用于存储虚拟系统模式的名字,这里可以直接通过 RTC RESTAPI 获得虚拟系统模式名称,然后通过 IWD 的命令行工具 (Command Line Tool) 或者 RESTAPI 在对应的 IWD 服务器上创建缺陷验证环境。

IWD 提供的命令行工具可以直接从 IWD 的登陆界面上下载:

图 5. IWD 登陆界面中命令行工具下载页面
图 5. IWD 登陆界面中命令行工具下载页面
图 6. IWD 命令行工具本地文件结构
图 6. IWD 命令行工具本地文件结构

IWD 命令行工具是通过 python 脚本调用 IWD RESTAPI 实现具体功能的,工具中的 readme 文件具体描述了如何调用 python 脚本,或者读者也可以从参考资源中提供的链接学习如何调用。以下是两个 python 脚本,在部署缺陷环境前先查找缺陷中记录的虚拟系统模式是否包含在这个 IWD 服务器中,如果在则继续部署这个虚拟系统模式为一个虚拟机环境。

清单 2. 查看 IWD 服务器上所有虚拟系统模式信息
#
# For each pattern returned, the name of the pattern is presented
#
import ConfigParser
import threading, time, csv, random

# get all the vsystems associated with a pattern
# this emulates the user clicking on the "Systems" selection in the tree view
def get_systems(pattern, fname, fhandle):
    nsystems = 0
    start = time.time()
    nSystems = len(pattern.virtualsystems)
    for cnt in range (nSystems):
        try:
            system = pattern.virtualsystems[cnt]
        except:
            break        
    finish = time.time()
    fname.writerow([time.strftime("%m/%d/%Y"), time.strftime("%H:%M:%S"),'system',
                     nSystems, (finish-start)])
    fhandle.flush()
    
# get all the patterns
# this emulates the user clicking on the "Patterns" selection in the tree view
def get_patterns(fname, fhandle):
    start = time.time()
    patterns = deployer.patterns
    finish = time.time()
    fname.writerow([time.strftime("%m/%d/%Y"), time.strftime("%H:%M:%S"),'pattern', 
                   len(patterns), (finish-start)])
    fhandle.flush()
    
    for pattern in patterns:
        if (len(pattern.virtualsystems) > 0):
            time.sleep(random.randint(1, 10))            
            get_systems(pattern, fname, fhandle)

######
config = ConfigParser.RawConfigParser()
config.read('listPatterns.cfg')

try:
    output = '%s_%s.csv' % (config.get('Main', 'outfile'),time.strftime("%Y%m%d@%H%M%S"))
    interval = config.getint('Main', 'interval')
    duration = config.getint('Main', 'duration')
except ConfigParser.Error:
    print "Error reading config file"
    sys.exit

fhandle = open(output, 'w')
fname = csv.writer(fhandle, delimiter=',')
fname.writerow(['date', 'time', 'type', 'number','duration'])
fhandle.flush()

print 'Running ...'
print 'Output File: %s\tDuration: %d\tInterval: %d' % (output, duration, interval)

end_time = time.time()+(60*duration)
while (end_time > time.time()):
    start = time.time()
    time.sleep(random.randint(1, 10))
    get_patterns(fname, fhandle)
    
    # only sleep the remainder of the interval
    time.sleep((time.time()+interval) - start)
    
print 'Completed on %s at %s' % (time.strftime("%m/%d/%Y"), time.strftime("%H:%M:%S"))
fhandle.close()
清单 3. 部署制定的虚拟系统模式
import time, threading, Queue, csv
   
# deploy the images for performance throughput
class waitfor(threading.Thread):
    def __init__(self, system, log_q):
        self.system = system
        self.log_q = log_q
        threading.Thread.__init__(self)
            
    def run(self):
        start = time.clock()
        self.system.waitFor()
        finish = time.clock()           
        self.log_q.put_nowait([time.strftime("%m/%d/%Y"), time.strftime("%H:%M:%S"),
                              self.system.name,self.system.currentstatus,(finish-start)])
    
def run_test(instances, fname):
    log_q = Queue.Queue()
    threads = []
    for instance in range (int(instances)):
        virtualsystem = None
        sysName = '%s-%s-%s-%d' % (pfx, time.strftime("%Y%m%d"),time.strftime("%H%M5%S"),
                                   instance+1)
        print 'deploying virtual system: %s' % sysName
        
        createParms['name'] = sysName
        virtualsystem = deployer.virtualsystems << createParms
        sys = waitfor(virtualsystem, log_q)
        sys.start()
        threads.append(sys)
    
    #wait until all are done
    for thd in threads:
        thd.join()
    
    # write out the results
    fname = csv.writer(open(fname, 'w'))
    fname.writerow(['date', 'time', 'system_name','status','duration'])
    for xx in range(log_q.qsize()):
        data = log_q.get()
        fname.writerow(data)

#################################################################
# select pattern to deploy
instances = raw_input('number of instances to create: ')
pfx = raw_input('prefix for created systems: ')
output = raw_input('results file (** will be overwritten **): ')
createParms = {}

# default parameters
createParms['*.script-4.CHEF_NODE'] = 'devops_default'
createParms['*.script-4.DEPS_FILE_URL'] = '/tmp/devops_install/media/Deps-devops_services'
createParms['*.script-4.CHEF_NODE_ATTR'] = 
    '\"devops_server\"\:
    {\"app_source\"\:\"file:///tmp/devops_install/media/devops_services_app.zip\"}, 
    \"ram\"\:{\"server_url\"\:
    \"http://devops.rtp.raleigh.ibm.com/downloads/RAM-Server-7.5.1.1-Linux64.zip\",
    \"persist_url\"
    \:\"http://devops.rtp.raleigh.ibm.com/downloads/RAM-Data-7.5.1.1.tar.gz\",
    /\"database_url\"\:
    \"http://devops.rtp.raleigh.ibm.com/downloads/RAM-Database-7.5.1.1.tar.gz\"},
    \"db2\"\:{
    \"host_server_url\"\:\"http://devops.rtp.raleigh.ibm.com/downloads\"},
    \"jruby\"\:{\"download_url\"\:\"http://devops.rtp.raleigh.ibm.com/downloads/\"}'
createParms['cloud'] = deployer.clouds[0]

# select the performance pattern to deploy
pattern = None
while not pattern:
    i = 1
    for p in deployer.patterns:
        print '%d. %s' % (i, p.name)
        i = i + 1
    x = raw_input('select the test pattern to deploy: ')
    try:
        pattern = deployer.patterns[int(x) - 1]
    except:
        # try again
        pass
createParms['pattern'] = pattern

#start deploying
started = time.strftime("%m/%d/%Y at %H:%M:%S")
run_test(instances, output)    
completed = time.strftime("%m/%d/%Y at %H:%M:%S")

print '#############   test summary  #######################'
print 'Started on: %s\tCompleted on: %s' % (started, completed)
print 'Images deployed: %s' % instances

实现部分 3:测试执行

测试执行的时候我们需要调用 RQM 的命令行执行工具 (Command Line Execution Tool),这个工具是跟着 RQM 同时发布在 Jazz.net 上的,具体下载请看参考资源中提供的链接。

RQM 命令行执行工具是我们可以方便的执行测试,通过 RQM RESTAPI 启动测试执行记录 (Test Execution Record) 的运行。在运行测试执行时需要提供以下信息:

  • RQM url – https://hostname:9443/qm
  • 用户名和密码 -- 使用这个用户名允许运行相应的测试执行记录
  • 项目– 测试用例所在的 RQM 中的测试项目
  • 测试执行记录的 id
  • 测试执行记录关联的脚本的 id
  • 执行适配器 (Adapter) 的 id
    • 执行适配器是 RQM 提供的用以连接并操作具体测试脚本的功能,常用的适配器包括 Rational Functional Tester、Rational Performance Tester、Rational Build Forge 以及其他更多第三方厂商和开源工具的适配器。用户也可以自己编写适配器。
  • 执行时需要的参数

通过命令行调用后执行结果是会输出到命令行的,也可以直接输出到文件,用于后续操作。

实现部分 4:自动化中注意事项

要把整个流程自动化需要用到流程工具,可以用开源的 ant 或者使用 IBM Rational Build Forge。

这里主要介绍几个重点:

  1. 测试用例相关信息获得
    1. 缺陷可以通过 RTC 与 RQM 之间的 OSLC 关联关系连接 RQM 中的测试执行结果 (Test Execution Result),测试执行结果是测试执行记录 (Test Execution Record) 的执行结果。
    2. 然后我们可以从缺陷中获得测试执行结果的 url,经过分析我们可以获得 RQM 项目名称,测试执行结果 id 等信息,用于调用 RQM 命令行执行工具。
  2. 缺陷验证环境信息的传递
    1. 缺陷验证环境是我们通过部署虚拟系统模式生成的新的虚拟机,所以 ip、hostname、用户名和密码都是新的。而在验证系列第二篇中提到的 WAS 卖花网站中遇到的缺陷,我们需要传送新的 url 地址给 RFT 脚本。
    2. 在调用 RQM 命令行执行工具的时候就需要传输参数
清单 4. RQM 命令行调用附参数
c:\IBM\java60\bin\java -jar RQMExecutionTool.jar -tcerId=1 -projectName=QM1 
                       -publicURI=https://paul801beta:9443/qm -user=paul 
                       -password=passw0rd -exitOnComplete=true 
                       -variables=host:clmsvr-sjy.cn.ibm.com
  1. RFT 中接受参数的脚本如下
清单 5. RFT 脚本
import com.rational.test.ft.script.IParameter;
import com.rational.test.ft.script.IVariablesManager;
public class SampleScript extends SampleScriptHelper
{
    /**
     * Script Name   : <b>SampleScript</b>
     * Generated     : <b>Dec 10, 2012 1:44:05 PM</b>
     * Description   : Functional Test Script
     * Original Host : WinNT Version 5.1  Build 2600 (S)
     *
     * @since  2012/12/10
     * @author Administrator
     */
    public void testMain(Object[] args)
    {
        //接收参数的定义
        IVariablesManager manager = getVariablesManager();
        IParameter host = manager.getInputParameter("host");
        
        
        startApp("http://" + host.getValue() + ":9081/PlantsByWebSphere/");
        
        // HTML Browser
        // Document: Plants by WebSphere: 
        // http://clmsvr-sjy.cn.ibm.com:9081/PlantsByWebSphere/
        // Document: http://clmsvr-sjy.cn.ibm.com:9081/PlantsByWebSphere/promo.html
        image_bonsaiTree().click();
        // Document: 
        // http://clmsvr-sjy.cn.ibm.com:9081/PlantsByWebSphere/servlet/ShoppingServlet?
        //      action=productdetail&itemID=T0003
        browser_htmlBrowser(document_plantsByWebSphere(),DEFAULT_FLAGS).inputChars("abc");
        button_addToCart().click();
    }
}

这里提示一下 RQM 命令行执行工具也提供了 ant 任务调用,方便把执行测试这部分集成到自动化的流程中。

实现部分 5:Jazz 插件的开发

Jazz 插件的开发与 Eclipse 插件开发模式是一致的,Jazz 平台定义了丰富而功能强大的扩展点,用户可以利用这些扩展点,定义和实现各种定制功能。

首先要搭建扩展开发环境,Jazz.net 网站提供了详细的下载 SDK 链接,以及参考文档。

RTC Server 端服务的扩展,有关 Jazz Component 开发详细资料请查看参考资源中提供的链接。Jazz Component 开发扩展扩展点"com.ibm.team.repository.common.components",定义服务类接口,类型是 Raw_HTTP。

清单 6
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
   <!--
      This extension defines our component to Jazz. Note that the
      common plugin is included on both the client and server, so
      the component is known both places.
   -->
   <extension
         point="com.ibm.team.repository.common.components">
      <component
            id="com.ibm.rational.svt.workitem.extensions"
            name="Workitem Validation Workflow">
         <service
               kind="RAW_HTTP"
               name="Workitem Validation WorkFlow Rest Service" 
               uri="com.ibm.rational.svt.workitem.extensions.common.
                    IWorkitemValidationWorkflowRestService"
               version="1">
         </service>
      </component>
   </extension>
</plugin>

通过扩展扩展点 com.ibm.team.repository.service.serviceProvider 创建一个 RTC Server 端服务具体实现,用来处理客户的具体 HTTP 请求

清单 7
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
   <extension
         point="com.ibm.team.repository.service.serviceProvider">
      <serviceProvider
            componentId="com.ibm.rational.svt.workitem.extensions" 
                      implementationClass="com.ibm.rational.svt.workitem.extensions.
                      service.DefectValidationProcessRestService">
         <provides>
            <providedService
            interface="com.ibm.rational.svt.workitem.extensions.common.
            IWorkitemValidationWorkflowRestService">
            </providedService>
         </provides>
         <prerequisites>
            <requiredService
                  interface="com.ibm.team.repository.common.service.IQueryService">
            </requiredService>
            <requiredService
                  interface="com.ibm.team.repository.service.IRepositoryItemService">
            </requiredService>
         </prerequisites>
      </serviceProvider>
   </extension>
</plugin>

通过创建一个 Web Viewlet 作为整个解决方案的 Web GUI 展示平台。Jazz 平台提供了专门的扩展点,用来扩展用户自定义的 Viewlet。通过这个 Viewlet,我们可以将后台定义的缺陷查询,及在不同的过滤条件下的结果,很好的展示出来;同时,这个 Viewlet 也实现了 IWD 环境自动部署的逻辑展现。Viewlets 实现的扩展 com.ibm.team.dashboard.common.viewlets。

清单 8
<?xml version="1.0" encoding="UTF-8"?>

<?eclipse version="3.2"?>
<plugin>

    <!-- Web bundle marker -->
    <extension point="net.jazz.ajax.webBundles"/>
    
    <!-- Viewlets and categories -->
    <extension
             point="com.ibm.team.dashboard.common.viewlets">
        <category
            id="com.ibm.helloworld.category"
            name="Testing App Store">
        </category>
        <viewlet
            allow-remote="true"
            applies-to="projects"
            closable="true"
            collapsable="true"
            dynamic-title="true"
            editable="true"
            filterable="true"
      icon="resources/ui/internal/graphics/helloworld/icons/processcustomization_obj.gif"
            id="com.ibm.helloworld.viewlet.queryresults"
            include-subteams="no"
            refresh-interval="600"
            scope-sensitive="false"
            title="%viewlet.workItems"
            title-as-hyperlink="true"
            version="2"
      widget="com.ibm.helloworld.viewlet.web.ui.internal.WorkItemsViewlet"
            zoomable="true">
                       
    ....    ..
</plugin>

其中 com.ibm.helloworld.viewlet.web.ui.internal.WorkItemsViewlet 定义了 viewlet 的具体实现。实现效果如下图:

图 7. viewlet 的具体实现
图 7. viewlet 的具体实现

参考资料

学习

获得产品和技术

  • 获取免费的 Rational 软件工具包系列,了解最新的 IBM Rational 软件开发工具技术文档和资源。
  • 下载更多免费的 IBM Rational 试用版软件,了解 IBM Rational 软件的最新特性。
  • 获取更多 IBM 试用版软件,并熟练掌握来自 DB2®、Lotus®、Tivoli®,以及 WebSphere® 的开发工具和中间件产品,用这些试用版软件开发您的下一个项目。这些试用版软件可以免费直接从 developerWorks 下载。

讨论

  • 加入 developerWorks 中文社区,developerWorks 社区是一个面向全球 IT 专业人员,可以提供博客、书签、wiki、群组、联系、共享和协作等社区功能的专业社交网络社区。
  • 访问 developerWorks 社区上的 Jazz 技术小组,这里汇集了丰富的 Jazz 平台中文技术资源。 您可以通过这里了解更多关于 Jazz 平台和 Jazz 技术发展趋势的最新信息。
  • 访问 developerWorks 社区上的 敏捷开发小组,在那里您将有机会与更多的开发人员一起交流敏捷开发最佳实践。
  • 加入 IBM 软件下载与技术交流群组,参与在线交流。

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Rational
ArticleID=863315
ArticleTitle=敏捷开发和测试中重现缺陷和验证缺陷的解决方案,第 3 部分: 验证缺陷
publish-date=03292013