JUnit源码分析(一)——Command模式和Composite模式
JUnit的源码相比于spring和hibernate来说比较简单,但麻雀虽小,五脏俱全,其中
用到了比较多的设计模式。很多人已经在网上分享了他们对JUnit源码解读心得,我这
篇小文谈不出什么新意,本来不打算写,可最近工作上暂时无事可做,那就写写吧,
结合《设计模式》来看看。
我读的是JUnit3.0的源码,目前JUnit已经发布到4.0版本了,尽管有比较大的改进,但
基本的骨架不变,读3.0是为了抓住重点,省去对旁支末节的关注。我们来看看JUnit的
核心代码,也就是Junit.framework包,除了4个辅助类
(Assert,AssertFailedError,Protectable,TestFailure),剩下的就是我们需要重点
关注的了。我先展示一张UML类图:
我们先不去关注TestDecorator类(此处是Decorator模式,下篇文章再讲),看看Test接
口,以及它的两个实现类TestCase和TestSuite。很明显,此处用到了Command模式,为
什么要使用这个模式呢?让我们先来看看什么是Command模式。
Command模式
Command模式是行为型模式之一
1.意图:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数
化;对请求排队或者记录请求日志,以及支持可撤销的操作。
2.适用场景:
1)抽象出待执行的动作以参数化对象,Command模式是回调函数的面向对象版本。回
调函数,我想大家都明白,函数在某处注册,然后在稍后的某个时候被调用。
2)可以在不同的时刻指定、排列和执行请求。
3)支持修改日志,当系统崩溃时,这些修改可以被重做一遍。
4)通过Command模式,你可以通过一个公共接口调用所有的事务,并且也易于添加新的事务。
3。UML图:
4.效果:
1)命令模式将调用操作的对象与如何实现该操作的对象解耦。
2)将命令当成一个头等对象,它们可以像一般对象那样进行操纵和扩展
3)可以将多个命令复合成一个命令,与Composite模式结合使用
4)增加新的命令很容易,隔离对现有类的影响
5)可以与备忘录模式配合,实现撤销功能。
在了解了Command模式之后,那我们来看JUnit的源码,Test接口就是命令的抽象
接口,而TestCase和TestSuite是具体的命令
//抽象命令接口
package junit.framework;
/**
* A <em>Test</em> can be run and collect its results.
*
* @see TestResult
*/
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);
}
//具体命令一
public abstract class TestCase extends Assert implements Test {
/**
* the name of the test case
*/
private final String fName;
/**
//具体命令二
public class TestSuite implements Test {
由此带来的好处:
1.客户无需使用任何条件语句去判断测试的类型,可以用统一的方式调用测试和测试
套件,解除了客户与具体测试子类的耦合
2.如果要增加新的TestCase也很容易,实现Test接口即可,不会影响到其他类。
3.很明显,TestSuite是通过组合多个TestCase的复合命令,这里使用到了Composite模式(组合)
4.尽管未实现redo和undo操作,但将来也很容易加入并实现。
我们上面说到TestSuite组合了多个TestCase,应用到了Composite模式,那什么是
Composite模式呢?具体来了解下。
Composite模式
composite模式是对象结构型模式之一。
1.意图:将对象组合成树形结构以表示“部分——整体”的层次结构。使得用户对单个对象和
组合结构的使用具有一致性。
2.适用场景:
1)想表示对象的部分-整体层次
2)希望用户能够统一地使用组合结构和单个对象。具体到JUnit源码,我们是希望用户能够
统一地方式使用TestCase和TestSuite
3.UML图:
图中单个对象就是树叶(Leaf),而组合结构就是Compoiste,它维护了一个Leaf的集合。
而Component是一个抽象角色,给出了共有接口和默认行为,也就是JUnit源码中的Test接口。
4.效果:
1)定义了基本对象和组合对象的类层次结构,通过递归可以产生更复杂的组合对象
2)简化了客户代码,客户可以使用一致的方式对待单个对象和组合结构
3)添加新的组件变的很容易。但这个会带来一个问题,你无法限制组件中的组件,只能靠
运行时的检查来施加必要的约束条件
具体到JUnit源码,单个对象就是TestCase,而复合结构就是TestSuite,Test是抽象角
色只有一个run方法。TestSuite维护了一个TestCase对象的集合fTests:
private Vector fTests= new Vector(10);
/**
* Adds a test to the suite.
*/
public void addTest(Test test) {
fTests.addElement(test);
}
/**
* Runs the tests and collects their result in a TestResult.
*/
public void run(TestResult result) {
for (Enumeration e= tests(); e.hasMoreElements(); ) {
if (result.shouldStop() )
break;
Test test= (Test)e.nextElement();
test.run(result);
}
}
当执行run方法时遍历这个集合,调用里面每个TestCase对象的run()方法,从而执行测试。
我们使用的时候仅仅需要把TestCase添加到集合内,然后用一致的方式(run方法)调用他们进行测试。
考虑使用Composite模式之后带来的好处:
1)JUnit可以统一地处理组合结构TestSuite和单个对象TestCase,避免了条件判断,并且
可以递归产生更复杂的测试对象
2)很容易增加新的TestCase。
JUnit源码分析(二)——观察者模式 - 庄周梦蝶时间:2008-10-21 23:33来源:信息化中国
作者: 点击:7次 收藏 挑错 推荐 打印
我们知道JUnit支持不同的使用方式:swt、swing的UI方式,甚至控制台方式,那么对于
这些不同的UI我们如何提供统一的接口供它们获取测试过程的信息(比如出 ...
我们知道JUnit支持不同的使用方式:swt、swing的UI方式,甚至控制台方式,那么对于
这些不同的UI我们如何提供统一的接口供它们获取测试过程的信息(比如出现的异常信息,
测试成功,测试失败的代码行数等等)?我们试想一下这个场景,当一个error或者exception产
生的时候,测试能够马上通知这些UI客户端:发生错误了,发生了什么错误,错误是什么等等。
显而易见,这是一个订阅-发布机制应用的场景,应当使用观察者模式。那么什么是观察者模式呢?
观察者模式(Observer)
Observer是对象行为型模式之一
1.意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发现改变时,所有依赖于它
的对象都得到通知并被自动更新
2.适用场景:
1)当一个抽象模型有两个方面,其中一个方面依赖于另一个方面,通过观察者模式将这两者
封装在不同的独立对象当中,以使它们可以独立的变化和复用
2)当一个对象改变时,需要同时改变其他对象,并且不知道其他对象的具体数目
3)当一个对象需要引用其他对象,但是你又不想让这个对象与其他对象产生紧耦合的时候
3.UML图:
Subject及其子类维护一个观察者列表,当需要通知所有的Observer对象时调用Nitify方法
遍历Observer集合,并调用它们的update方法更新。而具体的观察者实现Observer接口
(或者抽象类),提供具体的更新行为。其实看这张图,与Bridge有几分相似,当然两
者的意图和适用场景不同。
4.效果:
1)目标和观察者的抽象耦合,目标仅仅与抽象层次的简单接口Observer松耦合,而没
有与具体的观察者紧耦合
2)支持广播通信
3)缺点是可能导致意外的更新,因为一个观察者并不知道其他观察者,它的更新行为
也许将导致一连串不可预测的更新的行为
5.对于观察者实现需要注意的几个问题:
1)谁来触发更新?最好是由Subject通知观察者更新,而不是客户,因为客户可能忘记调用Notify
2)可以通过显式传参来指定感兴趣的更新
3)在发出通知前,确保Subject对象状态的一致性,也就是Notify操作应该在最后被调用
4)当Subject和Observer的依赖关系比较复杂的时候,可以通过一个更新管理器来管理
它们之间的关系,这是与中介者模式的结合应用。
讨论完观察者模式,那我们来看JUnit是怎么实现这个模式的。在junit.framework包中
我们看到了一个Observer接口——TestListener,看看它的代码:
/**
* A Listener for test progress
*/
public interface TestListener {
/**
* An error occurred.
*/
public void addError(Test test, Throwable t);
/**
* A failure occurred.
*/
public void addFailure(Test test, Throwable t);
/**
* A test ended.
*/
public void endTest(Test test);
/**
* A test started.
*/
public void startTest(Test test);
}
接口清晰易懂,就是一系列将测试过程的信息传递给观察者的操作。具体的子类
将接受这些信息,并按照它们的方式显示给用户。
比如,我们看看swing的UI中的TestRunner,它将这些信息显示在一个swing写的UI界面上:
showInfo("Running: " test);
}
public void addError(Test test, Throwable t) {
fNumberOfErrors.setText(Integer.toString(fTestResult.errorCount()));
appendFailure("Error", test, t);
}
public void addFailure(Test test, Throwable t) {
fNumberOfFailures.setText(Integer.toString(fTestResult.failureCount()));
appendFailure("Failure", test, t);
}
public void endTest(Test test) {
setLabelValue(fNumberOfRuns, fTestResult.runCount());
fProgressIndicator.step(fTestResult.wasSuccessful());
}
可以看到,它将错误信息,异常信息保存在List或者Vector集合内,然后显示在界面上:
int index= fFailureList.getSelectedIndex();
if (index == -1)
return;
Throwable t= (Throwable) fExceptions.elementAt(index);
if (fTraceFrame == null) {
fTraceFrame= new TraceFrame();
fTraceFrame.setLocation(100, 100);
JUnit源码分析 (三)——Template Method模式
在JUnit执行测试时,我们经常需要初始化一些环境供测试代码使用,比如数据
库连接、mock对象等等,这些初始化代码应当在每一个测试之前执行并在测试方
法运行后清理。在JUnit里面就是相应的setUp和tearDown方法。如果没有这两个
方法,那么我们要在每个测试方法的代码内写上一大堆重复的初始化和清理代码,这
是多么愚蠢的做法。那么JUnit是怎么让setUp和tearDown在测试执行前后被调用的呢?
如果你查看下TestCase方法,你会发现TestCase和TestSuite的run()方法都是将执行
测试的任务委托给了TestResult,由TestResult去执行测试代码并收集测试过程中的
信息(这里用到了Collecting Parameter模式)。
public TestResult run() {
TestResult result= createResult();
run(result);
return result;
}
/**
* Runs the test case and collects the results in TestResult.
* This is the template method that defines the control flow
* for running a test case.
*/
public void run(TestResult result) {
result.run(this);
}
我们直接找到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);
}
这里实例化了一个内部类,内部类实现了Protectable接口的 protect()方法,并执
行传入的TestCase的runBare()方法,显然,真正的测试代码在TestCase的runBare()方
法中,让我们来看下:
//将被子类实现
protected void setUp() throws Throwable {
}
//同上,将被具体的TestCase实现
protected void tearDown() throws Throwable {
}
/**
* 模板方法
* Runs the bare test sequence.
* @exception Throwable if any exception is thrown
*/
public void runBare() throws Throwable {
setUp();
try {
runTest();
}
finally {
tearDown();
}
}
真相水落石出,对于每一个测试方法,都遵循这样的模板:setUp->执行测
试 runTest()->tearDown。这正是模板方式模式的一个应用例子。什么是template method模式呢?
Template Method模式
类行为模式的一种
1.意图:定义一个操作中的算法的骨架,而将一些延迟步骤到子类中。Template Method使得子
类可以不改变一个算法的结构即可重定义该算法的某些步骤。
2.适用场景:
1)一次性实现算法的不变部分(基本骨架),将可变的行为留给子类来完成
2)子类中的公共部分(比如JUnit中的初始化和清理)被抽取到一个公共父类中以避免代码重复。
3)控制了子类的扩展,这里其实也有类似回调函数的性质,具体步骤先在骨架中注册,在具体执行时被回调。
3.UML图和结构
抽象父类定义了算法的基本骨架(模板方法),而不同的子类实现具体的算法步骤,客户
端由此可以与算法的更改隔离。
4.效果:
1)模板方法是代码复用的基本技术,在类库中经常使用,可以减少大量的代码重复
2)通过隔离算法的不变和可变部分,增加了系统的灵活性,扩展算法的某些步骤将变的很容易。
了解了Template Method模式之后,让我们回到JUnit的源码,看看runTest()方法,这里主要
应用的是java的反射技术,对于学习反射技术的有参考价值:
protected void runTest() throws Throwable {
Method runMethod= null;
try {
runMethod= getClass().getDeclaredMethod(fName, new Class[0]);
} catch (NoSuchMethodException e) {
fail("Method \""+fName+"\" not found");
}
if (runMethod != null && !Modifier.isPublic(runMethod.getModifiers())) {
fail("Method \""+fName+"\" should be public");
}
try {
runMethod.invoke(this, new Class[0]);
}
catch (InvocationTargetException e) {
e.fillInStackTrace();
throw e.getTargetException();
}
catch (IllegalAccessException e) {
e.fillInStackTrace();
throw e;
}
}
标签:分析,模式,public,TestCase,Test,源码,test,junit,JUnit
From: https://blog.51cto.com/u_16087012/6227115