论坛首页 Java企业应用论坛

俺摸,俺摸,俺默默摸 (2)

浏览 6898 次
该帖已经被评为精华帖
作者 正文
   发表时间:2008-01-17  
OO
那啥?刚才说到哪了?“如云朵般的呵护”?下面谁说的?给我拉出去好好“呵护呵护”!

人家没那么说啦!讨厌!是刚才广告里说的嘛!我说刚才我们“俺摸”系列说到哪了?
对了,说到我们可以这么用PorkMockTest:

public class LionHeadTest extends PorkMockTest {
  public void testHuoHou() {
    LionHead head = mock(LionHead.class);
    head.bite();
    replay();
    cook(head);
    // 忘记吧,忘记吧。忘记是一种幸福。能忘记的人才能快乐地吃狮子头啊!
  }
}


那要是俺老猪比较馋(好像不需要“要是”了?),就好狮子头这口,连写了八个test都要mock(LionHead.class)怎么办?俺以前都这么写:

public class LionHeadTest extends PorkMockTest {
  private final LionHead head = mock(LionHead.class);
  
  public void testEat() {...}
  public void testEatAgain() {...}
  public void testOneMorePlease() {...}
  ...
}


不过后来在喜欢作JUnit专家状的沙师弟的唠叨下,已经不敢再明目张胆地这么做了。据丫说吧:“JUnit 3.8下,所有TestCase的构造函数都是在TestSuite创建的时候调用的,所以不要在构造函数里做任何可能比较慢或者可能抛exception的动作,否则,一旦发生异常,错误信息是惨不忍睹地不知所云!!!”。在Google上搜索出来的结果也似乎站在这个喜欢冒充高手的家伙那边,让我真想打他的脸啊,打他的脸。

哎,我真想揪住某个洋洋得意写了《Test Driven Development》的人的领口,啐他一脸。凭啥非要恶心俺老猪?招你啦?

于是,俺只好捏着鼻子在这个讨厌的家伙的监督押送下返工:
class LionHeadTest extends PorkMockTest {
  private LionHead head;

  @Override protected void setUp() throws Exception {
    super.setUp();
    head = mock(LionHead.class);
  }

  ...
}


靠!这是一砣什么屎?于是,除非实在饿的不行了,俺老猪宁可在每个test里面调用一遍mock(LionHead.class)也懒得搞这么一砣东西出来。

这不?昨天听师傅讲楞加经(楞要加塞儿经?),迷迷糊糊又睡着了,等猴哥拎着耳朵(当然是他自己的耳朵,敢拎我的?哼哼,现在俺这肉可贵了,掂量着自己赔的起先!)把俺叫醒,正好听见师傅不小心把经文夹缝里的字儿给念出来几个。然后俺就觉得小腹一股热气冲上丹田,瞬间增加了几个甲子的功力。然后体力,智力,根骨,悟性都增加5点。任督二脉无师自通。这个讨厌的问题也终于找到了一个解决方案,哈哈。

长话短说,我想到了用annotation的方法。只要在PorkMockTest里面加上一点反射的咚咚,我就可以这么写了:
public class LionHeadTest extends PorkMockTest {
 @Mock  private LionHead head;
  
  public void testEat() {...}
  public void testEatAgain() {...}
  public void testOneMorePlease() {...}
  ...
}

比我原来的那个版本还爽,现在我都不用车轱辘话把LionHead说两遍了!那么PorkMockTest怎么搞的呢?南无阿弥陀佛:

@Target(FIELD)
@Retention(RUNTME)
protected @interface Mock {}

@Override public void runBare() {
  mockFields(getClass(), this);
  super.runBare();
}

