大家好,这些天在一位前辈的建议下,开始阅读Junit源码,当然这个过程中,也有在参考他人的经验,有兴趣的朋友可以去看一下下面这位前辈的博文,相信可以给各位一些收获:JUNIT源码分析,下面和大家分享一些博主自己在阅读源码时的浅显心得,希望各位多多指教,不当之处,还望不吝赐教。
首先我们来依次说明几个Junit中的核心的类,分别为以下几个类和接口:
public interface Test
public abstract class TestCase extends Assert implements Test
public class TestSuite implements Test
以下是三个类的继承关系:
可以看到Test是作为接口存在,然后TestSuite与TestCase分别实现了Test接口,其中有两个方法:
public interface Test {
/**
* Counts the number of test cases that will be run by this test.
*/
public abstract int countTestCases();
/**
* Runs a test and collects its result in a TestResult instance.
*/
public abstract void run(TestResult result);
}
其中方法countTestCases()可以获取到TestCase的数量,而run()方法则是运行Case,这里的TestCase可以简单理解为一个测试用例。
然后我们可以看到TestCase类还实现了Assert类,这样就可以获得一点优势,就是Assert类中的方法对于Case来说是完全透明的,使用TestCase的地方可以直接调用这些方法。
那这样的继承关系有什么好处呢?
首先我们来解释一下,这种继承关系在设计模式中叫做组合模式,TestSuite中可以组合多个TestCase用于测试:
public TestSuite(Class<? extends TestCase>[] classes, String name) {
this(classes);
setName(name);
}
同样我们也可以通过添加Test实例组合TestSuite:
// Cannot convert this to List because it is used directly by some test runners
private Vector<Test> fTests= new Vector<Test>(10);
public void addTest(Test test) {
fTests.add(test);
}
也就是说只要实现了Test接口的实现类,都可以组合到TestSuite中,当然,这其中也包括TestSuite自身,这样就形成了一个树状形式,在运行测试时,就可以逐层进行。
然后我们再来介绍一个类:
public class TestResult extends Object
TestResult中封装了测试运行结果,并作为返回结果,返回给Client,我们看一下其属性:
//测试失败
protected List<TestFailure> fFailures;
//测试错误
protected List<TestFailure> fErrors;
//监听器
protected List<TestListener> fListeners;
上面我们介绍了封装测试用例的TestCase和TestSuite以及其接口Test,并且介绍了封装测试结果的TestResult,那么有了结果,有了被执行对象,还缺少的就是测试执行者:
public class TestRunner extends BaseTestRunner
TestRunner就是测试执行者,我们在执行单元测试时,往往看到这样的注解:
@RunWith(SpringJUnit4ClassRunner.class)
这里就是指定测试执行的Runner,当然我们也可以不指定,那么默认的Runner就是:
public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod>
好了,我们接下来看一下TestRunner是怎么执行Test的:
static public TestResult run(Test test) {
TestRunner runner= new TestRunner();
return runner.doRun(test);
}
public TestResult doRun(Test test) {
return doRun(test, false);
}
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;
}
我们可以看到首先创建了TestRunner,然后使用Runner运行Test,首先创建了记录测试结果的TestResult对象,并向result中注册监听者,然后调用Test的run()方法进行测试,这个过程中涉及到另外的一个设计模式:观察者模式:
在这里我们向result中注册了我们的监听器,也就是对于Runner来说,扮演了观察者的身份,而result扮演了被观察者的身份,当result的状态变化时,可以通过观察媒介:监听器对Runner进行通知,或者说Runner通过监听器,监听到了result的变化,从而做出响应。
那么在这里的每个Test是怎么运行的呢?由于实现Test接口的类为TestSuite和TestCase,所以我们来看一下这两个类:
在TestSuite中:
public void run(TestResult result) {
for (Test each : fTests) {
if (result.shouldStop() )
break;
runTest(each, result);
}
}
我们可以看到,在TestSuite中依次调用了各个测试用例的封装来进行测试,也就是之前写到的树状结构,如果each为TestCase则调用TestCase,如果为TestSuite,则调用TestSuite,这样依次调用,直至全部调用完成。
接下来看一下TestCase:
/**
* Runs the test case and collects the results in TestResult.
*/
public void run(TestResult result) {
result.run(this);
}
/**
* Runs the bare test sequence.
* @throws Throwable if any exception is thrown
*/
public void runBare() throws Throwable {
Throwable exception= null;
setUp();
try {
runTest();
} catch (Throwable running) {
exception= running;
}
finally {
try {
tearDown();
} catch (Throwable tearingDown) {
if (exception == null) exception= tearingDown;
}
}
if (exception != null) throw exception;
}
这里涉及到TestResult中的run方法:
/**
* Runs a TestCase.
*/
protected void run(final TestCase test) {
startTest(test);
Protectable p= new Protectable() {
public void protect() throws Throwable {
test.runBare();
}
};
runProtected(test, p);
endTest(test);
}
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);
}
}
我们看到在TestCase中,调用TestResult的run()方法,在TestResult中,我们注册了回调方法,也就是TestResult最终还是回调回了TestCase中的runBare()方法,同时我们看到在TestResult中的runProtected方法中,对各种类型的Throwable进行了捕获,也就是说当发生Throwable时,就会把对应的Throwable添加进我们前面说的三个List属性中。
而在TestCase中,真正执行Test的是runTest()方法:
protected void runTest() throws Throwable {
assertNotNull("TestCase.fName cannot be null", fName); // Some VMs crash when calling getMethod(null,null);
Method runMethod= null;
try {
// use getMethod to get all public inherited
// methods. getDeclaredMethods returns all
// methods of this class but excludes the
// inherited ones.
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 {
runMethod.invoke(this);
}
catch (InvocationTargetException e) {
e.fillInStackTrace();
throw e.getTargetException();
}
catch (IllegalAccessException e) {
e.fillInStackTrace();
throw e;
}
}
在这里运用反射,根据存储的方法名称:fName找到要执行的方法,并予以执行,执行完毕后由TestResult做后续处理:endTest()方法:
public void endTest(Test test) {
for (TestListener each : cloneListeners())
each.endTest(test);
}
在这里我们遍历了所有的监听器,并告知执行已完毕。然后TestCase返回TestResult。
好了,暂时就和大家分享这些,如有不足之处,还请多多指教。