JUnit原始碼分析(二)——觀察者模式
我們知道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);
}
}
相關文章
- 設計模式(十八)——觀察者模式(JDK Observable原始碼分析)設計模式JDK原始碼
- JUnit原始碼分析 (三)——Template Method模式原始碼模式
- 從vue原始碼看觀察者模式Vue原始碼模式
- RxJava 原始碼解析之觀察者模式RxJava原始碼模式
- JUnit原始碼分析(四)——從Decorator模式說起原始碼模式
- JUnit原始碼分析(一)——Command模式和Composite模式原始碼模式
- 從觀察者模式到手寫EventEmitter原始碼模式MIT原始碼
- Android設計模式原始碼解析之ListView觀察者模式Android設計模式原始碼View
- Retrofit原始碼分析二 代理模式原始碼模式
- 觀察者模式模式
- Kafka原始碼分析(二) - 生產者Kafka原始碼
- 物件間的聯動——觀察者模式(二)物件模式
- 【程式碼簡述設計模式】----- 觀察者模式設計模式
- 當觀察者模式和回撥機制遇上Android原始碼模式Android原始碼
- Java中的設計模式(二):生產者-消費者模式與觀察者模式Java設計模式
- PHP觀察者模式PHP模式
- Unity——觀察者模式Unity模式
- 觀察者模式(2)模式
- Java 觀察者模式Java模式
- JS 觀察者模式JS模式
- 設計模式(九)——裝飾者模式(io原始碼分析)設計模式原始碼
- 設計模式----觀察者模式設計模式
- 設計模式 —— 觀察者模式設計模式
- 設計模式(觀察者模式)設計模式
- 設計模式——觀察者模式設計模式
- 設計模式-觀察者模式設計模式
- 設計模式_觀察者模式設計模式
- 【設計模式】觀察者模式設計模式
- 觀察者模式-將訊息通知給觀察者模式
- JUnit4.8.2原始碼分析-4 RunNotifier與RunListener原始碼
- [head first 設計模式]第二章 觀察者模式設計模式
- Laravel核心程式碼學習 — 觀察者模式Laravel模式
- PHP-觀察者模式PHP模式
- PHP 之觀察者模式PHP模式
- 大話--觀察者模式模式
- redux與觀察者模式Redux模式
- 觀察者模式介紹模式
- 重構 - 觀察者模式模式