OK,前面的博客我们整理了junit运行完了所有的测试用例,那么OK了,现在开始该收集测试结果了。
在这最后一步中,junit主要是玩一个类,TestResult。这里类中封装了几个参数,在初始化这个类的时候赋初始值:
protected List<TestFailure> fFailures; // 失败结果集 protected List<TestFailure> fErrors; // 错误结果集 protected List<TestListener> fListeners; // 测试监听 protected int fRunTests; // 执行的测试的数量 private boolean fStop; // 是否停止,开关 public TestResult() { fFailures = new ArrayList<TestFailure>(); fErrors = new ArrayList<TestFailure>(); fListeners = new ArrayList<TestListener>(); fRunTests = 0; fStop = false; }
/** * @创建时间: 2016年1月21日 * @相关参数: @param test * @相关参数: @param p * @功能描述: 运行一个用例 */ public void runProtected(final Test test, Protectable p) { try { p.protect(); } catch (AssertionFailedError e) { addFailure(test, e); } catch (ThreadDeath e) { // don't catch ThreadDeath by accident throw e; } catch (Throwable e) { addError(test, e); } }
这里的try块触发测试用例的执行,然后2个catch块来捕获异常。如果抛出了AssertionFialedError异常,那么就说明我们的语言失败了,然后添加到TestResult类的失败结果集中,如果抛出了其他异常,那么就是说我们编写的测试代码报错了,然后添加到TestResult类的错误结果集中。JUnit 执行测试方法,并在测试结束后将失败和错误信息通知给所有的 test listener 。其中 addFailure、addError、endTest、startTest 是 TestListener 接口的四大方法,而 TestListener 涉及到 Observer 设计模式。
/** * @创建时间: 2016年1月21日 * @相关参数: @param test 测试用例 * @相关参数: @param e 异常 * @功能描述: 往失败List中添加失败 */ public synchronized void addFailure(Test test, AssertionFailedError e) { fFailures.add(new TestFailure(test, e)); for (TestListener each : cloneListeners()) { each.addFailure(test, e); } }上面的代码不多分析了,又是观察者模式来开始通知TestResult类上注册的监听器。在Junit38中往TestResult类中默认添加的监听其实就一个ResultPrinter类。TestResult 的 addFailure 进一步调用 ResultPrinter 的 addFailure。
@Override public void addError(Test test, Throwable e) { getWriter().println("KAO,有报错啦!!!"); } @Override public void addFailure(Test test, AssertionFailedError t) { getWriter().println("KAO,有失败啦"); }
此处代码将产生的失败对象加入到了 fFailures,将错误对象加入到fErrors中,此处的结果在程序退出时作为测试总体成功或失败的判断依据。而在 for 循环中,TestResult 对象循环遍历观察者(监听器)列表,通过调用相应的更新方法,更新所有的观察者信息,这部分代码也是整个 Observer 设计模式架构的重要部分。根据以上描述,JUnit 采用 Observer 设计模式使得 TestResult 与众多测试结果监听器通过接口 TestListenner 达到松耦合,使 JUnit 可以支持不同的使用方式。目标对象(TestResult)不必关心有多少对象对自身注册,它只是根据列表通知所有观察者。因此,TestResult 不用更改自身代码,而轻易地支持了类似于 ResultPrinter 这种监听器的无限扩充。目前,已有文本界面、图形界面和 Eclipse 集成组件三种监听器,用户完全可以开发符合接口的更强大的监听器。
有一个东西也值得我们学习,出于安全考虑,cloneListeners() 使用克隆机制取出监听器列表:
/** * @创建时间: 2016年1月21日 * @相关参数: @return listeners复制品 * @功能描述: 复制测试监听器List */ private synchronized List<TestListener> cloneListeners() { List<TestListener> result = new ArrayList<TestListener>(); result.addAll(fListeners); return result; }
OK,现在测试用例都执行完了,我们返回到最初的TestRunner类中,看看测试执行器的运行测试用例3大步的最后一步。
/** * @创建时间: 2016年1月22日 * @相关参数: @param suite * @相关参数: @param wait * @相关参数: @return * @功能描述: 测试执行器执行测试 */ public TestResult doRun(Test suite, boolean wait) { TestResult result = createTestResult(); result.addListener(fPrinter); long startTime = System.currentTimeMillis(); suite.run(result); // 收集结果 long endTime = System.currentTimeMillis(); long runTime = endTime - startTime; fPrinter.print(result, runTime); pause(wait); return result; }计算了开始时间和结束时候,取他们差值就是运行测试用例的执行时间,然后调用ResultPrinter类型的属性fPrinter来开始打印结果。打印结果又分4步;打印耗时-->打印错误-->打印失败-->打印测试个数,失败个数,错误个数。
synchronized void print(TestResult result, long runTime) { printHeader(runTime); printErrors(result); printFailures(result); printFooter(result); }
protected void printHeader(long runTime) { getWriter().println("第四步:框架开始统计时间===="); getWriter().println("耗时:" + elapsedTimeAsString(runTime) + "秒"); } /** * @创建时间: 2016年1月21日 * @相关参数: @param runTime * @相关参数: @return * @功能描述: 格式化时间,单位毫秒 */ protected String elapsedTimeAsString(long runTime) { return NumberFormat.getInstance().format((double) runTime / 1000); } protected void printErrors(TestResult result) { printDefects(result.errors(), result.errorCount(), "错误"); } protected void printFailures(TestResult result) { printDefects(result.failures(), result.failureCount(), "失败"); } protected void printDefects(Enumeration<TestFailure> booBoos, int count, String type) { if (count == 0) return; if (count == 1) { getWriter().println("遗憾:!一共有" + count + "个" + type + ":"); } else { getWriter().println("遗憾:!一共有 " + count + "个" + type + ":"); } for (int i = 1; booBoos.hasMoreElements(); i++) { printDefect(booBoos.nextElement(), i); } } protected void printFooter(TestResult result) { getWriter().println("第五步:框架开始统计结果===="); if (result.wasSuccessful()) { getWriter().println("结果:OK,木问题!"); // getWriter().println(" (" + result.runCount() + " test" + (result.runCount() == 1 ? "" : "s") + ")"); getWriter().println("统计:一共执行了" + result.runCount() + "个测试用例"); } else { getWriter().println("结果:AU,出错啦!"); getWriter().println("Tests run: " + result.runCount() + ", Failures: " + result.failureCount() + ", Errors: " + result.errorCount()); } getWriter().println("第六步:框架结束整个测试===="); }
/** * @创建时间: 2016年1月21日 * @相关参数: @return * @功能描述: 判断测试是否成功 */ public synchronized boolean wasSuccessful() { return failureCount() == 0 && errorCount() == 0; }
这里有2个类值得我们研究下。
1,AssertionFailedError 异常类。
package org.linkinpark.commons.framework; /** * @创建作者: LinkinPark * @创建时间: 2016年1月21日 * @功能描述: 某个预言失败抛出该异常 * * <p> * AssertionError:抛出该异常指示某个断言失败。 * </p> */ public class AssertionFailedError extends AssertionError { private static final long serialVersionUID = 1L; public AssertionFailedError() { } /** * @创建时间: 2016年1月21日 * @相关参数: @param message 详细的错误信息。 * @构造描述: 如果错误信息为null就return空字符串。 */ public AssertionFailedError(String message) { super(defaultString(message)); } /** * @创建时间: 2016年1月21日 * @相关参数: @param message 详细的错误信息。 * @相关参数: @return 如果错误信息为null就return空字符串。 * @功能描述: 空值校验+处理错误信息 */ private static String defaultString(String message) { return message == null ? "" : message; } }
2,TestFailure 测试失败类。
package org.linkinpark.commons.framework; import java.io.PrintWriter; import java.io.StringWriter; /** * @创建作者: LinkinPark * @创建时间: 2016年1月21日 * @功能描述: 失败的测试用例容器 */ public class TestFailure { protected Test fFailedTest; // 失败的测试 protected Throwable fThrownException; // 异常信息 public TestFailure(Test failedTest, Throwable thrownException) { fFailedTest = failedTest; fThrownException = thrownException; } public Test failedTest() { return fFailedTest; } public Throwable thrownException() { return fThrownException; } /** * 重写toString(),返回一个错误原因描述 */ @Override public String toString() { return fFailedTest + ": " + fThrownException.getMessage(); } /** * 获取错误栈信息,期间打印到控制台 */ public String trace() { StringWriter stringWriter = new StringWriter(); PrintWriter writer = new PrintWriter(stringWriter); thrownException().printStackTrace(writer); return stringWriter.toString(); } /** * 获取错误描述 */ public String exceptionMessage() { return thrownException().getMessage(); } /** * Returns {@code true} if the error is considered a failure * (i.e. if it is an instance of {@code AssertionFailedError}), * {@code false} otherwise. */ public boolean isFailure() { return thrownException() instanceof AssertionFailedError; } }
好了,现在junit运行测试3大步都已经走完了,我们返回最初的测试入口,来看下最后程序的退出。勿忘初心,方得始终。
public static final int SUCCESS_EXIT = 0; public static final int FAILURE_EXIT = 1; public static final int EXCEPTION_EXIT = 2; /** * @创建时间: 2016年1月21日 * @相关参数: @param args * @功能描述: 测试框架入口 */ public static void main(String[] args) { TestRunner aTestRunner = new TestRunner(); try { String[] linkinArgs = new String[] { "org.linkinpark.commons.textui.LinkinTestAll" }; TestResult testResult = aTestRunner.start(linkinArgs); if (!testResult.wasSuccessful()) { System.exit(FAILURE_EXIT); } System.exit(SUCCESS_EXIT); } catch (Exception e) { System.err.println(e.getMessage()); System.exit(EXCEPTION_EXIT); } }
框架最终如何退出同样取决于TestResult类的失败个数和错误个数,如果wasSuccessful那么没有问题,直接正常退出就好了,否则的话异常退出。最后以System类的exit api结束这篇博客。
===========================================================================================================================================================================================
exit
public static void exit(int status)
- 终止当前正在运行的 Java 虚拟机。参数用作状态码;根据惯例,非 0 的状态码表示异常终止。
该方法调用
Runtime
类中的exit
方法。该方法永远不会正常返回。调用
System.exit(n)
实际上等效于调用:Runtime.getRuntime().exit(n)
-
- 参数:
status
- 退出状态。- 抛出:
SecurityException
- 如果安全管理器存在并且其checkExit
方法不允许以指定状态退出。- 另请参见:
Runtime.exit(int)