莊周夢(mèng)蝶

          生活、程序、未來(lái)
             :: 首頁(yè) ::  ::  :: 聚合  :: 管理

              我們知道JUnit支持不同的使用方式:swt、swing的UI方式,甚至控制臺(tái)方式,那么對(duì)于這些不同的UI我們?nèi)绾翁峁┙y(tǒng)一的接口供它們獲取測(cè)試過(guò)程的信息(比如出現(xiàn)的異常信息,測(cè)試成功,測(cè)試失敗的代碼行數(shù)等等)?我們?cè)囅胍幌逻@個(gè)場(chǎng)景,當(dāng)一個(gè)error或者exception產(chǎn)生的時(shí)候,測(cè)試能夠馬上通知這些UI客戶端:發(fā)生錯(cuò)誤了,發(fā)生了什么錯(cuò)誤,錯(cuò)誤是什么等等。顯而易見(jiàn),這是一個(gè)訂閱-發(fā)布機(jī)制應(yīng)用的場(chǎng)景,應(yīng)當(dāng)使用觀察者模式。那么什么是觀察者模式呢?

          觀察者模式(Observer)

          Observer是對(duì)象行為型模式之一

          1.意圖:定義對(duì)象間的一種一對(duì)多的依賴關(guān)系,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)現(xiàn)改變時(shí),所有依賴于它的對(duì)象都得到通知并被自動(dòng)更新

          2.適用場(chǎng)景:
          1)當(dāng)一個(gè)抽象模型有兩個(gè)方面,其中一個(gè)方面依賴于另一個(gè)方面,通過(guò)觀察者模式將這兩者封裝在不同的獨(dú)立對(duì)象當(dāng)中,以使它們可以獨(dú)立的變化和復(fù)用
          2)當(dāng)一個(gè)對(duì)象改變時(shí),需要同時(shí)改變其他對(duì)象,并且不知道其他對(duì)象的具體數(shù)目
          3)當(dāng)一個(gè)對(duì)象需要引用其他對(duì)象,但是你又不想讓這個(gè)對(duì)象與其他對(duì)象產(chǎn)生緊耦合的時(shí)候

          3.UML圖:
                           
             Subject及其子類維護(hù)一個(gè)觀察者列表,當(dāng)需要通知所有的Observer對(duì)象時(shí)調(diào)用Nitify方法遍歷Observer集合,并調(diào)用它們的update方法更新。而具體的觀察者實(shí)現(xiàn)Observer接口(或者抽象類),提供具體的更新行為。其實(shí)看這張圖,與Bridge有幾分相似,當(dāng)然兩者的意圖和適用場(chǎng)景不同。

          4.效果:
          1)目標(biāo)和觀察者的抽象耦合,目標(biāo)僅僅與抽象層次的簡(jiǎn)單接口Observer松耦合,而沒(méi)有與具體的觀察者緊耦合
          2)支持廣播通信
          3)缺點(diǎn)是可能導(dǎo)致意外的更新,因?yàn)橐粋€(gè)觀察者并不知道其他觀察者,它的更新行為也許將導(dǎo)致一連串不可預(yù)測(cè)的更新的行為

          5.對(duì)于觀察者實(shí)現(xiàn)需要注意的幾個(gè)問(wèn)題:
          1)誰(shuí)來(lái)觸發(fā)更新?最好是由Subject通知觀察者更新,而不是客戶,因?yàn)榭蛻艨赡芡浾{(diào)用Notify
          2)可以通過(guò)顯式傳參來(lái)指定感興趣的更新
          3)在發(fā)出通知前,確保Subject對(duì)象狀態(tài)的一致性,也就是Notify操作應(yīng)該在最后被調(diào)用
          4)當(dāng)Subject和Observer的依賴關(guān)系比較復(fù)雜的時(shí)候,可以通過(guò)一個(gè)更新管理器來(lái)管理它們之間的關(guān)系,這是與中介者模式的結(jié)合應(yīng)用。


              討論完觀察者模式,那我們來(lái)看JUnit是怎么實(shí)現(xiàn)這個(gè)模式的。在junit.framework包中我們看到了一個(gè)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);
          }

              接口清晰易懂,就是一系列將測(cè)試過(guò)程的信息傳遞給觀察者的操作。具體的子類將接受這些信息,并按照它們的方式顯示給用戶。
               比如,我們看看swing的UI中的TestRunner,它將這些信息顯示在一個(gè)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());
              }

          可以看到,它將錯(cuò)誤信息,異常信息保存在List或者Vector集合內(nèi),然后顯示在界面上:
          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(
          100100);
                     }
                  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中的目標(biāo)對(duì)象(Subject)就是TestResult對(duì)象,它有添加觀察者的方法:

          /**
               * Registers a TestListener
               
          */
              
          public synchronized void addListener(TestListener listener) {
                  fListeners.addElement(listener);
              }

          而通知觀察者又是怎么做的呢?請(qǐng)看這幾個(gè)方法,都是循環(huán)遍歷觀察者列表,并調(diào)用相應(yīng)的更新方法:

          /**
               * 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);
                  }
              }


          使用這個(gè)模式后帶來(lái)的好處:
          1)上面提到的Subject與Observer的抽象耦合,使JUnit可以支持不同的使用方式
          2)支持了廣播通信,目標(biāo)對(duì)象不關(guān)心有多少對(duì)象對(duì)自己注冊(cè),它只是通知注冊(cè)的觀察者

          最后,我實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的ConsoleRunner,在控制臺(tái)執(zhí)行JUnit,比如我們寫了一個(gè)簡(jiǎn)單測(cè)試:
          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(
          1212);
                  assertEquals(
          12L12L);
                  assertEquals(
          new Long(12), new Long(12));

                  assertEquals(
          "Size"1212);
                  assertEquals(
          "Capacity"12.011.990.01);
              }
          }

          使用ConsoleRunner調(diào)用這個(gè)測(cè)試,代碼很簡(jiǎn)單,不多做解釋了:
          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;
          //實(shí)現(xiàn)觀察者接口
          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(
          "測(cè)試結(jié)束:");
                  String message 
          = test.toString();
                  
          if (fTestResult.wasSuccessful())
                      System.out.println(message 
          + " 測(cè)試成功!");
                  
          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(
          "開(kāi)始測(cè)試:" + 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開(kāi)頭的方法
                      if (methods[i].getName().startsWith("test")) {
                          Test test 
          = new SimpleTest(methods[i].getName());
                          fTestResult 
          = createTestResult();
                          fTestResult.addListener(ConsoleRunner.
          this);
                          
          //執(zhí)行測(cè)試
                          test.run(fTestResult);
                      }
                  }

              }

              
          public static void main(String args[]) {
                  
          new ConsoleRunner().go(args);
              }

          }

          主站蜘蛛池模板: 邵阳市| 抚松县| 衡水市| 灵川县| 禹州市| 大石桥市| 伊春市| 太仓市| 淳化县| 罗山县| 翁源县| 晋中市| 友谊县| 双辽市| 漳平市| 东阿县| 黄山市| 南靖县| 牡丹江市| 双辽市| 顺义区| 克什克腾旗| 若羌县| 富民县| 文山县| 上杭县| 四平市| 哈密市| 古丈县| 五常市| 金川县| 娄底市| 锡林郭勒盟| 深水埗区| 宜丰县| 龙里县| 江城| 涡阳县| 若尔盖县| 客服| 大同市|