JUnit 4.8 源碼解讀
首先從junit-team/junit4下載源碼。
JUnit的源碼分成了2個大的package,junit和org.junit。junit包實現了基礎的功能。其中junit目錄是JUnit 3.X的內容,org.junit是後來JUnit 4.X新加的。先來講講junit。
程序員在寫完TestCase後,右擊Run As JUnit後,IDE會從TestCase的RunWith註解決定使用哪個Runner來運行TestCase,默認是TestRunner。
public class TestRunner extends BaseTestRunner {
...
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);
}
}
}
在main方法中,先新建一個TestRunner。這裏會將系統輸出作爲參數傳給構造器,生成一個ResultPrinter用於顯示測試結果。
public TestRunner() {
this(System.out);
}
然後執行
TestResult r = aTestRunner.start(args);
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];
}
}
...
}
start方法讀取了命令參數。-wait是會等用戶輸入,-c是指示TestCase的全類名,-m指定了要跑的方法,-v用來輸出當前JUnit的版本,或者直接跟一個類名。
testCase = args[i];
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);
}
獲取到方法名和類名後,如果有方法,則調用runSingleMethod(testCase, method, wait),否則調用getTest(testCase);
public Test getTest(String suiteClassName) {
if (suiteClassName.length() <= 0) {
clearStatus();
return null;
}
Class<?> testClass = null;
try {
// Class.forName(suiteClassName);
testClass = loadSuiteClass(suiteClassName);
} catch (ClassNotFoundException e) {
String clazz = e.getMessage();
if (clazz == null) {
clazz = suiteClassName;
}
runFailed("Class not found \"" + clazz + "\"");
return null;
} catch (Exception e) {
runFailed("Error: " + e.toString());
return null;
}
Method suiteMethod = null;
try {
//獲取 suite 方法
suiteMethod = testClass.getMethod(SUITE_METHODNAME);
} catch (Exception e) {
// try to extract a test suite automatically
clearStatus();
// 如果沒有suite方法,就生成一個TestSuite
return new TestSuite(testClass);
}
// suite方法必須是static
if (!Modifier.isStatic(suiteMethod.getModifiers())) {
runFailed("Suite() method must be static");
return null;
}
Test test = null;
try {
// 調用suite方法,返回方法返回值
test = (Test) suiteMethod.invoke(null); // static method
if (test == null) {
return test;
}
} catch (InvocationTargetException e) {
runFailed("Failed to invoke suite():" + e.getTargetException().toString());
return null;
} catch (IllegalAccessException e) {
runFailed("Failed to invoke suite():" + e.toString());
return null;
}
clearStatus();
return test;
}
通常情況下,我們不會去實現suite方法,所以這裏講新建TestSuite。
初始化TestSuite
public TestSuite(final Class<?> theClass) {
addTestsFromTestCase(theClass);
}
private void addTestsFromTestCase(final Class<?> theClass) {
fName = theClass.getName();
try {
// 先嚐試獲取帶一個String參數的constructor,如果沒有,則獲取無參constructor
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;
}
// TestCase類必須是public的
if (!Modifier.isPublic(theClass.getModifiers())) {
addTest(warning("Class " + theClass.getName() + " is not public"));
return;
}
// 嵌套獲取TestCase類的測試方法
Class<?> superClass = theClass;
List<String> names = new ArrayList<String>();
// 判斷父類是否繼承了Test接口
while (Test.class.isAssignableFrom(superClass)) {
for (Method each : MethodSorter.getDeclaredMethods(superClass)) {
//
addTestMethod(each, names, theClass);
}
superClass = superClass.getSuperclass();
}
if (fTests.size() == 0) {
// 如果沒有滿足的test method,則生成一個fail的測試用例
addTest(warning("No tests found in " + theClass.getName()));
}
}
private void addTestMethod(Method m, List<String> names, Class<?> theClass) {
String name = m.getName();
// 只添加最新的同名方法
if (names.contains(name)) {
return;
}
// isTestMethod(m) && Modifier.isPublic(m.getModifiers());
// 公有方法,且是test method
if (!isPublicTestMethod(m)) {
// 必須滿足三個條件:方法無參,方法名以test開頭,沒有返回值
if (isTestMethod(m)) {
addTest(warning("Test method isn't public: " + m.getName() + "(" + theClass.getCanonicalName() + ")"));
}
return;
}
names.add(name);
addTest(createTest(theClass, name));
}
static public Test createTest(Class<?> theClass, String name) {
Constructor<?> constructor;
try {
constructor = getTestConstructor(theClass);
} catch (NoSuchMethodException e) {
return warning("Class " + theClass.getName() + " has no public constructor TestCase(String name) or TestCase()");
}
Object test;
try {
if (constructor.getParameterTypes().length == 0) {
test = constructor.newInstance(new Object[0]);
if (test instanceof TestCase) {
((TestCase) test).setName(name);
}
} else {
test = constructor.newInstance(new Object[]{name});
}
} catch (InstantiationException e) {
return (warning("Cannot instantiate test case: " + name + " (" + Throwables.getStacktrace(e) + ")"));
} catch (InvocationTargetException e) {
return (warning("Exception in constructor: " + name + " (" + Throwables.getStacktrace(e.getTargetException()) + ")"));
} catch (IllegalAccessException e) {
return (warning("Cannot access test case: " + name + " (" + Throwables.getStacktrace(e) + ")"));
}
return (Test) test;
}
如果是這麼addTest的話,假設一個TestCase下有3個方法,那AddTes就會加3個對象,有點浪費。到此處TestSuite就初始化好了。
運行TestCase
public TestResult start(String[] args) throws Exception {
...
return doRun(suite, wait);
...
}
public TestResult doRun(Test suite, boolean wait) {
// 新建一個TestResult
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;
}
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);
}
運行TestCase
下面是junit.framework.TestCase.run方法,這裏以TestResult調用了run方法。
public void run(TestResult result) {
result.run(this);
}
在TestResult的run方法中,分爲三部分,startTest,runProtected,endTest。
protected void run(final TestCase test) {
startTest(test);
Protectable p = new Protectable() {
public void protect() throws Throwable {
test.runBare();
}
};
runProtected(test, p);
endTest(test);
}
在startTest方法中,記錄了運行的testcase數量,並且調用了TestListener的startTest方法。TestListener有四個接口,分別是addFailure, addError, startTest, endTest。
public void startTest(Test test) {
final int count = test.countTestCases();
synchronized (this) {
fRunTests += count;
}
for (TestListener each : cloneListeners()) {
each.startTest(test);
}
}
runProtected方法,就是調用Protected接口裏的protect方法。其實就是上面的test.runBare()。AssertionFailError就是Assert失敗拋出的異常。
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方法,定義了運行測試方法的順序,首先是setUp,然後是runTest,最後是tearDown。因爲tearDown是在finally中,所以tearDown是一定會運行的。
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的runTest方法運用java的反射,調用了測試方法。並對
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;
}
}
在測試用例跑完後,TestListener的endTest會被觸發。
public void endTest(Test test) {
for (TestListener each : cloneListeners()) {
each.endTest(test);
}
}
最後TestRunner會調用fPrinter的print方法打印出結果。
參考文檔:
1.分析 JUnit 框架源代碼