zoukankan      html  css  js  c++  java
  • junit源码解析--捕获测试结果


    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;
    	}


    前面框架在执行测试用例的过程中,用了命令者模式,调用runProtected真正开始执行test.runBare()方法。我们来看下这个runProtected()方法源码:

    /**
    	 * @创建时间: 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("第六步:框架结束整个测试====");
    	}


    在printFooter()这个方法中统计统计个数的时候,成功与否取决于TestTesult类。如果失败次数和错误次数都是0,那么测试成功。

    /**
    	 * @创建时间: 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)
    ===========================================================================================================================================================================================


  • 相关阅读:
    写代码如坐禅:你是哪一类程序员
    关于鸿蒙的商业讨论
    为什么你总是“把天聊死”?
    生活不易,唯有努力
    如何用一句话激怒一名程序员?
    华为正式开源方舟编译器,开源了,它真的开源了!
    为什么HTTPS比HTTP更安全?
    《管理者必读12篇》购买方法
    程序员都在用的电脑小技巧,看一遍就学会,每天早下班一小时
    一位程序员的一天工作清单:5:30下班,5:30起床
  • 原文地址:https://www.cnblogs.com/LinkinPark/p/5232883.html
Copyright © 2011-2022 走看看