Jenins插件修改-TestLink

来源:互联网 发布:手机淘宝流量的来源 编辑:程序博客网 时间:2024/06/14 11:57


问题:

集成完了TestLinkJenkins,结果更新到TestLink测试build,但是发现如果测试用例失败了,failed detail infotestlink里面看不到。插件提供了插入Junit测试附件,但每次看错误的测试用例都要打开附件也影响工作效率。我希望能再Notes看到具体错误消息。但是插件只提供了如下信息:

只告诉Junit用例的状态和Class。没把具体的错误写出来。

期望:

Notes里面,Jenkins能把错误的信息也写入里面。比如加入一行message。这样每次查看错误用例就可以知道为什么出错了。直接去测试代码里面定位问题。

解决:

Jenkins强大插件库和开源代码,让我可以去定制自己的需求。我查了下JenkinsJira,发现有人和我有一样的问题,并且报了Bug,这个Bug2013/4/15号报的,至今还是reopen,因为这个bug的优先级只有Minor,如果你想看这个具体的问题点击这里https://issues.jenkins-ci.org/i#browse/JENKINS-17608

言归正传,还是得靠自己,下面大概介绍下,解决问题的思路:

(关于Jenkins插件开发,官网已经很详细。我主要还是解决TestLink具体问题。有兴趣的可以自己去看官网)

Pull源码:

github上面pull插件testlink的源码。

配置环境:

Jenkins plugin开发环境,我是Eclipse+maven+JDK.具体配置可以参考官网。

编译依赖:

mvn命令去下载插件的依赖库,我是导入到eclipse,所以直接用mvn eclipse:eclipse

开始看码:

开始之前,大概给大家介绍下Action

Action是插件用来在JobBuild页面增加功能的一种主要方式,是Jenkins最常用的一个扩展点。从下图中可以看出什么是Action,就是页面左边菜单栏的一个菜单项,还可以在右边的主页面显示相应的功能。

每个继承了Action这个扩展点的插件都要实现3个方法,方法如下:

 

public interface Action extends hudson.model.ModelObject {    java.lang.String getIconFileName();    java.lang.String getDisplayName();    java.lang.String getUrlName();}

 

第一个方法是菜单项图片,第二个方法是菜单名称,第三方法个是菜单链接。

Action分瞬时和持久2种,这里主要介绍的是瞬时的Action。瞬时的Action可以随时废弃,让另外一个新的Action来取代,适合一些每次构建都要执行操作的插件,但不适合需要保存持久数据的插件。

Jenkins官网的插件开发指南中,推荐使用Transient***ActionFactory系列的继承点,有TransientViewActionFactoryTransientProjectActionFactoryTransientBuildActionFactory等,使用该系列的继承点,只需要简单的覆写父类的 createFor方法,就可以实现创建瞬时Action的目的,可以根据不同的需要创建JobBuildViewAction

打开TestLink源码,先看TestLinkBuilder.java,找到@Extension,jenkins插件开发是提供了一个Extension契约模块,可以是接口或者是抽象类。所以我们要扩展Jenkins就必须实现这些接口或者抽象类的方法。

/** * The Descriptor of this Builder. It contains the TestLink installation.*/@Extensionpublic static final TestLinkBuilderDescriptor DESCRIPTOR = new TestLinkBuilderDescriptor();

进入TestLinkBuilderDescriptor :

public class TestLinkBuilderDescriptor extends BuildStepDescriptor<Builder> 

这里BuildStepDescriptor就是一个抽象类,它继承了Descriptor

public abstract class BuildStepDescriptor<T extends BuildStep & Describable<T>> extends Descriptor<T>

这段代码是告诉我TestLink如何实现Extension,这里面我看见下面的定义。所以当我们安装好TestLink插件后会看见这个选项,名字是一致的。

这里就是对应下面的“Invoke TestLink”

private static final String DISPLAY_NAME = <span style="color:#000000;BACKGROUND-COLOR: #ffff99">"Invoke TestLink"; </span>@Override public String getDisplayName() { return DISPLAY_NAME; }

打开TestLinkBuilder.java,继承了Builder,重写了perform

