TestLink 1.9.20 API 使用详解
TestLink 1.9.20 内置 XML-RPC 接口实战:devKey 机制、struct 参数封装、项目/套件/用例/计划/构建/执行回写全链路 curl 与 Python 实测,含错误码语义。
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不会报「未知字段」,而是返回错误码 7004TESTPROJECT_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 |
| 7002 | TESTPROJECTNAME_EXISTS | 项目名重复 |
| 7004 | TESTPROJECT_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」。
九、参考链接
- 发行包内接口源码:
lib/api/xmlrpc/v1/xmlrpc.php、xmlrpc.class.php、APIErrors.php - 官方示例客户端(随源码发布):
lib/api/xmlrpc/v1/sample_clients/(Python/Java/PHP/Ruby/Perl) - TestLink 官网:https://testlink.org/ (查询日期 2026-10-25)
- GitHub 源码仓库:https://github.com/TestLinkOpenSourceTRMS/testlink-code
- 本专栏:《TestLink 1.9.20 介绍与选型指南》、《TestLink 1.9.20 Docker Compose 部署指南》、《TestLink 1.9.20 配置参数详解》