0

之前另一个用户提出了这个问题:

使用 Rally Python 或 Ruby API 复制测试用例和测试文件夹 [关闭]

但被主持人关闭为一个过于广泛的问题。但是,鉴于无法在Rally UI中复制测试文件夹及其成员测试用例,这是 Rally 用户的常见需求。

因此 - 我将重新提出这个问题,希望有足够的细节来作为一个有效的问题。我还将重新发布我为原始问题开发的答案。

问题:作为 Rally Python 和 Ruby REST API 的Rally用户和开发人员:我如何利用Rally API 工具包来完成这项任务?

4

3 回答 3

2

Python:

这是执行此任务的脚本 - 它将所有测试用例从由 FormattedID 标识的源测试文件夹复制到同样由 FormattedID 标识的目标测试文件夹。它还将复制所有测试步骤和附件。目标测试文件夹必须存在,即如果找不到目标,脚本不会为您创建测试文件夹。

该脚本不会将新测试用例与原始测试用例的工作产品(即缺陷、用户故事)相关联,或复制讨论项、测试用例结果或上次构建、判断等,因为假定需要新测试用例处于“空白”状态。

对于那些需要安装和配置 Rally Python REST 库的人:

Rally 开发者门户:Pyral 下载和安装

Pyral 文档

#!/usr/bin/env python

#################################################################################################
#
#  copy_test_folder.py -- Copy all Test Cases in Source Test Folder to Target. Includes Test Steps
#                         and attachments. Target Test Folder must exist (i.e. the script will not
#                         create a new targeet Test Folder for you)
#
USAGE = """
Usage: copy_test_folder.py
"""
#################################################################################################
# import needed python libs
import sys, os
import re
import string
import base64

from pprint import pprint

# import needed pyral libs
from pyral import Rally, rallySettings, RallyRESTAPIError

errout = sys.stderr.write

my_server      = "rally1.rallydev.com"
my_user        = "user@company.com"
my_password    = "topsecret"
my_workspace   = "My Workspace"
my_project     = "My Project"

source_test_folder_formatted_id = "TF1"
target_test_folder_formatted_id = "TF4"

rally = Rally(my_server, my_user, my_password, workspace=my_workspace, project=my_project)
# rally = Rally(my_server, my_user, my_password, workspace=my_workspace, project=my_project, debug=True)
rally.enableLogging('copy_test_folder.log')

# Query for source and target test folders
source_test_folder_response = rally.get('TestFolder', fetch=True, query='FormattedID = %s' % source_test_folder_formatted_id)
target_test_folder_response = rally.get('TestFolder', fetch=True, query='FormattedID = %s' % target_test_folder_formatted_id)

# Check to make sure folders exist
if source_test_folder_response.resultCount == 0:
                 errout('No Source Test Folder Found matching Formatted ID: %s\n' % (source_test_folder_formatted_id))
                 sys.exit(4)

if target_test_folder_response.resultCount == 0:
                 errout('No Target Test Folder Found matching Formatted ID: %s\n. Target Test Folder must be created before copying.' % (target_test_folder_formatted_id))
                 sys.exit(4)

# Get references to source Test Folder and Test Cases, etc.                 
source_test_folder = source_test_folder_response.next()
source_test_cases = source_test_folder.TestCases

# Get reference to target Test Folder
target_test_folder = target_test_folder_response.next()