@Overridepublic boolean perform(AbstractBuild<?, ?> build, Launcher launcher,BuildListener listener) throws InterruptedException, IOException {LOGGER.log(Level.INFO, "TestLink builder started");this.failure = false;// TestLink installationlistener.getLogger().println(Messages.TestLinkBuilder_PreparingTLAPI());final TestLinkInstallation installation = DESCRIPTOR.getInstallationByTestLinkName(this.testLinkName);if (installation == null) {throw new AbortException(Messages.TestLinkBuilder_InvalidTLAPI());}TestLinkHelper.setTestLinkJavaAPIProperties(installation.getTestLinkJavaAPIProperties(), listener);final TestLinkSite testLinkSite;final TestCaseWrapper[] automatedTestCases;final String testLinkUrl = installation.getUrl();final String testLinkDevKey = installation.getDevKey();listener.getLogger().println(Messages.TestLinkBuilder_UsedTLURL(testLinkUrl));try {final String testProjectName = expandVariable(build.getBuildVariableResolver(),build.getEnvironment(listener), getTestProjectName());final String testPlanName = expandVariable(build.getBuildVariableResolver(),build.getEnvironment(listener), getTestPlanName());final String platformName = expandVariable(build.getBuildVariableResolver(),build.getEnvironment(listener), getPlatformName());final String buildName = expandVariable(build.getBuildVariableResolver(),build.getEnvironment(listener), getBuildName());final String buildNotes = Messages.TestLinkBuilder_Build_Notes();if(LOGGER.isLoggable(Level.FINE)) {LOGGER.log(Level.FINE, "TestLink project name: ["+testProjectName+"]");LOGGER.log(Level.FINE, "TestLink plan name: ["+testPlanName+"]");LOGGER.log(Level.FINE, "TestLink platform name: ["+platformName+"]");LOGGER.log(Level.FINE, "TestLink build name: ["+buildName+"]");LOGGER.log(Level.FINE, "TestLink build notes: ["+buildNotes+"]");}// TestLink Site objecttestLinkSite = this.getTestLinkSite(testLinkUrl, testLinkDevKey, testProjectName, testPlanName, platformName, buildName, buildNotes);if (StringUtils.isNotBlank(platformName) && testLinkSite.getPlatform() == null)     listener.getLogger().println(Messages.TestLinkBuilder_PlatformNotFound(platformName));final String[] customFieldsNames = this.createArrayOfCustomFieldsNames(build.getBuildVariableResolver(), build.getEnvironment(listener));// Array of automated test casesTestCase[] testCases = testLinkSite.getAutomatedTestCases(customFieldsNames);// Transforms test cases into test case wrappersautomatedTestCases = this.transform(testCases);testCases = null;listener.getLogger().println(Messages.TestLinkBuilder_ShowFoundAutomatedTestCases(automatedTestCases.length));// Sorts test cases by each execution order (this info comes from// TestLink)listener.getLogger().println(Messages.TestLinkBuilder_SortingTestCases());Arrays.sort(automatedTestCases, this.executionOrderComparator);} catch (MalformedURLException mue) {mue.printStackTrace(listener.fatalError(mue.getMessage()));throw new AbortException(Messages.TestLinkBuilder_InvalidTLURL(testLinkUrl));} catch (TestLinkAPIException e) {e.printStackTrace(listener.fatalError(e.getMessage()));throw new AbortException(Messages.TestLinkBuilder_TestLinkCommunicationError());}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()+ "]");    }}listener.getLogger().println(Messages.TestLinkBuilder_ExecutingSingleBuildSteps());this.executeSingleBuildSteps(automatedTestCases.length, testLinkSite, build, launcher, listener);listener.getLogger().println(Messages.TestLinkBuilder_ExecutingIterativeBuildSteps());this.executeIterativeBuildSteps(automatedTestCases, testLinkSite, build, launcher, listener);// Here we search for test results. The return if a wrapped Test Case// that// contains attachments, platform and notes.try {listener.getLogger().println(Messages.Results_LookingForTestResults());if(getResultSeekers() != null) {for (ResultSeeker resultSeeker : getResultSeekers()) {LOGGER.log(Level.INFO, "Seeking test results. Using: " + resultSeeker.getDescriptor().getDisplayName());resultSeeker.seek(automatedTestCases, build, launcher, listener, testLinkSite);}}} catch (ResultSeekerException trse) {trse.printStackTrace(listener.fatalError(trse.getMessage()));throw new AbortException(Messages.Results_ErrorToLookForTestResults(trse.getMessage()));} catch (TestLinkAPIException tlae) {tlae.printStackTrace(listener.fatalError(tlae.getMessage()));throw new AbortException(Messages.TestLinkBuilder_FailedToUpdateTL(tlae.getMessage()));}// This report is used to generate the graphs and to store the list of// test cases with each found status.final Report report = testLinkSite.getReport();report.tally();listener.getLogger().println(Messages.TestLinkBuilder_ShowFoundTestResults(report.getTestsTotal()));final TestLinkResult result = new TestLinkResult(report, build);final TestLinkBuildAction buildAction = new TestLinkBuildAction(build, result);build.addAction(buildAction);if(report.getTestsTotal() <= 0 && this.getFailIfNoResults() == Boolean.TRUE) {listener.getLogger().println("No test results found. Setting the build result as FAILURE.");build.setResult(Result.FAILURE);} else if (report.getFailed() > 0) {if (this.failedTestsMarkBuildAsFailure != null && this.failedTestsMarkBuildAsFailure) {    listener.getLogger().println("There are failed tests, setting the build result as FAILURE.");build.setResult(Result.FAILURE);} else {    listener.getLogger().println("There are failed tests, setting the build result as UNSTABLE.");build.setResult(Result.UNSTABLE);}} else if (this.getFailOnNotRun() != null && this.getFailOnNotRun() && report.getNotRun() > 0) {    listener.getLogger().println("There are not run tests, setting the build result as FAILURE.");    build.setResult(Result.FAILURE);}LOGGER.log(Level.INFO, "TestLink builder finished");// endreturn Boolean.TRUE;}

关于Jenkins代码具体开发我也没有过多研究,毕竟这次只要想解决Notesmessage显示问题,毕竟一切以工作为目的。

找到JUnitMethodNameResultSeeker..java。因为我在Jenkins里面配置TestLink更新测试结果是采用“method方式策略”,所以看下找个代码.

如何勾选了Notes,那么执行下面代码

if(this.isIncludeNotes()) {final String notes = this.getJUnitNotes(caseResult);automatedTestCase.appendNotes(notes);}

继续跟踪,Notes的消息就是从整个方法传过去的。

/** * Retrieves the Notes about the JUnit test. *  * @param testCase JUnit test. * @return Notes about the JUnit test. */private String getJUnitNotes( CaseResult testCase ){StringBuilder notes = new StringBuilder();notes.append( Messages.Results_JUnit_NotesForTestMethod(testCase.getName(), testCase.getClassName(), testCase.getSkipCount(), testCase.getFailCount(), (testCase.getSuiteResult() != null ? testCase.getSuiteResult().getTimestamp() : null),<span style="BACKGROUND-COLOR: #ffff99">testCase.getErrorDetails())</span>);return notes.toString();}

那么来看看testCase这个类,查看API,发现还有个getErrorDetails方法,正好是我需要的。那么我只要把它传下去。黄色那行,是我添加的。

继续深入,进入Results_JUnit_NotesForTestMethod

   /**     * name: {0}     * classname: {1}     * errors: {2}     * failures: {3}     * time: {4}     *      *      */    public static String Results_JUnit_NotesForTestMethod(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) {        return holder.format("Results.JUnit.NotesForTestMethod", arg1, arg2, arg3, arg4, arg5);    }

 

到这里我想大家都知道了。于是我就重载了这个方法:

//Over load it    /**     *      * name: {0}     * classname: {1}     * errors: {2}     * failures: {3}     * time: {4}     * message: {5}     */    public static String Results_JUnit_NotesForTestMethod(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6) {        return holder.format("Results.JUnit.NotesForTestMethod", arg1, arg2, arg3, arg4, arg5, arg6);    }

 

下面的问题就是找到资源文件,这个源码是支持国际化的,我的环境是en,所以找到util/Messages_en.properties

修改成:

Results.JUnit.NotesForTestMethod=name: {0}\nclassname: {1}\nerrors: {2}\nfailures: {3}\ntime: {4}\nmessage: {5}\n

红色部分是我新加的error message

测试

新改好的TestLink,打包重新安装插件。

上图:

看看偷笑,message出来了,Junit熟悉的错误消息。再也不用去打开附件或者去Jenkins上查看具体错误了。


 


0 0
原创粉丝点击