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的结果捕获阶段。


转载于:https://www.cnblogs.com/LinkinPark/p/5232884.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值