for source_test_case in source_test_cases:
                 # Create update fields for target Test Case
                 # Does NOT associate new Test Case to original Test Case's work product (i.e. Defect, User Story)
                 # Does NOT copy Discussion items - as the old Discussions are likely not desired on new Test Case
                 # Does NOT copy Last Build, Last Run, Last Update Date, Last Verdict as new Test Case will effectively
                 # be "blank" and not have any results associated to it
                 if source_test_case.Owner != None:
                                  target_owner = source_test_case.Owner.ref
                 else:
                                  target_owner = None
                 target_test_case_fields = {
                                  "Package": source_test_case.Package,
                                  "Description": source_test_case.Description,
                                  "Method": source_test_case.Method,
                                  "Name": source_test_case.Name,
                                  "Objective": source_test_case.Objective,
                                  "Owner": target_owner,
                                  "PostConditions": source_test_case.PostConditions,
                                  "PreConditions": source_test_case.PreConditions,
                                  "Priority": source_test_case.Priority,
                                  "Project": source_test_case.Project.ref,
                                  "Risk": source_test_case.Risk,
                                  "ValidationInput": source_test_case.ValidationInput,
                                  "ValidationExpectedResult": source_test_case.ValidationExpectedResult,
                                  "TestFolder": target_test_folder.ref,
                 }

                 # Create the target test case
                 try:
                                  target_test_case = rally.create("TestCase", target_test_case_fields)
                                  message = "Copied Source Test Case: " + source_test_case.FormattedID + \
                                            " To: " + target_test_folder.FormattedID + ": " + target_test_folder.Name + \
                                            ": " + target_test_case.FormattedID
                                  print message

                 except RallyRESTAPIError, details:
                                  sys.stderr.write('ERROR: %s \n' % details)
                                  sys.exit(2)


                 # Copy Test Steps
                 # Add Test Case Steps
                 source_test_case_steps = source_test_case.Steps
                 for source_step in source_test_case_steps:
                                  target_step_fields = {
                                                  "TestCase"          : target_test_case.ref,
                                                  "StepIndex"         : source_step.StepIndex,
                                                  "Input"             : source_step.Input,
                                                  "ExpectedResult"    : source_step.ExpectedResult                                                   
                                  }
                                  target_test_case_step = rally.put('TestCaseStep', target_step_fields)
                                  print "===> Copied  TestCaseStep: %s   OID: %s" % (target_test_case_step.StepIndex, target_test_case_step.oid)                 

                 # Copy Attachments
                 source_attachments = rally.getAttachments(source_test_case)

                 for source_attachment in source_attachments:

                                  # First copy the content
                                  source_attachment_content = source_attachment.Content
                                  target_attachment_content_fields = {
                                                   "Content": base64.encodestring(source_attachment_content)
                                  }

                                  try:
                                                   target_attachment_content = rally.put('AttachmentContent', target_attachment_content_fields)
                                                   print "===> Copied AttachmentContent: %s" % target_attachment_content.ref
                                  except RallyRESTAPIError, details:
                                                   sys.stderr.write('ERROR: %s \n' % details)
                                                   sys.exit(2)

                                  # Next copy the attachment object
                                  target_attachment_fields = {
                                                   "Name": source_attachment.Name,
                                                   "Description": source_attachment.Description,
                                                   "Content": target_attachment_content.ref,
                                                   "ContentType": source_attachment.ContentType,
                                                   "Size": source_attachment.Size,
                                                   "Artifact": target_test_case.ref,
                                                   "User": source_attachment.User.ref
                                  }
                                  try:
                                                   target_attachment = rally.put('Attachment', target_attachment_fields)
                                                   print "===> Copied Attachment: %s" % target_attachment.ref
                                  except RallyRESTAPIError, details:
                                                   sys.stderr.write('ERROR: %s \n' % details)
                                                   sys.exit(2)


                 # Copy Tags
                 source_tags = source_test_case.Tags;
                 target_tags = list()
                 for source_tag in source_tags:
                                  target_tags.append({"_ref":source_tag.ref})


                 target_test_case_fields = {
                                  "FormattedID": target_test_case.FormattedID,
                                  "Tags": target_tags
                 }

                 try:
                                  update_response = rally.update('TestCase', target_test_case_fields)
                 except RallyRESTAPIError, details:
                                  sys.stderr.write('ERROR: %s \n' % details)
                                  sys.exit(2)
于 2012-11-04T22:29:32.120 回答
0

红宝石:

此 Ruby 脚本会将所有测试用例从由 FormattedID 标识的源测试文件夹复制到同样由 FormattedID 标识的目标测试文件夹。它还将复制所有测试步骤和附件。目标测试文件夹必须存在,即如果找不到目标,脚本不会为您创建测试文件夹。

该脚本不会将新测试用例与原始测试用例的工作产品(即缺陷、用户故事)相关联,或复制讨论项、测试用例结果或上次构建、判断等,因为假定需要新测试用例处于“空白”状态。

对于那些需要安装和配置 Ruby REST 工具包的人,链接在这里:

开发人员门户:Ruby 的 Rally REST API

Github

    # Copyright 2002-2012 Rally Software Development Corp. All Rights Reserved.

    require 'rally_api'

    $my_base_url       = "https://rally1.rallydev.com/slm"
    $my_username       = "user@company.com"
    $my_password       = "password"
    $my_workspace      = "My Workspace"
    $my_project        = "My Project"
    $wsapi_version     = "1.37"

    # Test Folders
    $source_test_folder_formatted_id = "TF4"
    $target_test_folder_formatted_id = "TF8"

    # Load (and maybe override with) my personal/private variables from a file...
    my_vars= File.dirname(__FILE__) + "/my_vars.rb"
    if FileTest.exist?( my_vars ) then require my_vars end

    #==================== Make a connection to Rally ====================
    config                  = {:base_url => $my_base_url}
    config[:username]       = $my_username
    config[:password]       = $my_password
    config[:workspace]      = $my_workspace
    config[:project]        = $my_project
    config[:version]        = $wsapi_version

    @rally = RallyAPI::RallyRestJson.new(config)

    begin

      # Lookup source Test Folder
      source_test_folder_query = RallyAPI::RallyQuery.new()
      source_test_folder_query.type = :testfolder
      source_test_folder_query.fetch = true
      source_test_folder_query.query_string = "(FormattedID = \"" + $source_test_folder_formatted_id + "\")"

      source_test_folder_result = @rally.find(source_test_folder_query)

      # Lookup Target Test Folder
      target_test_folder_query = RallyAPI::RallyQuery.new()
      target_test_folder_query.type = :testfolder
      target_test_folder_query.fetch = true
      target_test_folder_query.query_string = "(FormattedID = \"" + $target_test_folder_formatted_id + "\")"

      target_test_folder_result = @rally.find(target_test_folder_query)

      if source_test_folder_result.total_result_count == 0
        puts "Source Test Folder: " + $source_test_folder_formatted_id + "not found. Exiting."
        exit
      end

      if target_test_folder_result.total_result_count == 0
        puts "Target Test Folder: " + $target_test_folder_formatted_id + "not found. Target must exist before copying."
        exit
      end

      source_test_folder = source_test_folder_result.first()
      target_test_folder = target_test_folder_result.first()

      # Populate full object for Target Test Folder
      full_target_test_folder = target_test_folder.read

      # Get Target Project
      target_project = full_target_test_folder["Project"]

      # Grab collection of Source Test Cases
      source_test_cases = source_test_folder["TestCases"]

      # Loop through Source Test Cases and Copy to Target
      source_test_cases.each do |source_test_case|
        # Get full object for Source Test Case
        full_source_test_case = source_test_case.read

        # Check if there's an Owner
        if !full_source_test_case["Owner"].nil?
          source_owner = full_source_test_case["Owner"]
        else
          source_owner = nil
        end

        # Populate field data from Source to Target
        target_test_case_fields = {}
        target_test_case_fields["Package"] = full_source_test_case["Package"]
        target_test_case_fields["Description"] = full_source_test_case["Description"]
        target_test_case_fields["Method"] = full_source_test_case["Method"]
        target_test_case_fields["Name"] = full_source_test_case["Name"]
        target_test_case_fields["Objective"] = full_source_test_case["Objective"]
        target_test_case_fields["Owner"] = source_owner
        target_test_case_fields["PostConditions"] = full_source_test_case["PostConditions"]
        target_test_case_fields["PreConditions"] = full_source_test_case["PreConditions"]
        target_test_case_fields["Priority"] = full_source_test_case["Priority"]
        target_test_case_fields["Project"] = target_project
        target_test_case_fields["Risk"] = full_source_test_case["Risk"]
        target_test_case_fields["ValidationInput"] = full_source_test_case["ValidationInput"]
        target_test_case_fields["ValidationExpectedResult"] = full_source_test_case["ValidationExpectedResult"]
        target_test_case_fields["Tags"] = full_source_test_case["Tags"]
        target_test_case_fields["TestFolder"] = target_test_folder

        # Create the Target Test Case
        begin
          target_test_case = @rally.create(:testcase, target_test_case_fields)
          puts "Test Case: #{full_source_test_case["FormattedID"]} successfully copied to #{full_target_test_folder["FormattedID"]}"
        rescue => ex
          puts "Test Case: #{full_source_test_case["FormattedID"]} not copied due to error"
          puts ex
        end

        # Now Copy Test Steps
        # Add Test Case Steps
        source_test_case_steps = full_source_test_case["Steps"]

        source_test_case_steps.each do |source_test_case_step|
          full_source_step = source_test_case_step.read
          target_step_fields = {}
          target_step_fields["TestCase"] = target_test_case
          target_step_fields["StepIndex"] = full_source_step["StepIndex"]
          target_step_fields["Input"] = full_source_step["Input"]
          target_step_fields["ExpectedResult"] = full_source_step["ExpectedResult"]
          begin
            target_test_case_step = @rally.create(:testcasestep, target_step_fields)
            puts "===> Copied TestCaseStep: #{target_test_case_step["_ref"]}"
          rescue => ex
            puts "Test Case Step not copied due to error:"
            puts ex
          end
        end

          # Now Copy Attachments
          source_attachments = full_source_test_case["Attachments"]

        source_attachments.each do |source_attachment|
          full_source_attachment = source_attachment.read
          source_attachment_content = full_source_attachment["Content"]
          full_source_attachment_content = source_attachment_content.read

          # Create AttachmentContent Object for Target
          target_attachment_content_fields = {}
          target_attachment_content_fields["Content"] = full_source_attachment_content["Content"]
          begin
            target_attachment_content = @rally.create(:attachmentcontent, target_attachment_content_fields)
            puts "===> Copied AttachmentContent: #{target_attachment_content["_ref"]}"
          rescue => ex
            puts "AttachmentContent not copied due to error:"
            puts ex
          end

          # Now Create Attachment Container
          target_attachment_fields = {}
          target_attachment_fields["Name"] = full_source_attachment["Name"]
          target_attachment_fields["Description"] = full_source_attachment["Description"]
          target_attachment_fields["Content"] = target_attachment_content
          target_attachment_fields["ContentType"] = full_source_attachment["ContentType"]
          target_attachment_fields["Size"] = full_source_attachment["Size"]
          target_attachment_fields["Artifact"] = target_test_case
          target_attachment_fields["User"] = full_source_attachment["User"]
          begin
            target_attachment = @rally.create(:attachment, target_attachment_fields)
            puts "===> Copied Attachment: #{target_attachment["_ref"]}"
          rescue => ex
            puts "Attachment not copied due to error:"
            puts ex
          end
        end
      end
    end
