API-интерфейсы Rally: как скопировать тестовую папку и тестовые наборы участников

Этот вопрос ранее задавал другой пользователь:

Копирование тестовых случаев и тестовой папки с помощью Rally Python или Ruby API [закрыто]

но закрыт модераторами как слишком широкий вопрос. Однако, учитывая невозможность копирования тестовых папок и входящих в них тестовых наборов в интерфейсе Rally, это распространенная необходимость. для пользователей ралли.

Таким образом, я повторно задам вопрос, надеюсь, с достаточной детализацией, чтобы считаться действительным вопросом. Я также повторно опубликую ответы, которые я разработал для исходного вопроса.

Вопрос. Как пользователь и разработчик Rally Rally Python и Ruby REST API: как я могу использовать наборы инструментов Rally API для выполнения этой задачи?


person Community    schedule 04.11.2012    source источник


Ответы (3)


Питон:

Вот сценарий, который выполняет эту задачу: он копирует все тестовые наборы из исходной тестовой папки, указанной FormattedID, в целевую тестовую папку, также идентифицированную FormattedID. Также будут скопированы все этапы тестирования и вложения. Целевая тестовая папка должна существовать, т. е. скрипт не создаст для вас тестовую папку, если цель не найдена.

Сценарий не связывает новый тестовый пример с рабочим продуктом исходного тестового примера (т. е. дефектом, пользовательской историей) и не копирует элементы обсуждения, результаты тестового набора или последнюю сборку, вердикт и т. д., поскольку предполагается, что новый тестовый набор является желательным. находиться в «пустом» состоянии.

Для тех, кому необходимо установить и настроить REST-библиотеку Rally Python:

Портал разработчиков 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        = "[email protected]"
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)
person Community    schedule 04.11.2012

Рубин:

Этот сценарий Ruby скопирует все тестовые случаи из исходной тестовой папки, идентифицированной FormattedID, в целевую тестовую папку, также идентифицированную FormattedID. Он также скопирует все шаги теста и вложения. Целевая тестовая папка должна существовать, т. е. скрипт не создаст для вас тестовую папку, если цель не найдена.

Сценарий не связывает новый тестовый пример с рабочим продуктом исходного тестового примера (т. е. дефектом, пользовательской историей) и не копирует элементы обсуждения, результаты тестового примера или последнюю сборку, вердикт и т. д., поскольку предполагается, что новый тестовый пример желателен. находиться в «пустом» состоянии.

Для тех, кому необходимо установить и настроить Ruby REST Toolkit, ссылки находятся здесь:

Портал разработчика: API Rally REST для Ruby

Github

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

    require 'rally_api'

    $my_base_url       = "https://rally1.rallydev.com/slm"
    $my_username       = "[email protected]"
    $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
person Community    schedule 04.11.2012

Улучшение того же скрипта Python, которое добавляет следующие функции.

  • Тестовые папки (исходная и целевая) передаются в аргументах вместо жестко закодированных
  • Создание выполняется рекурсивно (т.е. копирование в него папок тестов и тестовых случаев)
  • Тестовые наборы заменяются, если они уже существуют (на основе имени)
  • Использование ключа API для подключения, а не пароля
  • Попробуйте 5 раз подключиться к Rally, прежде чем сдаться

Чтобы использовать его для копирования TF10 в TF99 (который уже существует): 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        = "[email protected]"
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)
person Jean-Francois T.    schedule 22.02.2017