ivaneye.com

读源码-JUnit3实现

内容回顾

本篇文章将梳理JUnit3源码

代码结构

上篇最后给出了测试UML图

我们可以看出

Assert类

我们先看Assert这个类,你会发现这个类很简单,提供了各种静态的assert*()方法来进行比较。

Test接口

package junit.framework;
public interface Test {
    public abstract int countTestCases();
    public abstract void run(TestResult result);
}

Test接口有两个方法

TeseCase类

TestCase实现了Test接口,那么肯定需要实现如上两个方法

public abstract class TestCase extends Assert implements Test {
  ...
  public int countTestCases() {
        return 1;
    }
  public void run(TestResult result) {
        result.run(this);
    }
  ...
}

那么问题来了?为什么要传入一个TestResult,然后再将自身传递给TestResult去执行呢?下篇分析!

TestResult类

TestResult类看名字也能才出来,是用来收集测试结果的类.

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.runBare()

我们再次回到了TestCase方法,看看runBare()方法

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

我们分别来看看这三个方法在TestCase中的实现:

protected void setUp() throws Exception {
}
protected void tearDown() throws Exception {
}

上面两个方法主要就是给子类覆盖的!

protected void runTest() throws Throwable {
  assertNotNull(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, (Object[])new Class[0]);
  } catch (InvocationTargetException e) {
    e.fillInStackTrace();
    throw e.getTargetException();
  } catch (IllegalAccessException e) {
    e.fillInStackTrace();
    throw e;
  }
}

这里就是通过反射来获取方法进而执行!

入口

如上的代码,只是测试结构代码。如何执行上面的测试代码呢?必然有个main方法啊!

在上篇中通过命令行执行

java junit.textui.TestRunner org.ivan.TestAll

可以看到,入口类为TestRunner

TestRunner类

public static void main(String args[]) {
  TestRunner aTestRunner= new TestRunner();
  try {
    TestResult r= aTestRunner.start(args);
    if (!r.wasSuccessful())
    System.exit(FAILURE_EXIT);
    System.exit(SUCCESS_EXIT);
  } catch(Exception e) {
    System.err.println(e.getMessage());
    System.exit(EXCEPTION_EXIT);
  }
}
public TestResult start(String args[]) throws Exception {
  String testCase= "";
  String method= "";
  boolean wait= false;
  for (int i= 0; i < args.length; i++) {
    if (args[i].equals("-wait"))
    wait= true;
    else if (args[i].equals("-c"))
    testCase= extractClassName(args[++i]);
    else if (args[i].equals("-m")) {
    String arg= args[++i];
    int lastIndex= arg.lastIndexOf('.');
    testCase= arg.substring(0, lastIndex);
    method= arg.substring(lastIndex + 1);
  } else if (args[i].equals("-v"))
    System.err.println("JUnit " + Version.id() + " by Kent Beck and Erich Gamma");
    else
    testCase= args[i];
  }
  if (testCase.equals(""))
    throw new Exception("Usage: TestRunner [-wait] testCaseName, where name is the name of the TestCase class");
  try {
    if (!method.equals(""))
    return runSingleMethod(testCase, method, wait);
    Test suite= getTest(testCase);
    return doRun(suite, wait);
  } catch (Exception e) {
    throw new Exception("Could not create and run test suite: " + e);
  }
}
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;
}

TestSuite

public void run(TestResult result) {
for (Enumeration e= tests(); e.hasMoreElements(); ) {
  if (result.shouldStop() )
    break;
    Test test= (Test)e.nextElement();
    runTest(test, result);
  }
}
public void runTest(Test test, TestResult result) {
  test.run(result);
}

那Test是如何添加到TestSuite中的呢?这个动作是在实例化TestSuite时进行的!

public TestSuite(final Class theClass) {
  fName= theClass.getName();
  try {
    getTestConstructor(theClass); // Avoid generating multiple error messages
  } catch (NoSuchMethodException e) {
    addTest(warning("Class "+theClass.getName()+" has no public constructor TestCase(String name) or TestCase()"));
    return;
  }
  if (!Modifier.isPublic(theClass.getModifiers())) {
    addTest(warning("Class "+theClass.getName()+" is not public"));
    return;
  }
  Class superClass= theClass;
  Vector names= new Vector();
  while (Test.class.isAssignableFrom(superClass)) {
    Method[] methods= superClass.getDeclaredMethods();
    for (int i= 0; i < methods.length; i++) {
      addTestMethod(methods[i], names, theClass);
    }
    superClass= superClass.getSuperclass();
  }
  if (fTests.size() == 0)
  addTest(warning("No tests found in "+theClass.getName()));
}
private void addTestMethod(Method m, Vector names, Class theClass) {
  String name= m.getName();
  if (names.contains(name))
    return;
  if (!isPublicTestMethod(m)) {
    if (isTestMethod(m))
      addTest(warning("Test method isn't public: "+m.getName()));
    return;
  }
  names.addElement(name);
  addTest(createTest(theClass, name));
}
private boolean isTestMethod(Method m) {
    String name= m.getName();
    Class[] parameters= m.getParameterTypes();
    Class returnType= m.getReturnType();
    return parameters.length == 0 && name.startsWith("test") && returnType.equals(Void.TYPE);
}

这就是整个JUnit的执行流程!

时序图

UML