于 2012-11-04T22:26:55.693 回答
0

相同 Python 脚本的改进,添加了以下功能

  • 测试文件夹(源和目标)在参数中传递,而不是硬编码
  • 创建是递归完成的(即在其中复制测试文件夹和测试用例)
  • 如果测试用例已经存在,则替换测试用例(基于名称)
  • 使用 API Key 连接而不是密码
  • 在放弃之前尝试 5 次连接到 Rally

要使用它复制TF10TF99(已经存在):python copy_test_folder TF10 TF99

#!/usr/bin/env python

#################################################################################################
#
#  copy_test_folder.py -- Copy all Test Cases in Source Test Folder to Target. Includes Test Steps
#                         and attachments. Target Test Folder must exist (i.e. the script will not
#                         create a new targeet Test Folder for you)
#
USAGE = """
Usage: copy_test_folder.py TF_src TF_dest
"""
#################################################################################################
# import needed python libs
import sys, os
import re
import string
import base64

from pprint import pprint

# import needed pyral libs
from pyral import Rally, rallySettings, RallyRESTAPIError

errout = sys.stderr.write

my_server      = "rally1.rallydev.com"
my_user        = "name@domain.com"
my_password    = "<PASSWORD>"
my_workspace   = "<WORKSPACE>"
my_project     = "<PROJECT>"
my_key         = "<API KEY>"


