Junit是非常短小精悍的单元测试框架,里面用到了大量的设计模式和设计原则,当然本文不是去分析这些模式,只是从头看代码分析一下它的执行过程:
1.Junit38ClassRunner的构造方法
public JUnit38ClassRunner(Class<?> klass) {
this(new TestSuite(klass.asSubclass(TestCase.class)));
}
2.TestSuite的构造方法
public TestSuite(final Class<?> theClass) {
addTestsFromTestCase(theClass);
}
下面看一下addTestsFromTestCase(theClass)方法的源码
private void addTestsFromTestCase(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;
List<String> names= new ArrayList<String>();
while (Test.class.isAssignableFrom(superClass)) {
for (Method each : superClass.getDeclaredMethods())
addTestMethod(each, names, theClass);
superClass= superClass.getSuperclass();
}
if (fTests.size() == 0)
addTest(warning("No tests found in "+theClass.getName()));
}
然后我们再来看看addTestMethod方法
private void addTestMethod(Method m, List<String> 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() + "(" + theClass.getCanonicalName() + ")"));
return;
}
names.add(name);
addTest(createTest(theClass, name));
}
isTestMethod(Method m)方法判断该方法是否为测试方法,即以test开头或者有相关注解的方法
addTest(createTest(theClass, name));这里将创建出TestCase的实例,并添加到fTests(Vector)中
这里创建的出的TestCase的fname属性将保存实际要测试的方法名
3.TestSuite的createTest方法
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+" ("+exceptionToString(e)+")"));
} catch (InvocationTargetException e) {
return(warning("Exception in constructor: "+name+" ("+exceptionToString(e.getTargetException())+")"));
} catch (IllegalAccessException e) {
return(warning("Cannot access test case: "+name+" ("+exceptionToString(e)+")"));
}
return (Test) test;
}
下面我们进入Test的执行过程:
4.Junit38ClassRunner的run方法
public void run(RunNotifier notifier) {
TestResult result= new TestResult();
result.addListener(createAdaptingListener(notifier));
getTest().run(result);
}
这里getTest()的得到的是TestSuite,上面的代码我们已经知道TestSuite中包含了多个TestCase实例,当然这里说的比较局限TestSuite也可以包含其他的TestSuite去执行一组测试,它们都保存在fTests中,这其实是组合模式,那接着我们就应该看看TestSuite的run方法
5.TestSuite的run方法
public void run(TestResult result) {
for (Test each : fTests) {
if (result.shouldStop() )
break;
runTest(each, result);
}
}
不出所料,在这里是遍历fTests去执行每一个TestCase实例的run方法,那我们就继续去看TestCase的run方法
6.TestCase的run方法
public void run(TestResult result) {
result.run(this);
}
这里TestCase把自己作为参数传给TestResult的run方法去执行,这里为什么要这样做呢,为什么不直接执行呢?这个问题先留在这里,接着看
7.TestResult的run方法
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、endTest方法无非就是做一些初始化和销毁的工作,就不进入每个方法去看了,TestResult顾名思义是用来收集测试结果的,它在Runner、TestSuite、TestCase之间一直传递,保存所有测试方法的执行结果
这里创建了一个受保护的Protectable类,在里面执行TestCase的runBare方法,runProtected()会去调用p.protect()方法.这说明测试方法实际上还是在TestCase中执行的,那为什么要传给TestResult,再在TestResult类中调用TestCase的runBare方法呢,我们来看一下runProtected()的具体过程吧
8.TestResult的runProtected方法
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);
}
}
很明显,这个过程是为了收集执行结果的,如果catch到AssertionFailureError表示测试结果不正确,如果catch到其他异常表示测试出现错误,addFailure和addError方法其实就是往TestResult的fFailures和fErrors列表中添加对象,并通知相应的观察者。
我们回到主过程中,来接着看测试方法是如何被调用的:
9.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;
}
这里我们就会发现为什么junit在对每个方法进行测试时都要先后执行setUp()、实际要测试的方法和tearDown()方法,这里用的模板方法模式,setUp和tearDown如果在测试类中有进行复写,那实际执行的是测试类中的setUp和tearDown,而且测试每个类的前后都会执行这两个方法,而实际执行要测试的方法是在runTest()中实现的,下面就来看看runTest()方法吧
10.TestCase的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;
}
}
这里就可以发现Junit实际上就是把我们要测试的方法反射出来去invoke,以上这些就是Junit进行测试的全过程了,下面我们再来看分析前面的过程中遗留的问题:
为什么在TestCase的run方法中不直接执行测试方法,而要将自身传给TestResult的run方法中去执行?
这个问题通过后面的执行过程,大家应该也看明白了,这其实是Junit的设计上非常值得我们学习的地方,就是类的单一职责特点,TestCase里的方法应该都是测试相关的,而TestResult里的方法都是为了结果服务的,这个交织的过程就是为了获取测试结果
分享到:
相关推荐
**Junit源码分析(圣思园)** Junit是Java编程语言中最广泛使用的单元测试框架,它使得开发者能够方便地编写和运行可重复的、可靠的测试用例。本篇文章将深入探讨Junit的源码,揭示其内部工作原理,帮助我们更好地...
自定义JUnit源码是一个对Java开发人员非常有帮助的主题,特别是对于那些想要深入理解测试框架工作原理或希望根据自身需求定制测试工具的开发者。JUnit是一个广泛使用的单元测试框架,它简化了编写和运行针对Java代码...
4. Junit源码分析:深入到Junit的源码层面,解释其核心组件和测试执行流程,为自动生成测试代码提供理论基础。 5. 实现策略:描述如何设计和实现一个自动化测试代码生成的系统,可能包括解析源代码结构,识别测试点...
【标题】"junit4 单元测试源码"涉及的是Java编程中单元测试的重要工具...通过分析和运行这些源码,学习者可以掌握单元测试的基本概念,了解如何编写有效的测试用例,以及如何利用Eclipse的集成环境进行测试驱动开发。
10. **持续集成**:JUnit源码也揭示了如何将测试集成到持续集成(CI)系统,如Jenkins、Travis CI等,确保每次代码变更后都能自动运行测试并获取反馈。 总的来说,分析《JUnit in Action》的源码,不仅可以帮助我们...
它能够从业务分析人员定义好的CVS或 Excel文件读取测试用例数据并在构建/单元测试框架中报告测试成功。利用Feed4JUnit能够很方便用随机但校验过的数据执行冒烟测试来提高代码 代码覆盖率和发现由非常特殊的数据结构...
本资源"Junit设计模式分析(带源码)"旨在深入探讨JUnit在设计上的模式和最佳实践,通过源码分析帮助开发者更好地理解和应用这个工具。 1. 单元测试基础: 单元测试是对软件中的最小可测试单元进行检查,如函数、...
2. **JUnit框架**:JUnit源码在NetBeans中的实现意味着我们可以看到如何在IDE内部封装和扩展JUnit的API。这可能涉及到对`@Test`注解的处理,以及如何触发测试执行和显示测试结果。 3. **事件监听和API调用**:...
通过分析和运行这些测试用例,我们可以了解如何编写有效的JUnit4测试,以及如何利用JUnit4提供的各种工具和特性来提高测试覆盖率和质量。 总之,理解和掌握JUnit4的源码对于Java开发者来说是至关重要的,它能帮助...
Java JUnit 源码分析 Java JUnit 是一个广泛使用的单元测试框架,它使得 Java 开发者能够方便地编写和执行针对代码功能的测试。JUnit 的核心在于它提供了断言(assertions)来验证代码行为,以及测试套件(test ...
同时,源码分析也有助于学习最佳实践和设计模式,提升自身的编程技能。 JUnit4的主要特性包括: 1. **注解驱动**:通过注解可以轻松地标识测试方法,如@Test表示测试方法,@Before和@After分别用于在每个测试方法...
SpringBoot-junit项目源码分析 SpringBoot是一个流行的Java框架,用于快速开发微服务和Web应用程序。它简化了Spring框架的配置,使得开发者可以更快地启动项目。JUnit是Java编程语言中最常用的单元测试框架,它使得...
本篇将深入分析JUnit源码中的设计模式,帮助你理解其内在的架构原理,提升你的编程技能。 首先,JUnit的核心设计原则之一是“开闭原则”(Open-Closed Principle),它主张软件实体(类、模块、函数等)应对于扩展...
通过阅读和分析肯特参与编写的这部分源代码,我们可以更深入地了解JUnit的内部工作原理,学习如何设计和实现测试框架,以及如何通过注解和运行器优化测试流程。这对于理解单元测试的底层机制,提升测试效率,甚至...
JavaJUnit Jenkins 源码分析 在 Java 开发过程中,单元测试是保证代码质量的重要环节,JUnit 是一个广泛使用的 Java 单元测试框架。而 Jenkins 是一个流行的持续集成(CI)工具,它可以帮助开发者自动化构建、测试...
【JUnit 框架详解】 JUnit 是一个广泛使用的 Java 编程语言的单元测试框架,由 Erich Gamma 和 Kent Beck 开发,它是 xUnit 家族的...结合源码分析,将帮助你更好地理解和应用这些概念,提升你的编程技能和代码质量。
6. **源码分析**:压缩包中的"testAntJunit"文件可能是包含了一个示例项目,这个项目演示了如何在Eclipse中设置ANT脚本和JUnit测试。通过查看源码,我们可以学习如何在代码中编写测试用例,以及如何在ANT构建文件中...
JUnit是Java开发者进行单元测试的重要工具,由著名程序员Erich Gamma和Kent Beck共同创建,它遵循简洁、可扩展的原则,使得测试代码易于编写和维护。本文将深入探讨JUnit中的设计模式,以及如何通过理解这些模式来...
9. **源码分析**:可能深入到JUnit的源代码,解释其设计模式,如观察者模式、装饰器模式,以及JUnit如何处理测试失败和测试报告。 10. **实战示例**:结合实际项目,展示如何利用JUnit进行单元测试,解决实际问题。...