JUnit源码分析(二)——观察者模式

    xiaoxiao2024-10-07  88

    我们知道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); }     接口清晰易懂,就是一系列将测试过程的信息传递给观察者的操作。具体的子类将接受这些信息,并按照它们的方式显示给用户。      比如,我们看看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());     } 可以看到,它将错误信息,异常信息保存在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);     } 而Junit中的目标对象(Subject)就是TestResult对象,它有添加观察者的方法: /**      * 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);         }     } 使用这个模式后带来的好处: 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 );     } } 使用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);     } } 文章转自庄周梦蝶  ,原文发布时间5.17 相关资源:敏捷开发V1.0.pptx
    最新回复(0)