`

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

    接口清晰易懂,就是一系列将测试过程的信息传递给观察者的操作。具体的子类将接受这些信息,并按照它们的方式显示给用户。
     比如,我们看看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(
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中的目标对象(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(
1212);
        assertEquals(
12L12L);
        assertEquals(
new Long(12), new Long(12));

        assertEquals(
"Size"1212);
        assertEquals(
"Capacity"12.011.990.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);
    }

}



dennis 2007-04-05 17:19 发表评论
分享到:
评论

相关推荐

    junit-4.10-src.zip

    5. **观察者模式**:JUnit的事件系统使用了观察者模式,允许测试监听器监听测试事件,如测试开始、结束、失败等。 通过阅读和分析这些源码,我们可以更深入地理解JUnit的工作原理,学习如何编写高效、可靠的测试...

    安卓Andriod源码——AppCodeFramework-master.zip

    2. 设计模式:AppCodeFramework-master中广泛应用了设计模式,如单例模式、工厂模式、观察者模式等,这有助于代码的组织和复用。 3. Android组件生命周期:深入理解Activity、Service等组件的生命周期,以及如何在...

    安卓Android源码——豆瓣手机客户端源码.zip

    9. **事件监听和响应**:观察者模式的运用,如使用EventBus或RxBus进行事件总线通信,提高组件间的解耦。 10. **测试**:源码中可能包含单元测试和集成测试,如使用JUnit和Mockito进行测试。 11. **版本控制**:...

    安卓Android源码——WeatherForecast1.rar

    - **LiveData和Observer模式**: 若使用Android Architecture Components,`LiveData`和观察者模式可以确保UI在数据变化时自动更新。 7. **动画和过渡效果** - **Transition API**: Android提供了丰富的动画工具,...

    安卓Android源码——[安卓开源]博客园客户端.7z

    3. **MVVM架构**:现代安卓应用往往采用MVVM(Model-View-ViewModel)架构,通过观察者模式和数据绑定简化开发,源码中可能有相关实现。 4. **Gradle构建系统**:Android项目通常使用Gradle进行构建,源码中会包含...

    安卓Android源码——[安卓开源]CBReader资讯阅读.7z

    9. **响应式编程**:如果CBReader使用了RxJava,那么我们可以学习如何在Android中运用响应式编程,理解观察者模式和流的概念,以及如何使用它来简化异步操作。 10. **测试与调试**:源码中可能包含单元测试和UI测试...

    仙林软件奇侠传——EL比赛.zip

    在软件开发中,设计模式是一种通用解决方案,比如单例模式、工厂模式、观察者模式等。参赛者可能会在项目中应用这些模式,提高代码可读性和可维护性。 ### 5. 软件工程原则 项目可能遵循软件工程的最佳实践,如模块...

    毕业设计论文-IT计算机-javapms-1.2-beta-源码.zip

    常见的设计模式如工厂模式、单例模式、观察者模式等,都有可能在源码中得到体现。这些模式可以提高代码的可读性、可维护性和可扩展性,使得项目更易于理解和升级。 在架构层面,JavaPMS可能遵循了MVC(Model-View-...

    面试题总结

    2. 设计模式:设计模式是解决软件设计中常见问题的模板,如单例模式、工厂模式、观察者模式等。熟悉并能灵活运用设计模式,可以提高代码的可读性和可维护性。 3. 操作系统原理:理解内存管理、进程通信、调度算法等...

    devnul-源码.rar

    4. **设计模式**:开发者可能会用到如工厂模式、观察者模式、装饰器模式等设计模式来提高代码的可维护性和复用性。 5. **数据结构与算法**:源码中可能会用到各种数据结构(如数组、链表、树、图等)和算法(排序、...

    星际

    2. **设计模式**:源码中可能会使用到多种设计模式,如工厂模式、单例模式、观察者模式等,这些模式是软件设计的通用解决方案,理解它们能帮助我们更好地把握代码的组织逻辑。 3. **数据结构与算法**:源码中的数据...

    基于Java的实例源码-copass源代码.zip

    1. **设计模式**:Java开发者经常使用设计模式来解决常见的编程问题,如单例模式、工厂模式、观察者模式等。查看源码可以帮助我们理解这些模式在实际项目中的应用。 2. **框架集成**:Java项目常常会依赖于各种框架...

    大学生(大一、大二、大三、大四)面向对象程序设计课程——Java项目大作业(小游戏).zip

    9. **设计模式**:游戏开发中可能涉及多种设计模式,如单例模式、工厂模式、观察者模式等,它们可以帮助学生写出更灵活、可维护的代码。 10. **调试和测试**:项目开发过程中,调试和单元测试是必不可少的环节。...

    简单的购物管理系统(java纯源码,无界面)

    8. **设计模式**:良好的代码结构通常会遵循一定的设计模式,如单例模式用于数据库连接,工厂模式用于对象的创建,观察者模式用于事件驱动等。源码中可以学习到这些设计模式的实际应用。 总的来说,通过分析这个...

    面向对象分析与设计

    设计模式是经过验证的解决方案,如工厂模式、单例模式、观察者模式等,它们是解决特定设计问题的最佳实践。 另一方面,"经典java项目源代码.rar"包含了一些实际的Java项目源码,这些源码可以用来学习和理解如何在...

    基于Java的贪吃蛇游戏源码.zip

    9. **设计模式**:游戏设计可能会用到一些设计模式,如单例模式(Singleton)用于创建唯一的游戏实例,工厂模式(Factory)用于创建游戏对象,观察者模式(Observer)用于更新游戏状态等。 10. **测试与调试**:...

    基于JAVA的ICQ系统毕业设计实现+源码毕业设计实现+源码.rar

    7. **设计模式**:可能运用到单例模式、工厂模式、观察者模式等常见设计模式以提高代码可读性和可维护性。 8. **消息序列化**:可能使用XML或JSON格式进行数据交换,需要理解相关的解析库,如Jackson或Gson。 9. **...

    JAVA项目开发全程实录(源码)

    Java项目常用的设计模式有单例、工厂、观察者等,这些模式有助于提高代码的可维护性和可扩展性。此外,MVC(Model-View-Controller)模式是Web应用开发中的常见架构,它将业务逻辑、数据模型和用户界面分离,便于...

    Android应用源码饭否网两种安卓客户端源码.zip

    9. **响应式编程**:如果使用了RxJava,可以学习如何使用观察者模式进行异步编程。 10. **单元测试与集成测试**:查看测试代码可以学习如何编写单元测试和集成测试,了解`JUnit`、`Espresso`等测试工具。 11. **...

    Java开放源码编程[www.TopSage.com].zip

    9. **设计模式**:理解并运用设计模式,如单例、工厂、观察者等,是提升Java编程能力的重要步骤。 通过阅读[大家网]Java开放源码编程[www.TopSage.com]这份文档,读者将能深入了解如何利用Java的开源生态,掌握最佳...

Global site tag (gtag.js) - Google Analytics