论坛首页 综合技术论坛

mock框架搞什么搞?

浏览 31328 次
该帖已经被评为良好帖
作者 正文
   发表时间:2007-05-12  
taowen 写道
to raimundox:
这样的写法实际上就是把一个序列的两个动作分开做了。我先测试addActionListener被调用了。然后再调用Listener的actionPerformed的逻辑是正确的。我不知道这样做好不好。你也可以说把这两个放在一起测是功能测试。所以我认同你的说法。拿mock框架放到做功能测试的场景下,不适合。这个是显而易见的。因为mock框架造出来的对象无法持有状态。

to firebody:
MVP模式的状态在view上,把listener放到presenter上是不行的。你的观点和raimundox一样,也就是只针对listener的逻辑进行测试好了。

这个例子不是我生造出来的。去年写的一个rich client的媒体上传客户端的测试代码里面就有不少这样的东西。只是那个时候的jMock还是1.x而Matcher还是叫Constrain。意图也就是一个,力图辨析出适合使用mock框架的场合。


不是说我可以叫他功能测试,而是本来你写的测试粒度就是functional级别的,而且没有TDD,直接跳步了,如果TDD的话,用Fake的结论是可以自然推断出来的,而不用一开始就假象要用mock。
TDD第一步,你需要一个真实的View,而不是一开始就有一个interface,因为interface是抽象,没有具体而去抽象就是过渡设计,就是过早优化,因此你第一个测试可能是这样的:

