• junit源码解析--测试驱动运行阶段


    前面的博客里面我们已经整理了junit的初始化阶段,接下来就是junit的测试驱动运行阶段,也就是运行所有的testXXX方法。OK,现在我们开始吧。

    前面初始化junit之后,开始执行doRun方法。

    Test suite = getTest(testCase);
    			return doRun(suite, wait);
    doRun()方法的代码如下:

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

    分析一下上面的代码,一共分3步。

    1,初始化TestResult,然后往该对象上注册事件。

    fPrinter 是 junit.textui.ResultPrinter 类的实例,该类提供了向控制台输出测试结果的一系列功能接口,输出的格式在类中定义。 ResultPrinter 类实现了 TestListener 接口,具体实现了 addError、addFailure、endTest 和 startTest 四个重要的方法,这种设计是 Observer 设计模式的体现,在 addListener 方法的代码中:

    /**
    	 * @创建时间: 2016年1月21日
    	 * @相关参数: @param listener 测试用例监听
    	 * @功能描述: 注册一个事件
    	 */
    	public synchronized void addListener(TestListener listener)
    	{
    		fListeners.add(listener);
    	}
    将 ResultPrinter 对象加入到 TestResult 对象的监听器列表中,因此实质上 TestResult 对象可以有多个监听器显示测试结果。下一篇博客junit--测试结果捕获阶段的分析中我将会描述对监听器的消息更新。这里有一个说明的就是,这个fPrinter是TestResult类的一个属性,在new TestResult的时候就被设值成System.out了。


    2,计时开始,运行测试用例,计时结束。

    这块才是重点,OK,我们来仔细的分析下。前面说的junit38默认的测试执行器就是Test接口的一个实现类TestSuite。我们先来看下该类的run源码:

    /**
    	 * 运行测试
    	 */
    	public void run(TestResult result)
    	{
    		for (Test each : fTests)
    		{
    			if (result.shouldStop())
    			{
    				break;
    			}
    			runTest(each, result);
    		}
    	}
    
    	public void runTest(Test test, TestResult result)
    	{
    		test.run(result);
    	}
    Junit 通过 fore循环 对 TestSuite 中的整个“树结构”递归遍历运行其中的节点和叶子。此处 JUnit 代码颇具说服力地说明了 Composite 模式的效力,run 接口方法的抽象具有重大意义,它实现了客户代码与复杂对象容器结构的解耦,让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器。上篇博客我们也说了,如果我们自己写的测试类提供suite()的静态方法,那么junit就自动帮我们创建一个运行组件new TestSuite。那假如我们提供了suite()静态方法,就会使用我们自己测试组件。

    ========================================================================================================================================================================================

    比如下面的测试代码:

    public class LinkinTestAll extends TestCase
    {
    	
    	public static Test suite()
    	{
    		TestSuite suite = new TestSuite().addTestSuite(LinkinTest.class);
    		suite.addTest(new LinkinTest1("testLinkin8Error")).addTest(new LinkinTest1("testLinkin4Normal"));
    		return suite;
    	}
    
    	public static void main(String args[])
    	{
    		TestRunner.run(suite());
    	}
    
    }

    明显的这里我们自己创建的TestSuite中也包含了一个Suite了呢。我们还是来看下addTestSuite()这个方法的源码吧;

    /**
    	 * @创建时间: 2016年1月22日
    	 * @相关参数: @param testClass
    	 * @功能描述: 直接添加测试类到suite中
    	 */
    	public TestSuite addTestSuite(Class<? extends TestCase> testClass)
    	{
    		addTest(new TestSuite(testClass));
    		return this;
    	}
    在我们的测试类中如果TestSuite又包含了一个TestSuite,那么全面我们说的那个fore循环就开始发生作用了,如果测试用例类型是TestCase直接就被触发执行,如果是TestSuite那么就再自己掉一次进去自己包含的TestSuite中去执行其中的测试用例。

    ========================================================================================================================================================================================================

    每次循环得到的节点 test,都带着我们在测试执行器中new()出来的TestResult,然后将result一起传递给 runTest 方法,进行下一步更深入的运行。那OK,现在我们在来看下每个测试用例的执行情况。下面贴出每个测试用例的run()方法源码:

    public void run(TestResult result)
    	{
    		result.run(this);
    	}
    下面贴出TestResult中的run()方法源码:

    /**
    	 * @创建时间: 2016年1月21日
    	 * @相关参数: @param test
    	 * @功能描述: 运行一个测试用例,命令者模式
    	 */
    	protected void run(final TestCase test)
    	{
    		startTest(test);
    		Protectable p = new Protectable()
    		{
    			public void protect() throws Throwable
    			{
    				test.runBare();
    			}
    		};
    		runProtected(test, p);
    		endTest(test);
    	}
    明显的这里这里执行每一个测试用例,同样分为小3步,通知监听测试开始了-->真正去反射执行测试用例(重点)-->通知监听测试结束了呢。

    这里的 startTest 和 endTest 方法也是 Observer 设计模式中的两个重要的消息更新方法。

    ========================================================================================================================================================================================================

    我们先来看下startTest()方法就好了。
    /**
    	 * @创建时间: 2016年1月21日
    	 * @相关参数: @param test
    	 * @功能描述: 通知结果:测试用例可以开始执行了
    	 */
    	public void startTest(Test test)
    	{
    		final int count = test.countTestCases();
    		synchronized (this)
    		{
    			fRunTests += count;
    		}
    		for (TestListener each : cloneListeners())
    		{
    			System.out.println("###########开始迭代运行整套测试,互相独立###########");
    			each.startTest(test);
    		}
    	}

    ========================================================================================================================================================================================================

    现在我们来看重点,test.runBare()。

    这里变量 P 指向一个实现了 Protectable 接口的匿名类的实例,Protectable 接口只有一个 protect 待实现方法。而 junit.framework.TestResult.runProtected(Test, Protectable) 方法的定义为:
    /**
    	 * @创建时间: 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);
    		}
    	}

    可见 runProtected 方法实际上是调用了刚刚实现的 protect 方法,也就是调用了 test.runBare() 方法。
    public void runBare() throws Throwable
    	{
    		System.out.println("~~~~~~~~~~~~~~~~~~~~~~~");
    		System.out.println("第二步:框架开始运行测试====");
    		Throwable exception = null;
    		setUp();
    		try
    		{
    			runTest();
    		}
    		catch (Throwable running)
    		{
    			exception = running;
    		}
    		finally
    		{
    			try
    			{
    				tearDown();
    				System.out.println("第三步:框架结束运行测试====");
    				System.out.println("~~~~~~~~~~~~~~~~~~~~~~~");
    			}
    			catch (Throwable tearingDown)
    			{
    				if (exception == null)
    					exception = tearingDown;
    			}
    		}
    		if (exception != null)
    			throw exception;
    	}
    
    	/**
    	 * @创建时间: 2016年1月21日
    	 * @相关参数: @throws Exception
    	 * @功能描述: 测试前准备
    	 */
    	protected void setUp() throws Exception
    	{
    		System.out.println("====框架执行默认的setUp====");
    	}
    
    	/**
    	 * @创建时间: 2016年1月21日
    	 * @相关参数: @throws Exception
    	 * @功能描述: 测试后操作
    	 */
    	protected void tearDown() throws Exception
    	{
    		System.out.println("====框架执行默认的tearDown====");
    	}
    
    	protected void runTest() throws Throwable
    	{
    		assertNotNull("测试的方法名不能为空", fName);
    		Method runMethod = null;
    		try
    		{
    			runMethod = getClass().getMethod(fName, (Class[]) null);
    		}
    		catch (NoSuchMethodException e)
    		{
    			fail("Method "" + fName + "" not found");
    		}
    		if (!Modifier.isPublic(runMethod.getModifiers()))
    		{
    			fail("Method "" + fName + "" should be public");
    		}
    
    		try
    		{
    			System.out.println(String.format("框架开始执行测试,执行的方法是-->%s", runMethod));
    			runMethod.invoke(this);
    			System.out.println(String.format("框架结束执行测试,执行的方法是-->%s", runMethod));
    		}
    		catch (InvocationTargetException e)
    		{
    			e.fillInStackTrace();
    			throw e.getTargetException();
    		}
    		catch (IllegalAccessException e)
    		{
    			e.fillInStackTrace();
    			throw e;
    		}
    	}

    在该方法中,最终的测试会传递给一个 runTest 方法执行,注意此处的 runTest 方法是无参的,注意与之前形似的方法区别。该方法中也出现了经典的 setUp 方法和 tearDown 方法,追溯代码可知它们的定义为空。用户可以覆盖两者,进行一些 fixture 的自定义和搭建。 ( 注意:tearDown 放在了 finally{} 中,在测试异常抛出后仍会被执行到,因此它是被保证运行的。 )

    该方法最根本的原理是:利用在前面初始化测试用例时候设定的 fName,借助 Reflection 机制,从 TestCase 中提取测试方法:
    runMethod = getClass().getMethod(fName, (Class[]) null);
    为每一个测试方法,创建一个方法对象 runMethod 并调用:
    runMethod.invoke(this);
    只有在这里,用户测试方法的代码才开始被运行。在测试方法运行时,众多的 Assert 方法会根据测试的实际情况,抛出失败异常或者错误。也是在上行代码这里,这些异常或错误往上逐层抛出,或者被某一层次处理,或者处理后再次抛出,依次递推,最终显示给用户。


    JUnit 执行测试方法,并在测试结束后将失败和错误信息通知所有 test listener。
    OK,至此真个junit的测试执行阶段结束了呢。下一篇整理最后下一个阶段,junit的结果捕获阶段。


  • 相关阅读:
    安卓任意两个或多个Fragment之间的交互与刷新界面
    内存溢出和内存泄漏
    求直方图围成的最大矩形面积
    判断一个字符串是否是由另2个字符串交错组成的
    矩阵的旋转
    求滑动窗口的最大值
    面向过程和面向对象的区别
    关于丑数
    求连续子数组的最大和
    多数投票算法(Majority Vote Algorithm)
  • 原文地址:https://www.cnblogs.com/LinkinPark/p/5232884.html
Copyright © 2020-2023  润新知