公司采用Rally作为实施敏捷开发的平台和工具,自然少不了自动化。Rally作为大牌敏捷工具其接口也是很丰富的,但baidu上搜到的内容不多,github 上也才是40+ star
pyral 是其官方支持的python库,底层也是通过封装restful API方式实现。 可以通过
pip install pyral
安装
https://pypi.python.org/pypi/pyral 是其官方文档, 有一个
https://github.com/RallyTools/RallyRestToolkitForPython 是其gihub的地址,包含了一些基本的操作的例子
开始code, 一个简单的例子
文档上给了一些例子:
import sys
from pyral import Rally, rallyWorkset
options = [arg for arg in sys.argv[1:] if arg.startswith('--')]
args = [arg for arg in sys.argv[1:] if arg not in options]
server, user, password, apikey, workspace, project = rallyWorkset(options)
rally = Rally(server, user, password, apikey=apikey, workspace=workspace, project=project)
rally.enableLogging('mypyral.log')
query_criteria = 'FormattedID = "%s"' % args[0]
response = rally.get('TestCase', fetch=True, query=query_criteria)
if response.errors:
sys.stdout.write("\n".join(errors))
sys.exit(1)
for testCase in response: # there should only be one qualifying TestCase
print "%s %s %s %s" % (testCase.Name, testCase.Type,
testCase.DefectStatus, testCase.LastVerdict)
这是个找TestCase 的例子, 很简单,根据Query语句来的'FormattedID = "%s"' % args[0]
, 你可以用python gettc.py TC1184
来看TC1184 这个TestCase的信息
记得query 语句=两边要留有空格,不然会报错。
有了API Key就可以不用User Name 和 Password了
在这个网址申请API Key https://rally1.rallydev.com/login/accounts/index.html#/keys
在pyral中获取object(TestCase, Defect, UserStory,User etc) 的方式一般有两种,一种是FormattedID,另一种是通过ObjectID来获得,上面的例子就是通过FormattedID来query的。 object id 才是真正的id, FormattedID其实就是方便记忆而已,也仅限于一些特定的object才有(US, TC, Feature等), 像TestCaseResult, User等是没有FormattedID的,所以必须要通过object id来获得他们
再来一个例子
创建一个新的defect
proj = rally.getProject()
# get the first (and hopefully only) user whose DisplayName is 'Sally Submitter'
user = rally.getUserInfo(name='Sally Submitter').pop(0)
defect_data = { "Project" : proj.ref, "SubmittedBy" : user.ref,
"Name" : name, "Severity" : severity, "Priority" : priority,
"State" : "Open", "ScheduleState" : "Defined",
"Description" : description }
try:
defect = rally.create('Defect', defect_data)
except Exception, details:
sys.stderr.write('ERROR: %s \n' % details)
sys.exit(1)
print "Defect created, ObjectID: %s FormattedID: %s" % (defect.oid, defect.FormattedID)
这个也很简单, 就是用rally.create()
的方法去创建一个新的defect,其中可以根据不同公司的要求去创建自己的defect_data,特别是有些custom field 需要自己手动去添加。
有些entity是需要用ref的,比如上面的user.ref, 可能有些entity没有ref这个变量,你可以试试用_ref
update 一个defect
defectID, customer, target_date, notes = args[:4]
# target_date must be in ISO-8601 format "YYYY-MM-DDThh:mm:ssZ"
defect_data = { "FormattedID" : defectID,
"Customer" : customer,
"TargetDate" : target_date,
"Notes" : notes
}
try:
defect = rally.update('Defect', defect_data)
except Exception, details:
sys.stderr.write('ERROR: %s \n' % details)
sys.exit(1)
print "Defect %s updated" % defect.FormattedID
这个和之前创建defect的例子比较类似,就是用rally对象的函数来更新rally.update
获得所有的用户
all_users = rally.getAllUsers()
for user in all_users:
tz = user.UserProfile.TimeZone or 'default'
role = user.Role or '-No Role-'
values = (int(user.oid), user.Name, user.UserName, role, tz)
print("%12.12d %-24.24s %-30.30s %-12.12s" % values)
以上就是pyral上给出的例子,在github上有更多的例子,简单的功能基本上都可以实现
Rally.get 函数在restapi.py中可以看到它的详细定义和实现, 建议可以仔细去过一遍代码,粗略看下到底有什么功能, 比如有search, delete这些可能example中并没有很好的使用的例子,需要的话还得自己去挖掘。 国内好像相关的社区也并不发达,要么你去官方发邮件咨询,要么还是自己看代码
def get(self, entity, fetch=False, query=None, order=None, **kwargs):
"""
A REST approach has the world seen as resources with the big 4 ops available on them
(GET, PUT, POST, DELETE). There are other ops but we don't care about them here.
Each resource _should_ have a unique URI that is used to identify the resource.
The GET operation is used to specify that we want a representation of that resource.
For Rally, in order to construct a URI, we need the name of the entity, the attributes
of the entity (and attributes on any child/parent entity related to the named entity),
the query (selection criteria) and the order in which the results should be returned.
The fetch argument (boolean or a comma separated list of attributes) indicates whether
we get complete representations of objects back or whether we get "shell" objects with
refs to be able to retrieve the full info at some later time.
An optional instance=True keyword argument will result in returning an instantiated
Rally Entity if and only if the resultCount of the get is exactly equal to 1.
Otherwise, a RallyRESTResponse instance is returned.
All optional keyword args:
fetch=True/False or "List,Of,Attributes,We,Are,Interested,In"
query='FieldName = "some value"' or ['fld1 = 19', 'fld27 != "Shamu"', etc.]
order="fieldName ASC|DESC"
instance=False/True
pagesize=n
start=n
limit=n
projectScopeUp=True/False
projectScopeDown=True/False
"""
这是search 函数的说明
def search(self, keywords, **kwargs):
"""
Given a list of keywords or a string with space separated words, issue
the relevant Rally WSAPI search request to find artifacts within the search
scope that have any of the keywords in any of the artifact's text fields.
https://rally1.rallydev.com/slm/webservice/v2.x/search?
_slug=%2Fsearch
&project=%2Fproject%2F3839949386
&projectScopeUp=false
&projectScopeDown=true
&searchScopeOid=3839949386 # in this case it is the Project ObjectID
&searchScopeUp=false
&searchScopeDown=true
&searchScopeType=project
&keywords=wsapi%20documentation
&fetch=true
&recycledItems=true
&includePermissions=true
&compact=true
&start=1
&pagesize=25
defaults:
projectScopeUp=false
projectScopeDown=false
includePermissions=true
recycledItems=false
compact=true
A successful search returns SearchObject instances, which have the useful attributes of:
ObjectID
FormattedID
Name
Project
MatchingText
LastUpdateDate
"""
delete 函数的说明
def delete(self, entityName, itemIdent, workspace='current', project='current', **kwargs):
"""
Given a Rally entityName, an identification of a specific Rally instnace of that
entity (in either OID or FormattedID format), issue the REST DELETE call and
return an indication of whether the delete operation was successful.
"""
get allow value:
from pyral import Rally, rallyWorkset
import io
RALLY_SERVER='rally1.rallydev.com'
API_KEY='xxxxxx'
class RallyUtil():
def __init__(self):
self.rally = Rally(server= RALLY_SERVER, apikey=API_KEY,
workspace='MicroStrategy Workspace', project='Delivery')
self.rally.enableLogging('mypyral.log')
def get_testset(self):
criterion = 'FormattedID = TS15992'
response= self.rally.get('TestSet', fetch='c_ProductionRelease', query=criterion, order="FormattedID", projectScopeDown=True)
for testset in response:
print testset.c_ProductionRelease
if __name__ == '__main__':
rally = RallyUtil()
rally.get_testset()
print self.rally.getAllowedValues('TestSet','c_ProductionRelease')