TestLink 1.9.20 API 使用详解

TestLink 1.9.20 内置 XML-RPC 接口实战:devKey 机制、struct 参数封装、项目/套件/用例/计划/构建/执行回写全链路 curl 与 Python 实测,含错误码语义。

TestLinkTestLink测试管理自托管Docker Compose2026/10/25

TestLink 1.9.20 API 使用详解

目标:TestLink 1.9.20 没有 REST API,只有内置的 XML-RPC 接口(API 版本 1.1)。本篇按「能不能调通」的顺序给出完整可用路径:接口地址、认证方式(devKey)、参数封装规则、5 个高频方法的逐条实测回显、错误码语义,以及一个可复用的 Python 客户端骨架。文中所有请求与响应均来自 2026-10-25 的本机实测,不做推测。

一、环境信息

项目实测值
接口地址http://localhost:8128/lib/api/xmlrpc/v1/xmlrpc.php
接口版本1.1(tl.about 返回原文)
认证方式devKey,与 users.script_key 字段比对
请求格式XML-RPC,Content-Type: text/xml
响应格式XML-RPC(成功为 <params>,失败为 <fault> 或业务错误结构)
HTTP 状态恒为 200,成败要看响应体
服务版本TestLink 1.9.20

⚠️ 注意两个容易走错的地方:其一,接口不是 /api/xmlrpc.php(那会 404),实际路径在 lib/api/xmlrpc/v1/ 下;其二,方法名必须带 tl. 命名空间,Python 客户端里写成 server.tl.about(),直接调 server.about() 会返回 requested method ... does not exist。

二、接口能力总览

发行包内 xmlrpc.class.php 注册的方法覆盖测试资产全生命周期,常用的分组如下(方法名均以 tl. 开头):

分组方法用途
元信息about、checkDevKey取接口版本、校验 devKey
项目createTestProject、getProjects、getProjectTestPlans、deleteTestProject测试项目增查删
套件createTestSuite、getFirstLevelTestSuitesForTestProject测试套件(可多级)
用例createTestCase、getTestCase、createTestCaseSteps、addTestCaseToTestPlan用例与步骤、挂到计划
计划与构建createTestPlan、createBuild、getBuildsForTestPlan、getProjectTestPlans测试计划、构建
执行reportTCResult、getLastExecutionResult、getExecutionSet、deleteExecution回写与查询执行结果
需求getRequirements、assignRequirements、getReqCoverage需求关联与覆盖率
平台createPlatform、addPlatformToTestPlan、getProjectPlatforms平台维度
关键字getProjectKeywords、addTestCaseKeywords关键字

接口开关在主配置 $tlCfg->api->enabled(实测默认 TRUE)。关闭后所有方法都会失败。

三、认证:devKey 是什么

TestLink 的 devKey 不是独立生成的接口密钥,而是直接取自用户表的 script_key 字段。源码里的校验逻辑(xmlrpc.class.php 第 1003 行)是:

SELECT id FROM users WHERE script_key='<devKey>'

含义很直接:

  • 某个用户的 script_key 是什么,它的 devKey 就是什么;该用户是谁,接口就以谁的身份操作。
  • script_key 为空表示该用户没有接口权限。
  • 想给集成账号单独发 key,就为那个账号生成 script_key,而不是共用管理员。

实测中为一个管理员账号写入 32 位十六进制 key 后校验通过:

checkDevKey -> True
<?xml version="1.0"?>
<methodResponse>
  <params>
    <param><value><struct>
      <member><name>devKey</name><value><string>a1b2c3d4e5f60718293a4b5c6d7e8f90</string></value></member>
    </struct></value></param>
  </params>
</methodResponse>

安全提示:script_key 是明文存储、明文比对,且接口没有限流、没有 IP 白名单。生产上务必在反向代理层限制来源网段,并为集成单独建一个只授予必要项目权限的账号,不要用全局管理员。

四、参数封装规则(最容易踩的坑)

TestLink 不接受「位置参数」,必须把所有参数塞进一个 struct 里作为唯一实参。这与普通 XML-RPC 习惯相反,也是新手调不通的首要原因。

正确写法(tl.checkDevKey):

<methodCall><methodName>tl.checkDevKey</methodName>
 <params><param><value><struct>
   <member><name>devKey</name><value><string>a1b2c3d4e5f60718293a4b5c6d7e8f90</string></value></member>
 </struct></value></param></params>