private void mockFields(Class cls, Object obj) {
  Field[] fields = cls.getDecalredFields();
  for (Field field : fields) {
    if (field.getAnnotation(Mock.class) != null) {
      field.setAccessible(true);
      field.set(obj, mock(field.getType());
    }
  }
  Class superclass = cls.getSuperclass();
  if (superclass != null) {
    mockFields(superclass, obj);
  }
}


几个可能的问题回答一下:
1。为什么用runBare()而不是runTest()?因为我希望所有的@Mock fields在setUp()之前就初始化好,这样子类在setUp()里面就可以为所欲为了。
2。这样的速度是不是比较慢?毕竟每个test都要跑一下runBare(),也就都要跑一下这些反射代码?是的。真实代码是用了一个cache来缓存每个class的所有@Mock field。不过这种技术细节我就不展开了。

现在的PorkMockClass不要太好用,尤其是需要用到很多mock的类。老猪俺想吃啥就mock啥。什么龙虾,鲍鱼,燕窝,象拔蚌,都不在话下。
public class FeastTest extends PorkMockTest {
  @Mock private Lobster lobster;
  @Mock private Oyster oyster;
  @Mock private BirdNest nest;
  @Mock private ElephantNoseClam clam;

  ...
}


俺摸,俺摸,俺使劲儿摸!哈哈。
   发表时间:2008-01-17  
引用

真实代码是用了一个cache来缓存每个class的所有@Mock field。不过这种技术细节我就不展开了。


1、如果使用cache的话,如果保证不同的测试方法不会有关联性?
   比如第一个测试方法中猪肉已经被咬一口了,第二个测试方法需要的是
   完整的猪肉,怎么恢复呢?
2、cache的控制是简单还是复杂?如果是复杂,还是用简单的方式吃猪肉
   比较爽:)
0 请登录后投票
   发表时间:2008-01-17  
yananay 写道
引用

真实代码是用了一个cache来缓存每个class的所有@Mock field。不过这种技术细节我就不展开了。


1、如果使用cache的话,如果保证不同的测试方法不会有关联性?
   比如第一个测试方法中猪肉已经被咬一口了,第二个测试方法需要的是
   完整的猪肉,怎么恢复呢?
2、cache的控制是简单还是复杂?如果是复杂,还是用简单的方式吃猪肉
   比较爽:)

cache的接口:
Field[] getMockFields(Class<? extends TestCase>);

setAccessible()很昂贵地,所以节省还是很可观地。
0 请登录后投票
   发表时间:2008-01-18  
可是你还是没回答我的第一个问题,既然有了cache,怎么保证第2个测试
一定能得到一个完整的猪肉?
0 请登录后投票
   发表时间:2008-01-18  
yananay 写道
可是你还是没回答我的第一个问题,既然有了cache,怎么保证第2个测试
一定能得到一个完整的猪肉?

cache只cache Field[]。每个测试实例都会被注射独立的mock instances。
0 请登录后投票
   发表时间:2008-01-18  
ajoo 写道
yananay 写道
可是你还是没回答我的第一个问题,既然有了cache,怎么保证第2个测试
一定能得到一个完整的猪肉?

cache只cache Field[]。每个测试实例都会被注射独立的mock instances。


这么说cache所作的只是 setAccessible ?
使用的时候再生成需要的 instance?
我的理解没错吧

最重要的是,不明白为什么放在 runBare 里就比放在 setup 里爽了?
0 请登录后投票
   发表时间:2008-01-18  
yananay 写道
ajoo 写道
yananay 写道
可是你还是没回答我的第一个问题,既然有了cache,怎么保证第2个测试
一定能得到一个完整的猪肉?

cache只cache Field[]。每个测试实例都会被注射独立的mock instances。


这么说cache所作的只是 setAccessible ?
使用的时候再生成需要的 instance?
我的理解没错吧

最重要的是,不明白为什么放在 runBare 里就比放在 setup 里爽了?


缓存了Class -> Field[]这个过程,也缓存了setAccessible。这两个过程都比较昂贵的。使用的时候再生成mock instance。

放在setUp()里,害怕子类覆盖的时候忘了super.setUp()。

而runBare()一般没人会override的。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics