我们知道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,看看它的代码:
package
junit.framework;
/**
* 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);
}
/**
* 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界面上:
public
void
startTest(Test test) {
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());
}
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集合内,然后显示在界面上:
private
void
showErrorTrace() {
int index = fFailureList.getSelectedIndex();
if (index == - 1 )
return ;
Throwable t = (Throwable) fExceptions.elementAt(index);
if (fTraceFrame == null ) {
fTraceFrame = new TraceFrame();
fTraceFrame.setLocation( 100 , 100 );
}
fTraceFrame.showTrace(t);
fTraceFrame.setVisible( true );
}
private void showInfo(String message) {
fStatusLine.setFont(PLAIN_FONT);
fStatusLine.setForeground(Color.black);
fStatusLine.setText(message);
}
private void showStatus(String status) {
fStatusLine.setFont(BOLD_FONT);
fStatusLine.setForeground(Color.red);
fStatusLine.setText(status);
}
int index = fFailureList.getSelectedIndex();
if (index == - 1 )
return ;
Throwable t = (Throwable) fExceptions.elementAt(index);
if (fTraceFrame == null ) {
fTraceFrame = new TraceFrame();
fTraceFrame.setLocation( 100 , 100 );
}
fTraceFrame.showTrace(t);
fTraceFrame.setVisible( true );
}
private void showInfo(String message) {
fStatusLine.setFont(PLAIN_FONT);
fStatusLine.setForeground(Color.black);
fStatusLine.setText(message);
}
private void showStatus(String status) {
fStatusLine.setFont(BOLD_FONT);
fStatusLine.setForeground(Color.red);
fStatusLine.setText(status);
}
而Junit中的目标对象(Subject)就是TestResult对象,它有添加观察者的方法:
/**
* Registers a TestListener
*/
public synchronized void addListener(TestListener listener) {
fListeners.addElement(listener);
}
* Registers a TestListener
*/
public synchronized void addListener(TestListener listener) {
fListeners.addElement(listener);
}
而通知观察者又是怎么做的呢?请看这几个方法,都是循环遍历观察者列表,并调用相应的更新方法:
/**
* Adds an error to the list of errors. The passed in exception caused the
* error.
*/
public synchronized void addError(Test test, Throwable t) {
fErrors.addElement( new TestFailure(test, t));
for (Enumeration e = fListeners.elements(); e.hasMoreElements();) {
((TestListener) e.nextElement()).addError(test, t);
}
}
/**
* Adds a failure to the list of failures. The passed in exception caused
* the failure.
*/
public synchronized void addFailure(Test test, AssertionFailedError t) {
fFailures.addElement( new TestFailure(test, t));
for (Enumeration e = fListeners.elements(); e.hasMoreElements();) {
((TestListener) e.nextElement()).addFailure(test, t);
}
}
/**
* Registers a TestListener
*/
public synchronized void addListener(TestListener listener) {
fListeners.addElement(listener);
}
/**
* Informs the result that a test was completed.
*/
public synchronized void endTest(Test test) {
for (Enumeration e = fListeners.elements(); e.hasMoreElements();) {
((TestListener) e.nextElement()).endTest(test);
}
}
* Adds an error to the list of errors. The passed in exception caused the
* error.
*/
public synchronized void addError(Test test, Throwable t) {
fErrors.addElement( new TestFailure(test, t));
for (Enumeration e = fListeners.elements(); e.hasMoreElements();) {
((TestListener) e.nextElement()).addError(test, t);
}
}
/**
* Adds a failure to the list of failures. The passed in exception caused
* the failure.
*/
public synchronized void addFailure(Test test, AssertionFailedError t) {
fFailures.addElement( new TestFailure(test, t));
for (Enumeration e = fListeners.elements(); e.hasMoreElements();) {
((TestListener) e.nextElement()).addFailure(test, t);
}
}
/**
* Registers a TestListener
*/
public synchronized void addListener(TestListener listener) {
fListeners.addElement(listener);
}
/**
* Informs the result that a test was completed.
*/
public synchronized void endTest(Test test) {
for (Enumeration e = fListeners.elements(); e.hasMoreElements();) {
((TestListener) e.nextElement()).endTest(test);
}
}
使用这个模式后带来的好处:
1)上面提到的Subject与Observer的抽象耦合,使JUnit可以支持不同的使用方式
2)支持了广播通信,目标对象不关心有多少对象对自己注册,它只是通知注册的观察者
最后,我实现了一个简单的ConsoleRunner,在控制台执行JUnit,比如我们写了一个简单测试:
package
junit.samples;
import junit.framework. * ;
/**
* Some simple tests.
*
*/
public class SimpleTest extends TestCase {
protected int fValue1;
protected int fValue2;
public SimpleTest(String name) {
super (name);
}
public void setUp() {
fValue1 = 2 ;
fValue2 = 3 ;
}
public void testAdd() {
double result = fValue1 + fValue2;
assert (result == 5 );
}
public void testEquals() {
assertEquals( 12 , 12 );
assertEquals( 12L , 12L );
assertEquals( new Long( 12 ), new Long( 12 ));
assertEquals( " Size " , 12 , 12 );
assertEquals( " Capacity " , 12.0 , 11.99 , 0.01 );
}
}
import junit.framework. * ;
/**
* Some simple tests.
*
*/
public class SimpleTest extends TestCase {
protected int fValue1;
protected int fValue2;
public SimpleTest(String name) {
super (name);
}
public void setUp() {
fValue1 = 2 ;
fValue2 = 3 ;
}
public void testAdd() {
double result = fValue1 + fValue2;
assert (result == 5 );
}
public void testEquals() {
assertEquals( 12 , 12 );
assertEquals( 12L , 12L );
assertEquals( new Long( 12 ), new Long( 12 ));
assertEquals( " Size " , 12 , 12 );
assertEquals( " Capacity " , 12.0 , 11.99 , 0.01 );
}
}
使用ConsoleRunner调用这个测试,代码很简单,不多做解释了:
package
net.rubyeye.junit.framework;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import junit.framework.Test;
import junit.framework.TestListener;
import junit.framework.TestResult;
import junit.samples.SimpleTest;
// 实现观察者接口
public class ConsoleRunner implements TestListener {
private TestResult fTestResult;
private Vector fExceptions;
private Vector fFailedTests;
private List fFailureList;
public ConsoleRunner() {
fExceptions = new Vector();
fFailedTests = new Vector();
fFailureList = new ArrayList();
}
public void endTest(Test test) {
System.out.println( " 测试结束: " );
String message = test.toString();
if (fTestResult.wasSuccessful())
System.out.println(message + " 测试成功! " );
else if (fTestResult.errorCount() == 1 )
System.out.println(message + " had an error " );
else
System.out.println(message + " had a failure " );
for ( int i = 0 ; i < fFailureList.size(); i ++ ) {
System.out.println(fFailureList.get(i));
}
for ( int i = 0 ; i < fFailedTests.size(); i ++ ) {
System.out.println(fFailureList.get(i));
}
for ( int i = 0 ; i < fExceptions.size(); i ++ ) {
System.out.println(fFailureList.get(i));
}
System.out.println( " ------------------------ " );
}
public void startTest(Test test) {
System.out.println( " 开始测试: " + test);
}
public static TestResult createTestResult() {
return new TestResult();
}
private String truncateString(String s, int length) {
if (s.length() > length)
s = s.substring( 0 , length) + " " ;
return s;
}
public void addError(Test test, Throwable t) {
System.out.println(fTestResult.errorCount());
appendFailure( " Error " , test, t);
}
public void addFailure(Test test, Throwable t) {
System.out.println(fTestResult.failureCount());
appendFailure( " Failure " , test, t);
}
private void appendFailure(String kind, Test test, Throwable t) {
kind += " : " + test;
String msg = t.getMessage();
if (msg != null ) {
kind += " : " + truncateString(msg, 100 );
}
fFailureList.add(kind);
fExceptions.addElement(t);
fFailedTests.addElement(test);
}
public void go(String args[]) {
Method[] methods = SimpleTest. class .getDeclaredMethods();
for ( int i = 0 ; i < methods.length; i ++ ) {
// 取所有以test开头的方法
if (methods[i].getName().startsWith( " test " )) {
Test test = new SimpleTest(methods[i].getName());
fTestResult = createTestResult();
fTestResult.addListener(ConsoleRunner. this );
// 执行测试
test.run(fTestResult);
}
}
}
public static void main(String args[]) {
new ConsoleRunner().go(args);
}
}
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import junit.framework.Test;
import junit.framework.TestListener;
import junit.framework.TestResult;
import junit.samples.SimpleTest;
// 实现观察者接口
public class ConsoleRunner implements TestListener {
private TestResult fTestResult;
private Vector fExceptions;
private Vector fFailedTests;
private List fFailureList;
public ConsoleRunner() {
fExceptions = new Vector();
fFailedTests = new Vector();
fFailureList = new ArrayList();
}
public void endTest(Test test) {
System.out.println( " 测试结束: " );
String message = test.toString();
if (fTestResult.wasSuccessful())
System.out.println(message + " 测试成功! " );
else if (fTestResult.errorCount() == 1 )
System.out.println(message + " had an error " );
else
System.out.println(message + " had a failure " );
for ( int i = 0 ; i < fFailureList.size(); i ++ ) {
System.out.println(fFailureList.get(i));
}
for ( int i = 0 ; i < fFailedTests.size(); i ++ ) {
System.out.println(fFailureList.get(i));
}
for ( int i = 0 ; i < fExceptions.size(); i ++ ) {
System.out.println(fFailureList.get(i));
}
System.out.println( " ------------------------ " );
}
public void startTest(Test test) {
System.out.println( " 开始测试: " + test);
}
public static TestResult createTestResult() {
return new TestResult();
}
private String truncateString(String s, int length) {
if (s.length() > length)
s = s.substring( 0 , length) + " " ;
return s;
}
public void addError(Test test, Throwable t) {
System.out.println(fTestResult.errorCount());
appendFailure( " Error " , test, t);
}
public void addFailure(Test test, Throwable t) {
System.out.println(fTestResult.failureCount());
appendFailure( " Failure " , test, t);
}
private void appendFailure(String kind, Test test, Throwable t) {
kind += " : " + test;
String msg = t.getMessage();
if (msg != null ) {
kind += " : " + truncateString(msg, 100 );
}
fFailureList.add(kind);
fExceptions.addElement(t);
fFailedTests.addElement(test);
}
public void go(String args[]) {
Method[] methods = SimpleTest. class .getDeclaredMethods();
for ( int i = 0 ; i < methods.length; i ++ ) {
// 取所有以test开头的方法
if (methods[i].getName().startsWith( " test " )) {
Test test = new SimpleTest(methods[i].getName());
fTestResult = createTestResult();
fTestResult.addListener(ConsoleRunner. this );
// 执行测试
test.run(fTestResult);
}
}
}
public static void main(String args[]) {
new ConsoleRunner().go(args);
}
}