def connect(nb_attempts=5):
    '''Connect to Rally'''
    global is_connected
    global rally
    #Connect
    if not is_connected and nb_attempts>0:
        print "...Attempting to connect to Rally for project %s..." % my_project
        try:
            # rally = Rally(my_server, my_user, my_password, workspace=my_workspace, project=my_project)
            rally = Rally(my_server, apikey=my_key, workspace=my_workspace, project=my_project)
            print "Connected to Rally for project %s (%s)" % (my_project, my_workspace)
            is_connected=True
            rally.enableLogging('copy_test_folder.log')
        #Errors during connection (attempting to connect again)
        # except Exception, details: 
        except AttributeError: 
            if nb_attempts>1:
                connect(nb_attempts-1)
            else:
                errout('Error during connection to Rally (%s)\n' % details)
                exit(4)
    else: pass


def copyTF(src_TF, dest_TF):
    '''Copy Test Cases from one folder into another (including children Test Folders)'''
    # List names and FormattedID of existing Test Cases in destination
    dest_TCs = dest_TF.TestCases
    existing_dest_TC = {tc.Name:tc.FormattedID for tc in dest_TCs} 

    # Copy Test Cases to destination folder (replace if Test Case with same name exists)
    src_TCs = src_TF.TestCases
    for src_TC in src_TCs:
        # Create update fields for target Test Case
        # Does NOT associate new Test Case to original Test Case's WorkProduct (i.e. Defect, User Story)
        # Does NOT copy Discussion items - as the old Discussions are likely not desired on new Test Case
        # Does NOT copy Last Build, Last Run, Last Update Date, Last Verdict as new Test Case will effectively
        # be "blank" and not have any results associated to it
        tcName = src_TC.Name
        dest_TC_fields = {
            "Package": src_TC.Package,
            "Description": src_TC.Description,
            "Method": src_TC.Method,
            "Name": tcName,
            "Objective": src_TC.Objective,
            "Owner": getattr(src_TC.Owner, 'ref', None),
            "PostConditions": src_TC.PostConditions,
            "PreConditions": src_TC.PreConditions,
            "Priority": src_TC.Priority,
            "Project": src_TC.Project.ref,
            "Risk": src_TC.Risk,
            "ValidationInput": src_TC.ValidationInput,
            "ValidationExpectedResult": src_TC.ValidationExpectedResult,
            "TestFolder": dest_TF.ref,
         }

        # Create/Update the target test case
        try:
            if existing_dest_TC.has_key(tcName):
                operation = "Update"
                dest_TC_fields['FormattedID'] = existing_dest_TC[tcName]
                dest_TC = rally.update("TestCase", dest_TC_fields)
            else:
                #Create
                operation = "Create"
                dest_TC = rally.create("TestCase", dest_TC_fields)

            message = operation + "d Source Test Case: " + src_TC.FormattedID + \
                " To: " + dest_TF.FormattedID + ": " + dest_TF.Name + \
                ": " + dest_TC.FormattedID
            print message

        except RallyRESTAPIError, details:
            sys.stderr.write('ERROR: %s \n' % details)
            sys.exit(2)


        # Copy Test Steps
        #Cleared-up all Test Steps in destination if Test Case is updated
        if operation == "Update":
            dest_TC_steps = dest_TC.Steps
            for dest_step in dest_TC_steps:
                rally.delete('TestCaseStep', dest_step.oid)
            print "===> Cleared all Test Steps"

        # Add Test Case Steps
        src_TC_steps = src_TC.Steps
        for src_step in src_TC_steps:
            target_step_fields = {
                "TestCase"          : dest_TC.ref,
                "StepIndex"         : src_step.StepIndex,
                "Input"             : src_step.Input,
                "ExpectedResult"    : src_step.ExpectedResult                                                   
            }
            dest_TC_step = rally.put('TestCaseStep', target_step_fields)
            print "===> Copied  TestCaseStep: %s   OID: %s" % (dest_TC_step.StepIndex, dest_TC_step.oid)                 

        # Copy Attachments
        source_attachments = rally.getAttachments(src_TC)

        for source_attachment in source_attachments:
            # First copy the content
            source_attachment_content = source_attachment.Content
            target_attachment_content_fields = {
                "Content": base64.encodestring(source_attachment_content)
            }

            try:
                target_attachment_content = rally.put('AttachmentContent', target_attachment_content_fields)
                print "===> Copied AttachmentContent: %s" % target_attachment_content.ref
            except RallyRESTAPIError, details:
                sys.stderr.write('ERROR: %s \n' % details)
                sys.exit(2)

            # Next copy the attachment object
            target_attachment_fields = {
                "Name": source_attachment.Name,
                "Description": source_attachment.Description,
                "Content": target_attachment_content.ref,
                "ContentType": source_attachment.ContentType,
                "Size": source_attachment.Size,
                "Artifact": dest_TC.ref,
                "User": source_attachment.User.ref
            }
            try:
                target_attachment = rally.put('Attachment', target_attachment_fields)
                print "===> Copied Attachment: %s" % target_attachment.ref
            except RallyRESTAPIError, details:
                sys.stderr.write('ERROR: %s \n' % details)
                sys.exit(2)


        # Copy Tags
        source_tags = src_TC.Tags;
        target_tags = list()
        for source_tag in source_tags:
            target_tags.append({"_ref":source_tag.ref})

        dest_TC_fields = {
            "FormattedID": dest_TC.FormattedID,
            "Tags": target_tags
         }

        try:
            update_response = rally.update('TestCase', dest_TC_fields)
        except RallyRESTAPIError, details:
            sys.stderr.write('ERROR: %s \n' % details)
            sys.exit(2)

    # Recursive call for each child Test Folder (after creating it)
    src_children_TFs = src_TF.Children
    dest_children_TFs = dest_TF.Children
    existing_dest_TF = {tf.Name:tf for tf in dest_children_TFs} 
    for src_child_TF in src_children_TFs:
        tfName = src_child_TF.Name
        # Create Test Folder if needed
        if existing_dest_TF.has_key(tfName):
            dest_child_TF = existing_dest_TF[tfName]
        else:
            target_TF_fields = {
                'Name': tfName,
                'Parent': dest_TF.ref
            }
            dest_child_TF = rally.put('TestFolder', target_TF_fields)
            print "Created Test folder %s (%s)" % (tfName, dest_child_TF.FormattedID)
        # Copy Test Cases of this folder
        copyTF(src_child_TF, dest_child_TF)

if '__main__' in __name__:

    # Get source and destination test folders
    src_TF_formatted_id = sys.argv[1]
    dest_TF_formatted_id = sys.argv[2]

    # Connect to Rally
    global is_connected
    is_connected = False
    connect(5)

    # Query for source and target test folders
    global rally
    src_TF_response = rally.get('TestFolder', fetch=True, query='FormattedID = %s' % src_TF_formatted_id)
    dest_TF_response = rally.get('TestFolder', fetch=True, query='FormattedID = %s' % dest_TF_formatted_id)

    # Check to make sure folders exist
    if src_TF_response.resultCount == 0:
        errout('No Source Test Folder Found matching Formatted ID: %s\n' % (src_TF_formatted_id))
        sys.exit(4)

    if dest_TF_response.resultCount == 0:
        errout('No Target Test Folder Found matching Formatted ID: %s\n. Target Test Folder must be created before copying.' % (dest_TF_formatted_id))
        sys.exit(4)

    # Get Objects of source and target Test Folders                
    src_TF = src_TF_response.next()
    dest_TF = dest_TF_response.next()

    # Copy file
    copyTF(src_TF, dest_TF)
于 2017-02-22T10:44:46.127 回答