Testlink 的 testcase 可以通过 Jenkins 去执行,当 Jenkins job 执行完之后,可以将执行结果保存到 Testlink 中。
jenkins 中 testlink plugin 仅仅可以在 freestyle 项目类型中使用,但目前大多数的 job 已经转移到 pipeline 类型,所以 testlink plugin 支持 pipeline 是一个自然的需要。
首先查到官方 Testlink-plugin 的 repo https://github.com/jenkinsci/testlink-plugin
pull 到本地查看一下项目结构
1
2
3
4
5
6
7
8
9
|
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ ├── resources
│ │ └── webapp
│ └── test
│ ├── java
│ └── resources
|
其中主要代码存放在 src/main/hudson/plugins/testlink 下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
├── AbstractTestLinkBuilder.java
├── AbstractTestLinkProjectAction.java
├── GraphHelper.java
├── Report.java
├── TestLinkBuildAction.java
├── TestLinkBuilder.java
├── TestLinkBuilderDescriptor.java
├── TestLinkInstallation.java
├── TestLinkJunitWrapper.java
├── TestLinkProjectAction.java
├── TestLinkResult.java
├── TestLinkSite.java
├── result
│ ├── AbstractJUnitResultSeeker.java
│ ├── AbstractTAPFileNameResultSeeker.java
│ ├── AbstractTestNGResultSeeker.java
│ ├── JUnitCaseClassNameResultSeeker.java
│ ├── JUnitCaseNameResultSeeker.java
│ ├── JUnitMethodNameResultSeeker.java
│ ├── JUnitSuiteNameResultSeeker.java
│ ├── ResultSeeker.java
│ ├── ResultSeekerDescriptor.java
│ ├── ResultSeekerException.java
│ ├── TAPFileNameMultiTestPointsResultSeeker.java
│ ├── TAPFileNameResultSeeker.java
│ ├── TestCaseWrapper.java
│ ├── TestNGClassNameResultSeeker.java
│ ├── TestNGMethodNameDataProviderNameResultSeeker.java
│ ├── TestNGMethodNameResultSeeker.java
│ └── TestNGSuiteNameResultSeeker.java
└── util
├── ExecutionOrderComparator.java
└── TestLinkHelper.java
|
现在可以来对代码进行分析了,首先我们寻找到调用的入口 TestlinkBuilder.java
定位到 perfrom()
函数
1
2
3
|
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
// function body
}
|
可以看到入参列表:
AbstractBuild<?, ?> build
Launcher
BuildListener
接下来看函数体内容:
1
2
3
4
5
6
7
|
// TestLink installation
listener.getLogger().println(Messages.TestLinkBuilder_PreparingTLAPI());
final TestLinkInstallation installation = DESCRIPTOR
.getInstallationByTestLinkName(this.testLinkName);
if (installation == null) {
throw new AbortException(Messages.TestLinkBuilder_InvalidTLAPI());
}
|
TestlinkInstallation 保存 configuration 里面对 Testlink 的配置信息:
包括 name
, url
, devKey
, testlinkJavaAPIPr operties
接下来是初始化其他的东西
1
2
3
4
5
6
7
8
9
10
|
TestLinkHelper.setTestLinkJavaAPIProperties(installation.getTestLinkJavaAPIProperties(), listener);
final TestLinkSite testLinkSite;
final TestCaseWrapper[] automatedTestCases;
final String testLinkUrl = installation.getUrl();
final String testLinkDevKey = installation.getDevKey();
TestPlan testPlan;
listener.getLogger().println(Messages.TestLinkBuilder_UsedTLURL(testLinkUrl));
...
testLinkSite = this.getTestLinkSite(testLinkUrl, testLinkDevKey, testProjectName, testPlanName, platformName, buildName, buildCustomFields, buildNotes);
|
TestlinkSite
成员里有 TestlinkAPI
, 可以通过传入 configuration 里面所设置的参数对 Testlink
进行操作。
1
2
3
4
5
6
7
8
9
10
|
final String[] testCaseCustomFieldsNames = TestLinkHelper.createArrayOfCustomFieldsNames(build.getBuildVariableResolver(), build.getEnvironment(listener), this.getCustomFields());
// Array of automated test cases
TestCase[] testCases = testLinkSite.getAutomatedTestCases(testCaseCustomFieldsNames);
// Retrieve custom fields in test plan
final String[] testPlanCustomFieldsNames = TestLinkHelper.createArrayOfCustomFieldsNames(build.getBuildVariableResolver(), build.getEnvironment(listener), this.getTestPlanCustomFields());
testPlan = testLinkSite.getTestPlanWithCustomFields(testPlanCustomFieldsNames);
// Transforms test cases into test case wrappers
automatedTestCases = this.transform(testCases);
|
获取 CustomFields
, 通过TestlinkSite
拿到对应的 (List)Testcase
,转换为(List)TestlinkWrapper
, 针对其进行了一层封装,具体细节看 result/TestcaseWrapper.java
.
1
2
3
4
5
6
|
for(TestCaseWrapper tcw : automatedTestCases) {
testLinkSite.getReport().addTestCase(tcw);
if(LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "TestLink automated test case ID [" + tcw.getId() + "], name [" +tcw.getName()+ "]");
}
}
|
TestSite
中有成员 Report
,用来存储基本的 Testcase,以及 TestStatus 这些信息
1
2
3
4
5
6
|
if(getResultSeekers() != null) {
for (ResultSeeker resultSeeker : getResultSeekers()) {
LOGGER.log(Level.INFO, "Seeking test results. Using: " + resultSeeker.getDescriptor().getDisplayName());
resultSeeker.seek(automatedTestCases, build, build.getWorkspace(), launcher, listener, testLinkSite);
}
}
|
ResultSeeker
通过执行测试用例得到的 *report.xml 文件解析得到相应 Testcase 的执行结果。
1
2
3
4
5
6
|
final Report report = testLinkSite.getReport();
report.tally();
...
final TestLinkResult result = new TestLinkResult(report);
final TestLinkBuildAction buildAction = new TestLinkBuildAction(result);
build.addAction(buildAction);
|
最后一步,生成 TestlinkReport,这里的对应的是 Jenkins 显示的report,而不是 TestlinkAPI 的 report。
执行逻辑结束。
在完成这篇文章之前,我对于能否清晰的表达出我的分析有很大的怀疑。我之前也曾阅读过源码,是关于数据结构的。针对大的,互相有依赖的,以一定代码规模的,我不曾分析过。
在阅读源码的时候,获得了以下几个小的知识点
- 从入口到各个模块的调用,是阅读源码的脉络
- 不要一开始纠结于细节。大致了解各个功能模块的作用就行
- 良好的抽象能力是关键技能