JUnit 4.8 源码解读
来源:互联网 发布:指南针炒股软件诈骗: 编辑:程序博客网 时间:2024/05/15 23:52
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 框架源代码
- JUnit 4.8 源码解读
- JUnit 4.8 源码解读2
- 【Junit源码解读】--概括
- 源码解读
- JUnit源码分析
- Junit源码分析
- junit源码解析总结
- Junit源码解析总结
- JUnit源码分析
- junit源码分析
- JUnit源码分析
- 源码解读之Intent解读
- 解读Junit的@Test注解,避免initializationerror
- [源码解读] FastClick.js源码解读
- CppUnit源码解读(1)
- CppUnit源码解读(2)
- CppUnit源码解读(3)
- CppUnit源码解读(4)
- hdu2824(the Euler Function)
- 基于邻域的协同过滤算法(一)
- Lock、Condition
- 如何设计好的RESTful API之安全性
- ulua与unity互传数组
- JUnit 4.8 源码解读
- React Native入门教程 3 -- Flex布局
- 基于邻域的协同过滤算法(二)
- MSDB数据库置疑的解决方法
- CentOS7源码包安装apache
- xpath文档详解
- 人工智能的突破需要颠覆图灵机吗?
- 深入浅出 RPC - 深入篇
- 基于回溯法的0-1背包问题