in PresentTest
void testShouldAddTextChangeBehaviourToXXXView() {
   XXXView view = new XXXView();
   new Present(view);
   view.getButtonA().click();
   assertEqauls("Hello", view.getLabelA().getText();
}


很简单,意图也很明显,基本和你写的是一样的。然后当你有了第二个View,你应该有这样的测试

in PresentTest
void testShouldAddTextChangeBehaviourToYYYView() {
   YYYView view = new YYYView();
   new Present(view);
   view.getButtonB().click();
   assertEqauls("Hello", view.getLabelB().getText();
}


这里Present有重复的实现了(因为要针对不同的View类型作出实现),如果这时候如果你希望重构,你不会先动测试,而是改实现
XXXView implements View {

  void setText(String text) {
     getLabelA().setText(text);
  }
  void addActionListener(ActionListerner listerner) {
     ....
  }
}


YYYView同理,然后改Present,使之不依赖于具体View,然后跑测试,发现一切ok,这时候你可以选择重构测试,也可以选择不重构,如果你选择重构的话:
void testShouldAddTextChangeBehaviourToXXXView() {
   View view = new XXXView();
   new Present(view);
   pressButtonOn((XXXView) view);
   assertEqauls("Hello", labelTextOf((XXXView)view).getText();
}

void testShouldAddTextChangeBehaviourToYYYView() {
   View view = new YYYView();
   new Present(view);
   pressButtonOn((YYYView) view);
   assertEqauls("Hello", labelTextOf((YYYView)view).getText();
}

private void pressButtonOn(XXXView view)...
private String labelTextOf(XXXView view)...
private void pressButtonOn(YYYView view)...
private String labelTextOf(YYYView view)...


跑测试通过了,然后很容易发现这两个测试的相似性,然后提取共同的intention,就会发现,实际上我传去那个View是不重要的,因此用不用真正的实现无所谓,所以可以用一个Fake来代替
void testShouldAddTextChangeBehaviourToView() {
   WhateverView view = new WhateverView();
   new Present(view);
   view.pressButton();
   assertEqauls("Hello", view.getLabel().getText();
}


然后干掉其余的。

结论,使用mock的确要主义场合,但是如果严格TDD的话,保证每一步都不提前假设,不提前设计,不提前优化,不提前给出shortcut,大多数不需要用mock的场景都可以避免。
0 请登录后投票
   发表时间:2007-05-12  
那直接用view地实现来功能测试 也就足够了,没必要再多一个WhateverView来fake了。

这点倒似乎是最合理。

也不会违反taowen说的 “把listener放到view上的原则。”。

分层的好处虽然使得结构清晰,但是要真是TDD起来,mock那么多也是一种噩梦。

分层的好处更实在的作用倒不是“易于测试这点“,感觉还不如说使得 代码质量更高来的实在一些。


0 请登录后投票
   发表时间:2007-05-12  
to raimundox:
我明白你的意思。就是没有两个相似的具体的view的情况下,不需要一个抽象的view。也就是说不要一上来就套什么MVP。也就是说你认为使用MVP的唯一原因是因为View要可替换。但是实际情况中,比如view是Windows Forms,你拿到了button也是无法click的,因为button控件没有提供这个方法。所以很多时候应用MVP就是让界面上的代码可测试。所以才会有MockView的出现。

to firebody:
这个还没有到分层那个层次吧。我同意你的观点,代码质量更高是最实在的标准。
0 请登录后投票
   发表时间:2007-05-12  
taowen 写道
to raimundox:
我明白你的意思。就是没有两个相似的具体的view的情况下,不需要一个抽象的view。也就是说不要一上来就套什么MVP。也就是说你认为使用MVP的唯一原因是因为View要可替换。但是实际情况中,比如view是Windows Forms,你拿到了button也是无法click的,因为button控件没有提供这个方法。所以很多时候应用MVP就是让界面上的代码可测试。所以才会有MockView的出现。


嘿嘿,这个我可没说。通篇我都没讨论MVP,我说的是Mock。我想说的是: 如果你按照TDD的方法来作,起码不会这么早就出现一个View interface,也就不会过早地去想要mock了。

p.s. 就算是WinForms吧,我相信其实WinForms可以更简单,且更不用mock,因为有delegate啊,你可以直接测delegate的方法,保证细力度行为正确,然后测试Present做了正确的装配。一样是连mock都不用要的啊
0 请登录后投票
   发表时间:2007-05-12  
taowen 写道
to raimundox:
但是实际情况中,比如view是Windows Forms,你拿到了button也是无法click的,因为button控件没有提供这个方法。所以很多时候应用MVP就是让界面上的代码可测试。所以才会有MockView的出现。


再ps一下,WinForms里不能掉Click吗?Click事件不是一个delegate吗?对于这个例子难道不可以

view.buttom.Click(DUMMY_SENDER, DUMMY_EVENT_ARGS) ?

当然我对.net没啥了解,这个是瞎猜的...
0 请登录后投票
   发表时间:2007-05-12  
引用

view.buttom.Click(DUMMY_SENDER, DUMMY_EVENT_ARGS)

这个是不行的。event(即delegate)在其所声明的类之外是不能触发的,只能通过+=来添加listener。如果不用反射,很多控件的事件是很难触发的。所以才会有一个view接口出现。之所以有这个view接口出现是因为TDD,而不是因为不TDD。如果不测试,根本不用去考虑如何触发一个界面上的事件这样的问题。因为要测试,所以才会弄出来一个view接口。然后把逻辑分离到presenter里面。通过mockView来驱动presenter的设计。如果东西都写到了view里面,而写界面的框架又很难去模拟鼠标点击之类的事件。这样做的结果就是导致所有的测试都是通过手工来做。
0 请登录后投票
   发表时间:2007-05-12  
taowen 写道

如果不用反射,很多控件的事件是很难触发的。所以才会有一个view接口出现。之所以有这个view接口出现是因为TDD,而不是因为不TDD。


嘿嘿,同学,但是你也没在View上定义出发的方法啊,如果你说TDD出来,用一个interface来做placeholder,那么也好,至少第一步代码是这样的

public void testShouldXXX() {
   View view = createView(); //空方法,不考虑实现
   new Present(view);
   view.clickButton();
   assertEqauls("Hello", view.getLabelText());
}

好了表达出意图了,那么再看接口,因为View是个接口,不希望因为测试多放几个方法,那么改,第二步:

public void testShouldXXX() {
   View view = createView(); //空方法,不考虑实现
   new Present(view);
   clickButtonOn(view); //空方法,不考虑实现
   assertEqauls("Hello", textOn(view)); //空方法,不考虑实现
}

好开始之前,考虑测试这里怎么实现,基本上两种选择,fake实现createView, clickButtonOn, textOn方法或着mock实现createView, clickButtonOn, textOn。正如你之前说的,如果真是环境下没有办法很简单的去click button的话。那么显然mock走不通,fake也是自然的选择。
总之,你的这个例子,用fake是TDD的自然推论,而用mock也自然是用错了地方。

0 请登录后投票
   发表时间:2007-05-12  
用真实的view来测,用mock框架造一个来测,自己手写一个假的来测。三者没有谁推出谁的关系。我只是要说明一,在这里用MVP没有你说的过度设计的问题。二,这里用mock框架很笨拙。

对于你提到的设计步骤的问题。我同意你的看法。下次写代码的时候会注意用小步骤前进。最终的view接口是这样的没有什么问题吧?难道小步骤TDD会出现另外一个版本的view接口?view是如何得出来的,不是本帖的重点。我当然可以把你所说的两个步骤给隐匿掉。
0 请登录后投票
   发表时间:2007-05-12  
taowen 写道
用真实的view来测,用mock框架造一个来测,自己手写一个假的来测。三者没有谁推出谁的关系。我只是要说明一,在这里用MVP没有你说的过度设计的问题。二,这里用mock框架很笨拙。

对于你提到的设计步骤的问题。我同意你的看法。下次写代码的时候会注意用小步骤前进。最终的view接口是这样的没有什么问题吧?难道小步骤TDD会出现另外一个版本的view接口?


呵呵,我没说MVP是过渡设计,我说的是View interface...毕竟我受不了上来就一个接口...怎么也得有两个实现类吧...
0 请登录后投票
   发表时间:2007-05-12  
taowen 写道
view是如何得出来的,不是本帖的重点。我当然可以把你所说的两个步骤给隐匿掉。


哎...过程是重要的...没有过程就看出不来哪里做了决策,也就没有办法理解你这里为啥要用mock。如果按我上面列出的过程,看不出有用mock的必要,因此也就不用指责什么了。
个人意见觉得这里你硬说是mock的复杂有点论据不足。
0 请登录后投票
论坛首页 综合技术版

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