</methodCall>

错误写法(把 devKey 当位置参数):

<params><param><value><string>a1b2c3d4e5f60718293a4b5c6d7e8f90</string></value></param></params>

返回业务错误,而不是抛异常:

{'code': 100, 'message': '(checkDevKey) - No devKey provided.'}

用 Python 的 xmlrpc.client 时,这个规则天然满足——传 dict 即可。

五、高频方法逐条实测

以下调用均基于同一个 devKey,服务地址 http://localhost:8128/lib/api/xmlrpc/v1/xmlrpc.php,Python 写法为 s.tl.<方法>({...})。

5.1 tl.about —— 探活与版本

s.tl.about({})
Testlink API Version: 1.1 initially written by Asiel Brumfield
 with contributions by TestLink development Team

无参数、无需 devKey,是最合适的健康检查调用。

5.2 tl.createTestProject —— 建测试项目

s.tl.createTestProject({
    'devKey': K, 'testprojectname': 'QuZuoDemo', 'testcaseprefix': 'QZ',
    'notes': 'quzuo doc demo', 'enableRequirements': 1, 'enableTestPriority': 1,
    'enableAutomation': 1, 'enableInventory': 0, 'active': 1, 'public': 1 })
[{'operation': 'createTestProject', 'additionalInfo': '', 'status': True, 'id': 1, 'message': 'Success!'}]

实测踩坑:前缀字段名是 testcaseprefix(单数、无 s)。写成 testcasesprefix 不会报「未知字段」,而是返回错误码 7004 TESTPROJECT_TESTCASEPREFIX_IS_EMPTY——看起来像参数为空,实际是字段名拼错导致该值根本没被读到。

5.3 tl.createTestSuite —— 建测试套件

s.tl.createTestSuite({'devKey': K, 'testprojectname': 'QuZuoDemo',
                      'testsuitename': 'LoginSuite', 'details': 'login suite'})
[{'id': 2, 'name': '', 'name_changed': False, 'status': True,
  'operation': 'createTestSuite', 'additionalInfo': '', 'message': 'ok'}]

注意返回里 name 是空串,但套件确实建成了——getFirstLevelTestSuitesForTestProject 能看到 name='LoginSuite',不要被这个空字段误导。

5.4 tl.createTestCase —— 建步骤式用例

s.tl.createTestCase({
    'devKey': K, 'testprojectname': 'QuZuoDemo', 'testsuiteid': 2,
    'testcasename': 'Login with valid account', 'summary': '验证正确账号可登录',
    'steps': [{'step_number': 1, 'actions': '打开登录页', 'expectedresults': '显示登录表单'},
              {'step_number': 2, 'actions': '输入账号密码并提交', 'expectedresults': '进入首页'}],
    'authorlogin': 'admin', 'priority': 2 })
[{'operation': 'createTestCase', 'status': True, 'id': 4,
  'additionalInfo': {'id': 4, 'external_id': '1', 'status_ok': 1, 'msg': 'ok',
                     'version_number': 1, 'tcversion_id': 5, ...},
  'message': 'Success!'}]

必须给 testsuiteid(不是套件名)。漏了会返回:

[{'code': 180, 'message': '(createTestCase) - No testsuiteid provided.'}]

5.5 tl.createTestPlan / tl.createBuild

s.tl.createTestPlan({'devKey': K, 'testplanname': 'v1.0 Regression',
                     'testprojectname': 'QuZuoDemo', 'notes': 'regression',
                     'active': 1, 'public': 1})
s.tl.createBuild({'devKey': K, 'testplanid': 3,
                  'buildname': 'build-20261025', 'buildnotes': 'first build'})
[{'operation': 'createTestPlan', 'additionalInfo': '', 'status': True, 'id': 3, 'message': 'Success!'}]
[{'status': True, 'operation': 'createBuild', 'id': 1, 'message': 'Success!'}]

getProjectTestPlans 返回的计划结构里带一个 api_key 字段(32 字节十六进制),是计划级的标识,不属于认证体系:

[{'id': '3', 'name': 'v1.0 Regression', 'active': '1', 'is_public': '1',
  'testproject_id': '1', 'api_key': '3aa31092118143829adf9a5d1db04df0...'}]

5.6 tl.reportTCResult —— 回写执行结果

先 addTestCaseToTestPlan 把用例(外部编号 QZ-1)挂到计划,再回写结果:

