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源码解析--测试驱动运行阶段



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


上一篇:Unit Testing a zend-mvc application


下一篇:jquery 修改 bootstrap模态框的宽度并且居中