`
ajoo
  • 浏览: 453110 次
社区版块
存档分类
最新评论
阅读更多
场景一:

个人喜欢state-based的单元测试。(定义见mock is not stub)。

可是有些时候,比如在测试一些使用java.sql.Connection, ibatis SqlMapClient等接口的类的时候,明显写stub很不好。(1,接口很大,有很多不相干的东西。2,版本一旦变化,这些接口可能跟着变化,如果写stub的话,就意味着stub要跟着这些第三方接口变化)

于是,只好mock。只好interaction based。只好每回内部实现一变就死盯着一坨坨的expectAndReturn找不再有效的expectation。

场景二:

一个遗留系统用自己的连接池,使用ConnectionManager.checkIn(Connection)来释放连接(而不是Connection.close)

为了重构,希望能够重载Connection.close(),让它调用checkIn()。

跟场景一的提到的原因一样,不希望直接写一个MyConnection implements Connection。于是只好自己写dynamic proxy,形成如下的代码:
  private Connection conn;
  public Object invoke(Object proxy, Method method, Object[] args){
    if((args==null || args.length==0) && method.getName().equals("close")){
      ConnectionManager.checkIn((Connection)conn);
    }
    else {
      try {
        return method.invoke(conn, args);
      }
      catch(InvocationTargetException e){
        throw e.getTargetException();
      }
    }
  }



后面又发现这个变态的一流系统居然用自己的startMyTransaction, endMyTransaction(), rollbackMyTransaction()来搞事务处理!
于是又要继续判断method.getName().equals("startTransaction"), method.getName().equals("commitTransaction")等等等等。

dynamic proxy的代码难看的要死。



最近终于受不了了。愤然写了一个叫做酒窝的东西(dimple)。

所谓dimple,就是dynamic implementor的意思。我要用纯java语法来实现我想实现的方法(不是expectAndReturn,也不是if(method.getName().equals("method1"))),但是同时我又不愿意写“implements TheInterface”,以避免被迫处理那些我不关心的method。

用法如下:

场景一:

这样实现我的stub:
MyParameter myparam = ...;
SqlMapClient client = (SqlMapClient) Implementor.proxy(SqlMapClient.class, new Object(){
  public Object insert(String id, Object param) {
    assertEquals("test id", id);
    assertSame(myparam, param);
    return null;
  }
});
assertNull(new SomeClassUsingSqlMapClient(client).runSomeInsert(myparam));


没有讨厌的expectAndReturn,不用担心parameter matcher。


场景二:
final Connection realConn = ...;
Connection conn = (Connection) Implementor.proxy(Connection.class, new Object(){
  public void close() {
    ConnectionManager.checkIn(realConn);
  }
  public void startTransaction(){
    ConnectionManager.startMyTransaction(realConn);
  }
  public void commitTransaction(){
    ConnectionManager.commitTransaction(realConn);
  }
}, realConn);

于是,对close(), startTransaction, commitTransaction(),我们都直接调用ConnectionManager, 而对其它的method则委托给realConn。




这个Implementor类是纯java代码,除了dynamic proxy没有用任何其它技术。(没有enhancement,aop之类的)



个人感觉还是挺有用的。大家点评一下?
分享到:
评论
91 楼 ajoo 2007-01-11  
继续给酒窝寻找用途。

在robbin的domain model设计的帖子中,提到了第二种模型(rich domain model)的一些不足之处(贫血模型也有一样的毛病)。引用如下:
引用

问题就在于这个充当business workflow facade的业务逻辑对象,它的变动是相当频繁的。业务逻辑对象通常都是无状态的、受事务控制的、Singleton类,我们可以考察一下业务逻辑对象都有哪几类业务逻辑方法:

第一类:DAO接口方法的代理,就是上面例子中的loadItemById方法和findAll方法。

ItemManager之所以要代理这种类,目的有两个:向Web层提供统一的服务调用入口点和给持久化方法增加事务控制功能。这两点都很容易理解,你不能既给Web层程序员提供xxxManager,也给他提供xxxDao,所以你需要用xxxManager封装xxxDao,在这里,充当了一个简单代理功能;而事务控制也是持久化方法必须的,事务可能需要跨越多个DAO方法调用,所以必须放在业务逻辑层,而不能放在DAO层。

但是必须看到,对于一个典型的web应用来说,绝大多数的业务逻辑都是简单的CRUD逻辑,所以这种情况下,针对每个DAO方法,xxxManager都需要提供一个对应的封装方法,这不但是非常枯燥的,也是令人感觉非常不好的。

第二类:domain logic的方法代理。就是上面例子中placeBid方法。虽然Item已经有了placeBid方法,但是ItemManager仍然需要封装一下Item的placeBid,然后再提供一个简单封装之后的代理方法。

这和第一种情况类似,其原因也一样,也是为了给Web层提供一个统一的服务调用入口点和给隐式的持久化动作提供事务控制。

同样,和第一种情况一样,针对每个domain logic方法,xxxManager都需要提供一个对应的封装方法,同样是枯燥的,令人不爽的。

第三类:需要多个domain object和DAO参与协作的business workflow。这种情况是业务逻辑对象真正应该完成的职责。

在这个简单的例子中,没有涉及到这种情况,不过大家都可以想像的出来这种应用场景,因此不必举例说明了。

通过上面的分析可以看出,只有第三类业务逻辑方法才是业务逻辑对象真正应该承担的职责,而前两类业务逻辑方法都是“无奈之举”,不得不为之的事情,不但枯燥,而且令人沮丧。


原文见:http://www.iteye.com/topic/11712

引用的这段话主要是说,在下面这个代码中,ItemManager必须要对loadItemById(), findAll()这两个方法进行委托:
public interface ItemDao {  
  public Item getItemById(Long id);  
  public Collection findAll();  
  public void updateItem(Item item);  
}  
public class ItemManager { 
    private ItemDao itemDao; 
    public void setItemDao(ItemDao itemDao) { this.itemDao = itemDao;} 
    public Bid loadItemById(Long id) { 
        itemDao.loadItemById(id); 
    } 
    public Collection listAllItems() { 
        return  itemDao.findAll(); 
    } 
    public Bid placeBid(Item item, User bidder, MonetaryAmount bidAmount, 
                            Bid currentMaxBid, Bid currentMinBid) throws BusinessException { 
        item.placeBid(bidder, bidAmount, currentMaxBid, currentMinBid);
        itemDao.update(item);    // 必须显式的调用DAO,保持持久化
    }
}  

例子中只有两个方法,还没什么,但是如果有10个呢?100个呢?这个委托就烦死人了。

我无意在此继续炒哪种domain model更好,我要做的,只是show一下酒窝可以用来解决robbin提出的这个问题的。

怎么解决呢,很简单,只要保证ItemManager里面的方法和被委托的方法签名一致就好了。最容易达到这个目的的办法就是弄一个额外的package private interface出来。这个接口的唯一目的是让ItemDao和ItemManager共同继承一些签名:
interface AboutItem {  
  Item getItemById(Long id);  
  Collection findAll();
}

然后,让ItemDao继承AboutItem,再给ItemManager提取出一个接口出来:
public interface ItemDao extends AboutItem {
  void updateItem(Item item);  
}
public interface ItemManager extends AboutItem {
  Bid placeBid(Item item, User bidder, MonetaryAmount bidAmount, 
                            Bid currentMaxBid, Bid currentMinBid) throws BusinessException;
}

到此为止呢,仅仅是声明一些接口,没有任何实现上的动作。
下面用dimple来实现ItemManagerImpl对ItemDao的委托:
public class ItemManagerImpl { 
    private ItemDao itemDao; 
    public Bid placeBid(Item item, User bidder, MonetaryAmount bidAmount, 
                            Bid currentMaxBid, Bid currentMinBid) throws BusinessException { 
        item.placeBid(bidder, bidAmount, currentMaxBid, currentMinBid);
        itemDao.update(item);    // 必须显式的调用DAO,保持持久化
    }
}
ItemDao dao = ...;
ItemManagerImpl impl = ...;
ItemManager manager = (ItemManager)Implementor.proxy(ItemManager.class, impl, itemDao);

这样,robbin提出的被迫写很多委托的问题就不存在了。也就不令人沮丧了。
90 楼 ajoo 2007-01-08  
codehaus上的用dimple进行ibatis integration的介绍
http://dimple.codehaus.org/Integrate+IBatis+With+Legacy+System
89 楼 ajoo 2007-01-07  
关于那个Connection的例子,实际的情况(简化了的)是这样:
1。遗留系统用ConnectionManager.checkOut()和ConnectionManager.checkIn(Connection)这两个静态方法来创建和释放连接。(看到了吧?什么ConnectionManager, ResourceManager之类的方法都是害群之马!)
2。现在需要使用iBatis来实现一部分持久层。但是需要仍然遵循遗留系统的ConnectionManager方式。很明显,ibatis对我们自制的ConnectionManager一无所知,它只知道调用标准的Connection.close()来关闭连接。

解决方案是给ibatis配置一个DataSourceFactory类。要配置DataSourceFactory,需要提供一个特制的DataSource实现和Connection实现,让DataSource.getConnection()调用ConnectionManager.checkOut(),把ConnectionManager返回的realConnection封装成另外一个Connection实例,这个实例在被调用Connection.close()的时候,实际调用ConnectionManager.checkIn(realConnection)。

伪代码实现如下:
class LegacyConnection implements Connection {
  private final Connection realConnection;
  private boolean closed;
  public void close(){
    if(closed) return;
    ConnectionManager.checkIn(realConnection);
    closed = true;
  }
  public boolean isClosed() {
    return closed;
  }
  //所有其它的方法都委托给realConnection。
}

class LegacyDataSource implements DataSource {
  public Connection getConnection() {
    return new LegacyConnection(ConnectionManager.checkOut());
  }
  //所有其它的方法都不需要实现。
}
public class LegacyDataSourceFactory implements DataSourceFactory {
  public DataSource getDataSource() {
    return new LegacyDataSource();
  }
}


明显,上面的代码编译不过,因为我们没有实现所有的Connection和DataSource接口定义的方法。

传统的解决方法是,根据我们上面的注释,用Eclipse的自动代码生成来把所有其它的方法搞定。这样做的问题除了繁琐之外,最大的麻烦是versioning。也许你生成的代码是针对jdbc 3.0的,那么它放在jdbc 4.0下,将不能编译。

使用dimple,就不会有这个问题:

class LegacyConnection /*implements Connection*/ {
  private final Connection realConnection;
  private boolean closed;
  public void close(){
    if(closed) return;
    ConnectionManager.checkIn(realConnection);
    closed = true;
  }
  public boolean isClosed() {
    return closed;
  }
}

class LegacyDataSource /*implements DataSource*/ {
  public Connection getConnection() {
    final Connection realConnection = ConnectionManager.checkOut();
    return Implementor.proxy(Connection.class, new LegacyConnection(realConnection), realConnection);
  }
}
public class LegacyDataSourceFactory implements DataSourceFactory {
  public DataSource getDataSource() {
    return Implementor.proxy(DataSource.class, new LegacyDataSource());
  }
}


上面的代码里面,我们只实现了关心的方法,其它的都用dimple给自动搞定了。这个代码不管放到哪个jdbc版本下都工作(呵呵,当然得支持DataSource才行了)
88 楼 ajoo 2007-01-05  
firebody 写道
ajoo 写道
引用
恩,更具体的情况或许我没有考虑到,但是从你提供的runApi(Map map) 这个接口来看,runApi它还需要知道这个map是PropertyMap? 还需要依赖PropertyMap更深的语义?

不需要了解。但是,语义上一个clone或者是那个对象自己是有区别的。就说不是PropertyMap而是直接的Map,下面两个代码也是有区别的:
Map someMap = new HashMap();
MyClass mc = new MyClass(someMap);
someMap.put(...);
mc.doSomething();

Map someMap = new HashMap();
MyClass mc = new MyClass(cloneMap(someMap));
someMap.put(...);
mc.doSomething();

对么?

当我需要第一段代码的语义的时候,你的Utils.convertToMap就不成了。


哦,看了你的代码,想了一下,估计你的意思是说,runApi的逻辑是需要改变的参数的state的,而且外面的逻辑需要依赖于这个改变的state.

如果这样的话,确实只能这样了。

不过还想啰嗦多一些题外话, 另外针对这样的设计,我觉得是一种很奇怪或者危险的设计:
a.getMap().put(..) ,a.getList().add   , 或者更甚的,就像你举的例子,直接把Map,List当作参数传来传去,在每个逻辑里面修改map/list的状态。 


换成这样:
class a{
 Map getMap() {return collections.unmodifialbale(map) } ; void addMapMember(key,value) ,..... } 



这样的话,通过明确语义的接口,逻辑至少要清晰很多 。 然后,把所有的map.put的逻辑都封装到一个方法里面,针对你提到的语义的危险,那么也可以在这class:A 这个 地方加以规范了 。


至少,不再为此想那么多tricky甚至古怪到精妙的方式了。

runApi需要的就是一个Map,一点trick也没有。它才不管这个state是在什么时候变化的呢。这也是dependency injection的好处,组件可以简单到极点。

tricky的是这个runApi的客户代码。这本来是个遗留系统,现在要使用runApi来重构一下。原来是代码到处都是直接调用PropFactory.getInstance().getProperty(),现在要改为依赖注射的方式,把Map注射进runApi。

因为无法确定这个遗留系统本来是否依赖于状态变化,或者什么时候状态会变化,(其实,我也不想关心太多这个系统的实现细节)最好的办法就是不做任何假设,原封不动地照搬原来的语义(也就是直接使用一个PropertyMap,而不是一个PropertyMap的clone)。

这既没有什么古怪,也没有什么精妙之处。只是重构的一个原则罢了。
87 楼 firebody 2007-01-05  
ajoo 写道
引用
恩,更具体的情况或许我没有考虑到,但是从你提供的runApi(Map map) 这个接口来看,runApi它还需要知道这个map是PropertyMap? 还需要依赖PropertyMap更深的语义?

不需要了解。但是,语义上一个clone或者是那个对象自己是有区别的。就说不是PropertyMap而是直接的Map,下面两个代码也是有区别的:
Map someMap = new HashMap();
MyClass mc = new MyClass(someMap);
someMap.put(...);
mc.doSomething();

Map someMap = new HashMap();
MyClass mc = new MyClass(cloneMap(someMap));
someMap.put(...);
mc.doSomething();

对么?

当我需要第一段代码的语义的时候,你的Utils.convertToMap就不成了。


哦,看了你的代码,想了一下,估计你的意思是说,runApi的逻辑是需要改变的参数的state的,而且外面的逻辑需要依赖于这个改变的state.

如果这样的话,确实只能这样了。

不过还想啰嗦多一些题外话, 另外针对这样的设计,我觉得是一种很奇怪或者危险的设计:
a.getMap().put(..) ,a.getList().add   , 或者更甚的,就像你举的例子,直接把Map,List当作参数传来传去,在每个逻辑里面修改map/list的状态。 


换成这样:
class a{
 Map getMap() {return collections.unmodifialbale(map) } ; void addMapMember(key,value) ,..... } 



这样的话,通过明确语义的接口,逻辑至少要清晰很多 。 然后,把所有的map.put的逻辑都封装到一个方法里面,针对你提到的语义的危险,那么也可以在这class:A 这个 地方加以规范了 。


至少,不再为此想那么多tricky甚至古怪到精妙的方式了。
86 楼 ajoo 2007-01-05  
引用
恩,更具体的情况或许我没有考虑到,但是从你提供的runApi(Map map) 这个接口来看,runApi它还需要知道这个map是PropertyMap? 还需要依赖PropertyMap更深的语义?

不需要了解。但是,语义上一个clone或者是那个对象自己是有区别的。就说不是PropertyMap而是直接的Map,下面两个代码也是有区别的:
Map someMap = new HashMap();
MyClass mc = new MyClass(someMap);
someMap.put(...);
mc.doSomething();

Map someMap = new HashMap();
MyClass mc = new MyClass(cloneMap(someMap));
someMap.put(...);
mc.doSomething();

对么?

当我需要第一段代码的语义的时候,你的Utils.convertToMap就不成了。
85 楼 firebody 2007-01-05  
ajoo 写道
Utils.convertoMapFromPropertyMap(PropertyMap pm){ Map result = new HashMap(); ...
return result; }

为什么不这么做呢?因为这样做的语义是不同的。

这个PropertyMap可能被注射进组件后才被populate。
一个简单化的例子:
PropertyMapImpl pmap = new PropertyMapImpl();
MyClass mc = new MyClass(convertToMap(pmap));
pmap.addProperty(...);
mc.doSomething();

所以不能copy,只能adapter

另外,dimple不是要替代easymock。

只是说,当前提是你并不想做mock testing,而仅仅是为了方便用easymock来创建stub的时候,用dimple直接创建stub可能更合适。


恩,更具体的情况或许我没有考虑到,但是从你提供的runApi(Map map) 这个接口来看,runApi它还需要知道这个map是PropertyMap?  还需要依赖PropertyMap更深的语义?

84 楼 ajoo 2007-01-05  
Utils.convertoMapFromPropertyMap(PropertyMap pm){ Map result = new HashMap(); ...
return result; }

为什么不这么做呢?因为这样做的语义是不同的。

这个PropertyMap可能被注射进组件后才被populate。
一个简单化的例子:
PropertyMapImpl pmap = new PropertyMapImpl();
MyClass mc = new MyClass(convertToMap(pmap));
pmap.addProperty(...);
mc.doSomething();

所以不能copy,只能adapter

另外,dimple不是要替代easymock。

只是说,当前提是你并不想做mock testing,而仅仅是为了方便用easymock来创建stub的时候,用dimple直接创建stub可能更合适。
83 楼 firebody 2007-01-05  
ajoo 写道
引用
更正一下:
是说 Dimple相比于直接Utils.convertToMap有何优势 。

第二个疑问:

意思是说原来conn.close()---> resourceManager.closeConn(conn);

dimple就是用来实现convertToMap的呀。要不你怎么实现Utils.convertToMap?


你是说ResourceManager.closeConnection(conn)么?嗯,老实说我一点也看不出这种方法为什么可以不被禁止。

其实,这个问题之所以被提出,一个原因就是因为一个遗留系统使用了ConnectionManager.checkIn(Connection)的方法。而我要调用Ibatis,(Ibatis对ConnectionManager当然是一无所知),于是为了保证安全我才把一个禁止了close()的Connection对象传递给IBatis,以避免IBatis,或者IBatis的用户代码调用Connection.close()。其实所有的麻烦罪魁祸首就是这个遗留系统非要标新立异弄什么ConnectionManager.checkIn,而不是遵循标准,允许Connection.close()


dimple就是用来实现convertToMap的呀。要不你怎么实现Utils.convertToMap?

Utils.convertoMapFromPropertyMap(PropertyMap pm){ Map result = new HashMap();  ...
return result; }


对于你的第一个帖子,确实会碰到这样的问题,除了用一个Adapter, 没有更好的办法解决了。 不过对于后面提到的要替代EasyMock,我不觉得Dimple有充分的优势。
82 楼 ajoo 2007-01-05  
引用
更正一下:
是说 Dimple相比于直接Utils.convertToMap有何优势 。

第二个疑问:

意思是说原来conn.close()---> resourceManager.closeConn(conn);

dimple就是用来实现convertToMap的呀。要不你怎么实现Utils.convertToMap?


你是说ResourceManager.closeConnection(conn)么?嗯,老实说我一点也看不出这种方法为什么可以不被禁止。

其实,这个问题之所以被提出,一个原因就是因为一个遗留系统使用了ConnectionManager.checkIn(Connection)的方法。而我要调用Ibatis,(Ibatis对ConnectionManager当然是一无所知),于是为了保证安全我才把一个禁止了close()的Connection对象传递给IBatis,以避免IBatis,或者IBatis的用户代码调用Connection.close()。其实所有的麻烦罪魁祸首就是这个遗留系统非要标新立异弄什么ConnectionManager.checkIn,而不是遵循标准,允许Connection.close()
81 楼 firebody 2007-01-05  
ajoo 写道
firebody 写道
ajoo 写道
没有看到我说的“不是测试的问题”么?

这里面dimple是用在production代码里面的。测试代码不用dimple。



哦,不好意思,我满脑子都是测试,哈哈。 打错靶子了。

不过,我仍然想多啰嗦几句。


问题在于某个和第三方库或者legacy system集成的部分碰巧没有Map,而只有一个PropertyMap,这时就可以用dimple。

这个例子用Dimple确实可以,但是我看不出我直接用一个Utils.convertToMap的逻辑有何优势。

那个,啥意思?你是说看不出相比于直接Utils.convertToMap有何优势?还是看不出用一个adapter有何优势?

firebody 写道

另外,对于你提到的需要拦截Connection.close ,而需要一个ConnectionAdapter 来统一拦截,以此提出了Dimple的设计。

我对这个出发点也提出疑义。  虽然这是一个很好的办法,但是我更倾向于用一个统一的接口来open,close Connection来达到统一拦截的目的,或许这个接口语义很弱,但是在一个项目/框架开发里面,把这个需求提到每个开发者的需求也不是很难的事情。

虽然,这里有点类似AOP的概念,但是对于AOP,正如ROD所说的,AOP不应该和OOP成为对立的关系,两者应该是互补的关系。 AOP应该在OOP设计良好的基础上成为良好的补充。


统一的接口来open, close又是啥吗意思?俺咋糊涂了尼。


更正一下:
是说 Dimple相比于直接Utils.convertToMap有何优势 。


第二个疑问:

意思是说原来conn.close()---> resourceManager.closeConn(conn);

80 楼 ajoo 2007-01-05  
firebody 写道
ajoo 写道
没有看到我说的“不是测试的问题”么?

这里面dimple是用在production代码里面的。测试代码不用dimple。



哦,不好意思,我满脑子都是测试,哈哈。 打错靶子了。

不过,我仍然想多啰嗦几句。


问题在于某个和第三方库或者legacy system集成的部分碰巧没有Map,而只有一个PropertyMap,这时就可以用dimple。

这个例子用Dimple确实可以,但是我看不出我直接用一个Utils.convertToMap的逻辑有何优势。

那个,啥意思?你是说看不出相比于直接Utils.convertToMap有何优势?还是看不出用一个adapter有何优势?

firebody 写道

另外,对于你提到的需要拦截Connection.close ,而需要一个ConnectionAdapter 来统一拦截,以此提出了Dimple的设计。

我对这个出发点也提出疑义。  虽然这是一个很好的办法,但是我更倾向于用一个统一的接口来open,close Connection来达到统一拦截的目的,或许这个接口语义很弱,但是在一个项目/框架开发里面,把这个需求提到每个开发者的需求也不是很难的事情。

虽然,这里有点类似AOP的概念,但是对于AOP,正如ROD所说的,AOP不应该和OOP成为对立的关系,两者应该是互补的关系。 AOP应该在OOP设计良好的基础上成为良好的补充。

统一的接口来open, close又是啥吗意思?俺咋糊涂了尼。
79 楼 firebody 2007-01-05  
ajoo 写道
没有看到我说的“不是测试的问题”么?

这里面dimple是用在production代码里面的。测试代码不用dimple。



哦,不好意思,我满脑子都是测试,哈哈。 打错靶子了。

不过,我仍然想多啰嗦几句。


问题在于某个和第三方库或者legacy system集成的部分碰巧没有Map,而只有一个PropertyMap,这时就可以用dimple。

这个例子用Dimple确实可以,但是我看不出我直接用一个Utils.convertToMap的逻辑有何优势。


另外,对于你提到的需要拦截Connection.close ,而需要一个ConnectionAdapter 来统一拦截,以此提出了Dimple的设计。

我对这个出发点也提出疑义。  虽然这是一个很好的办法,但是我更倾向于用一个统一的接口来open,close Connection来达到统一拦截的目的,或许这个接口语义很弱,但是在一个项目/框架开发里面,把这个需求提到每个开发者的需求也不是很难的事情。

虽然,这里有点类似AOP的概念,但是对于AOP,正如ROD所说的,AOP不应该和OOP成为对立的关系,两者应该是互补的关系。 AOP应该在OOP设计良好的基础上成为良好的补充。
78 楼 ajoo 2007-01-05  
没有看到我说的“不是测试的问题”么?

这里面dimple是用在production代码里面的。测试代码不用dimple。
77 楼 firebody 2007-01-05  
ajoo 写道
引用
这个例子我存在异议。 看着有点困惑。

如果对于 因为”stub“比较难写,需要一个工具框架来自动实现这么一个stub,我赞成。
但是如果 因为”测试“难写而拼命的使用这么一个框架,我觉得有点舍本求末了。 或许,回过头来重构设计和代码,让测试更容易写 ,或许这样获得事倍功半的效果呢?

这个例子不是测试阿。它的前提是:
一个模块设计成可以注射java.util.Map。这个设计是很自然的。
其他的普通客户代码都是直接用HashMap或者java.util.Properties之类的东西来注射。
单元测试代码也是用HashMap的。也没问题。

问题在于某个和第三方库或者legacy system集成的部分碰巧没有Map,而只有一个PropertyMap,这时就可以用dimple。


如果是这样的话,那么ConvertToMap 的逻辑也是有必要冒烟测试到的。 所以,

那测试也就很自然的从PropertyMap准备测试数据进行测试了,这样的测试也没有必要用DImple来模拟 ConvertToMap的逻辑阿,我只需要在测试里面测试到就可以了。
76 楼 firebody 2007-01-05  
taowen 写道
呵呵,有些系统提供的接口我们是重构不了的啊。所以实现这些接口就是价值所在了。


我所能想到的 ”需要实现这些接口/抽象类的“  充要条件:

1) Easymock模拟的代码很难写 ,或者代码比用Dimple作stub要多得多

2)人家提供的接口是一个类,而不是一个接口。

对于1)如果我们依赖人家接口的一两个method,我觉的EasyMock写起来难度不大。  如果我们的逻辑依赖人家接口的大部分API 。 确实,stub要来得好得多。 但是此时,我可能就需要在 自己写一个stubImpl和用Dimple实现一个Stub来权衡了,但是这个结果是在 ”我们的逻辑需要依赖很多人家接口的method“情况下发生的,这样一来,估计看看Dimple的代码,满篇也是实现的代码了吧,在这个前提下,优势我暂时看不出来。

对于2) ,因为不用接口带来如此的难题,stub似乎不得不提出来了。但是作为选择,也可以用一个Adapter接口来封装人家的类。对这个Adapter接口的处理又回到了1)
75 楼 ajoo 2007-01-05  
taowen 写道
看了一下发型包,已经0.4啦?现在有sample了么?有没有想好写一些什么经典应用场景呢?比如在做stub的情况下,和easymock做一个对比。至少可以告诉大家,在只要stub的情况下,犯不着搬出mock框架这么重的东西来。

easymock倒也不算多重。而且它还有重构安全的特性。

区别仅仅在于stub vs. mock。如果需要做interaction-based test,当然是easymock,没得说。而如果要做state-based test,用easymock来做stub就有点别扭。

我的sample都很简单,已经写在confluence里了。当然也许有点生造出来的嫌疑。你有什么好的例子么?
74 楼 ajoo 2007-01-05  
引用
这个例子我存在异议。 看着有点困惑。

如果对于 因为”stub“比较难写,需要一个工具框架来自动实现这么一个stub,我赞成。
但是如果 因为”测试“难写而拼命的使用这么一个框架,我觉得有点舍本求末了。 或许,回过头来重构设计和代码,让测试更容易写 ,或许这样获得事倍功半的效果呢?

这个例子不是测试阿。它的前提是:
一个模块设计成可以注射java.util.Map。这个设计是很自然的。
其他的普通客户代码都是直接用HashMap或者java.util.Properties之类的东西来注射。
单元测试代码也是用HashMap的。也没问题。

问题在于某个和第三方库或者legacy system集成的部分碰巧没有Map,而只有一个PropertyMap,这时就可以用dimple。
73 楼 taowen 2007-01-05  
呵呵,有些系统提供的接口我们是重构不了的啊。所以实现这些接口就是价值所在了。
72 楼 firebody 2007-01-05  
ajoo 写道
再举一个adapter的例子。

不知道大家遇到过这样的情况没有。有一个api,有这个接口签名:
runApi(Map map){
  ...
  map.get(...);
  ...
}


它仅仅使用get()一个方法,但是因为种种原因,并没有使用最小接口(我认为这种设计是非常正常的),而是用了这个java.util.Map接口。


在使用这个api的时候,我们不幸的没有一个Map对象,而只有一个proprietary的PropertyMap(或者HttpServletRequest也行)对象:
interface PropertyMap {
  String getProperty(String key);
  boolean containsKey(String key);
  Set keySet();
}

需要写个adapter吧?目标代码如下:
PropertyMap pmap = ...;
runApi(convertToMap(pmap));


那么这个convertToMap怎么写呢?

Map接口相当的肥厚阿。继承AbstractMap的话要实现entrySet(),我们也没有。而我们明明知道runApi()只需要一个能支持get()的Map就成了。

一个方法呢,就是用dimple,下面是一个pojo的adapter(不依赖dimple):
public class PropertyMapAdapter {
  private final PropertyMap pmap;
  public Object get(Object key) {
    if(key instanceof String) {
      return pmap.get((String)key);
    }
    else return null;
  }
  public boolean containsKey(Object key) {
    if(key instanceof String) {
      return pmap.containsKey((String)key);
    }
    else return false;
  }
  public Set keySet() {
    return pmap.keySet();
  }
  public boolean isEmpty() {
    return keySet().isEmpty();
  }
  public int size() {
    return keySet().size();
  }
}

只需要实现我们关心的方法(一个安全的方法,可以显然PropertyMapAdapter implements Map,这样就可以用eclipse的代码生成来选择性地实现这些我们关心的方法,不会有typo。),不用管其它的乱七八糟的。

然后使用dimple,实现convertToMap如下:
Map convertToMap(PropertyMap pmap) {
  return Implementor.proxy(Map.class, new PropertyMapAdapter(pmap));
}


搞定。

可见,dimple并不是仅仅可以用做aop的interceptor,它还是一个克服静态类型局限的OO利器亚。而且不象aspectj,相当的light-weight,没有什么侵入性。



它仅仅使用get()一个方法,但是因为种种原因,并没有使用最小接口(我认为这种设计是非常正常的),而是用了这个java.util.Map接口。


看了这个例子,我的看法:
如果在视线中 需要这么复杂的代码来做测试,那么就意味着个这个接口设计并不“正常”了,根据你提到的“仅仅用到了map.get()"的这个意思,那么接口应该这样设计:
map.get()--->Object o

runApi(Map map)--->runApi(Object o) { // not dependent on map}


 

这个例子我存在异议。 看着有点困惑。

如果对于 因为”stub“比较难写,需要一个工具框架来自动实现这么一个stub,我赞成。
但是如果 因为”测试“难写而拼命的使用这么一个框架,我觉得有点舍本求末了。 或许,回过头来重构设计和代码,让测试更容易写 ,或许这样获得事倍功半的效果呢?

相关推荐

    酒窝状换热板强化换热效果分析

    设计了一种酒窝状换热板,利用Fluent软件对酒窝板、平板及波纹板的换热及压降特性进行了模拟,对这3种板在不同流速下板间流道内壁面平均换热系数、换热量、平均努塞尔数以及摩擦阻力的变化特性进行了对比分析。...

    《小酒窝》教学设计.doc

    《小酒窝》教学设计正是为了打开这样一扇窗,让小学生在音乐的海洋中畅游。 《小酒窝》是一首旋律优美、节奏明快的歌曲,它不仅简单易学,而且充满了童趣,非常适合小学生的音乐课堂。而另一首乐曲《我们多么幸福》...

    三年级上册音乐教案 第一单元《小酒窝》人音版(五线谱)(2014秋).doc

    在小学三年级的音乐课堂上,有一首特别的歌曲正在等待着孩子们——《小酒窝》。这首歌以其独特的魅力和深沉的情感,为孩子们打开了一扇探索音乐世界的大门。借助人音版教材,通过五线谱的传授方式,孩子们将沉浸在这...

    人音三年级上册小酒窝PPT教案学习.pptx

    人音三年级上册小酒窝PPT教案学习.pptx

    小酒窝三年级上册人音小学音乐学唱PPT学习教案.pptx

    在今天的小学音乐课堂上,我们将会一起学习一首名为《小酒窝》的歌曲。这首歌曲以其轻松愉快的旋律和简单明快的节奏,成为了小学三年级学生音乐教学中一道亮丽的风景线。通过精心设计的PPT教案,我们将引导孩子们一...

    人音版三年级上册小酒窝PPT教案学习.pptx

    人音版三年级上册小酒窝PPT教案学习.pptx

    酒窝窝DIGG类web2.0全站 -ASP源码.zip

    【标题】"酒窝窝DIGG类web2.0全站 -ASP源码.zip" 提供的是一款基于ASP编程语言的Web2.0网站源码,适用于构建互动性强、用户参与度高的社交平台。Web2.0是互联网发展的一个重要阶段,强调用户的交互性、共创性和分享...

    酒窝143

    【酒窝143】项目概述 "酒窝143"这个名称可能是指一个项目的代号或主题,但它本身并没有直接提供关于IT技术的知识点。不过,从提供的压缩包文件名“dimple143-master”来看,我们可以推测这可能是一个开源软件项目...

    我也当上姐姐了作文.doc

    小男孩长得特别可爱,尤其是笑起来时露出的小酒窝,让人忍不住想多看几眼。我本就喜欢小孩子,所以心里暗自希望能和他多交流几句。 旅行的时间总是过得特别快,不知不觉间,火车已经行驶了大半路程。然而,就在这时...

    四川省仁寿县城北实验初级中学2020学年八年级生物下学期第一次月考试题(无答案) 新人教版.doc

    15. 酒窝性状遗传分析:儿子、儿媳均有酒窝但生出无酒窝孩子,说明他们都是杂合子(Aa),再生一个孩子有酒窝的概率为75%,无酒窝的概率为25%。 以上知识点涵盖了生物学的多个核心概念,如生物生殖、发育、遗传规律...

    rcdimple:htmlwidgets的rCharts +酒窝

    rcdimple | htmlwidget rcdimple是在凹坑片的版本 。 我们的目标是直接从R漂亮,可定制的d3.js图表,并且只需最少的代码并且不了解JavaScript。 首先,可以使用devtools::install_github进行安装。...

    四川省仁寿县城北实验2014 2015学年八年级生物下学期第一次月考试题(无答案) 新人教版.doc

    - 若有酒窝为显性,无酒窝为隐性,有酒窝的祖父母、儿子、儿媳可能基因型为Aa,孙子无酒窝则基因型为aa。 - 儿子、儿媳再次生育,有酒窝孩子的概率为50%,因为他们都是Aa,子代可能为AA或Aa,其中Aa表现为有酒窝。...

    生物历年大题归纳遗传题.doc

    - 小明的家庭中,如果有酒窝是显性(A),无酒窝是隐性(a),小明父亲无酒窝,因此其基因型为aa,母亲有酒窝,基因型为AA或Aa。反之,如果无酒窝是显性,有酒窝是隐性,则母亲的基因型为aa,父亲必须是Aa,因为...

    winbeep for python

    使用python给你beep个"祝你生日快乐" 和 "小酒窝" 简介不够代码来凑 import winsound def winbeep_di(): winsound.Beep(1000, 100) ....此处省略500宇宙代码 if __name__ == '__main__': winbeep_di() ...

    我自画像作文怎么写

    描述时,可以用比喻、拟人等修辞手法增加语言的表现力,如“笑起来非常好看,像是小酒窝里面装了酒一样”。外貌描述不仅要细致,还要力求生动、有趣,让人印象深刻。 其次,性格特征的呈现是自画像作文的重点。性格...

    贵州省六盘水市第十三中学八年级生物下册 第七单元 第二章 第三节 基因的显性和隐性导学案(无答案) 新人教版

    此外,一对无酒窝的夫妇无法生育出有酒窝的子女,因为有酒窝的基因D是显性的,而决定无酒窝的基因d是隐性的,所以无酒窝的夫妇基因型只能是dd,无法提供D基因给子女。 在能力提升环节,卷舌与非卷舌的例子进一步...

    小学六年级语文总复习之句型转换教学教案教(学)案.doc

    例如,"脸蛋印着酒窝"可以扩写为"可爱的脸蛋印着迷人的酒窝",缩写为"脸蛋印着酒窝"。 - **反问句与陈述句的互换**:反问句常用来强调陈述,转换时要去掉反问词,调整语气,如"我们是小学生,难道不要好好学习吗?...

    夸奖一个女孩子的话.doc

    3. 你笑起来的样子最为动人,两片薄薄的嘴唇在笑,长长的眼睛在笑,腮上两个陷得很举动的酒窝也在笑。 4. 青翠的柳丝,怎能比及你的秀发;碧绿涟漪,怎能比及你的眸子; 5. 你热情似火,你的微笑让我神魂颠倒 这些...

    六年级语文总复习之句型转换教学设计1电子版教(学)案.doc

    而缩句则相反,它通过去除修饰成分,保留句子的核心内容,如将"可爱的脸蛋印着迷人的酒窝"简化为"脸蛋印着酒窝"。掌握扩句与缩句的技巧,可以让学生的语言表达更为丰富和准确。 反问句与述句的互换也是句型转换中的...

    自我介绍的作文精选.doc

    例如:“我是一个阳光的女孩,虽然谈不上特别美丽,但也不丑陋,上翘的嘴角和薄薄的嘴唇,笑起来还有浅浅的酒窝。” 3. **性格特点**:性格特征是自我介绍中的重要组成部分,能反映你的内在世界。如:“我性格热情...

Global site tag (gtag.js) - Google Analytics