s.tl.addTestCaseToTestPlan({'devKey': K, 'testprojectid': 1, 'testplanid': 3,
                            'testcaseexternalid': 'QZ-1', 'version': 1})
s.tl.reportTCResult({'devKey': K, 'testplanid': 3, 'testcaseexternalid': 'QZ-1',
                     'status': 'p', 'buildname': 'build-20261025', 'notes': 'manual pass'})
{'feature_id': 1, 'operation': 'addTestCaseToTestPlan', 'status': True, 'message': ''}
[{'status': True, 'operation': 'reportTCResult', 'overwrite': False,
  'message': 'Success!', 'id': 1}]

实测踩坑:计划里还没有构建时回写会失败,错误码 3031: (reportTCResult) - There are no builds defined for Test Plan (name=v1.0 Regression/id=3) 顺序必须是「建计划 → 建构建 → 挂用例 → 回写结果」。

查询最近一次执行:

s.tl.getLastExecutionResult({'devKey': K, 'testplanid': 3, 'testcaseexternalid': 'QZ-1'})
[{'id': '1', 'build_id': '1', 'tester_id': '1', 'execution_ts': '2026-10-25 15:12:09',
  'status': 'p', 'testplan_id': '3', 'tcversion_id': '5', 'tcversion_number': '1',
  'platform_id': '0', 'execution_type': '2', 'execution_duration': '', 'notes': 'manual pass'}]

status 取值:p 通过、f 失败、b 阻塞、n 未执行(与界面一致)。

六、错误语义与排查

XML-RPC 层与业务层是两套错误:

类型表现含义处置
协议层 fault<fault>,faultCode -32601 + requested method xxx does not exist方法名缺少 tl. 命名空间改成 tl.xxx
业务错误<params> 内为 code+message 结构参数不合法、对象不存在等按 code 查 APIErrors.php
HTTP 层恒为 200不能靠状态码判断成败必须解析响应体

常见 code:

code常量触发原因
100—缺少参数(如 No devKey provided.)
180—createTestCase 缺 testsuiteid
7002TESTPROJECTNAME_EXISTS项目名重复
7004TESTPROJECT_TESTCASEPREFIX_IS_EMPTY前缀字段名拼错或为空
3031—计划未定义构建就回写执行

接口没有分页协议,getProjects 之类返回全量;项目多时响应体较大,客户端应自行缓存。

七、可复用的 Python 客户端骨架

import xmlrpc.client as xc

SERVER = 'http://localhost:8128/lib/api/xmlrpc/v1/xmlrpc.php'
DEVKEY = 'your-32-hex-key'

s = xc.ServerProxy(SERVER)

def call(method, **kwargs):
    """统一封装:自动注入 devKey,自动解包单元素列表。"""
    kwargs.setdefault('devKey', DEVKEY)
    result = getattr(s.tl, method)(kwargs)
    return result

# 1) 探活(about 无需 devKey)
print(s.tl.about({}))

# 2) 建项目 → 套件 → 用例 → 计划 → 构建 → 回写结果
call('createTestProject', testprojectname='Demo2', testcaseprefix='D2',
     enableRequirements=1, enableTestPriority=1, enableAutomation=1,
     enableInventory=0, active=1, public=1)
call('createTestSuite', testprojectname='Demo2', testsuitename='Smoke')

要点:

  • getattr(s.tl, method)(kwargs) 的实参必须是一个 dict,这正是 TestLink 要求的封装形式。
  • 错误以返回结构出现,不抛异常,因此封装里要显式判断 status 或 code 字段。
  • 时间字段是服务端本地时间字符串(YYYY-MM-DD HH:MM:SS),无时区标识,跨时区集成需自行约定。

八、集成注意事项

  • 幂等:createTestProject 重复调用会返回 7002,集成脚本应把「已存在」当成功处理。
  • 事务性:TestLink 没有批量事务接口,一个用例一步请求;大批量导入(上千条)建议分批 + 失败重试,并在本地记录 external_id 映射。
  • 认证隔离:CI 用独立账号 + 独立 script_key,只授予目标项目权限;泄露后可单独吊销(清空该账号 script_key)而不影响其他人。
  • 超时与并发:单请求是同步 PHP 执行,批量场景串行即可;并发过高会把数据库连接打满。
  • 版本锁定:项目已停更,接口不会有破坏性变更,但也不会新增方法;集成层不要依赖「未来会有 REST」。

九、参考链接