锁定老帖子 主题:mock框架搞什么搞?
该帖已经被评为良好帖
|
|
---|---|
作者 | 正文 |
发表时间: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的场景都可以避免。 |
|
返回顶楼 | |
发表时间:2007-05-12
那直接用view地实现来功能测试 也就足够了,没必要再多一个WhateverView来fake了。
这点倒似乎是最合理。 也不会违反taowen说的 “把listener放到view上的原则。”。 分层的好处虽然使得结构清晰,但是要真是TDD起来,mock那么多也是一种噩梦。 分层的好处更实在的作用倒不是“易于测试这点“,感觉还不如说使得 代码质量更高来的实在一些。 |
|
返回顶楼 | |
发表时间:2007-05-12
to raimundox:
我明白你的意思。就是没有两个相似的具体的view的情况下,不需要一个抽象的view。也就是说不要一上来就套什么MVP。也就是说你认为使用MVP的唯一原因是因为View要可替换。但是实际情况中,比如view是Windows Forms,你拿到了button也是无法click的,因为button控件没有提供这个方法。所以很多时候应用MVP就是让界面上的代码可测试。所以才会有MockView的出现。 to firebody: 这个还没有到分层那个层次吧。我同意你的观点,代码质量更高是最实在的标准。 |
|
返回顶楼 | |
发表时间: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都不用要的啊 |
|
返回顶楼 | |
发表时间: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没啥了解,这个是瞎猜的... |
|
返回顶楼 | |
发表时间:2007-05-12
引用 view.buttom.Click(DUMMY_SENDER, DUMMY_EVENT_ARGS) 这个是不行的。event(即delegate)在其所声明的类之外是不能触发的,只能通过+=来添加listener。如果不用反射,很多控件的事件是很难触发的。所以才会有一个view接口出现。之所以有这个view接口出现是因为TDD,而不是因为不TDD。如果不测试,根本不用去考虑如何触发一个界面上的事件这样的问题。因为要测试,所以才会弄出来一个view接口。然后把逻辑分离到presenter里面。通过mockView来驱动presenter的设计。如果东西都写到了view里面,而写界面的框架又很难去模拟鼠标点击之类的事件。这样做的结果就是导致所有的测试都是通过手工来做。 |
|
返回顶楼 | |
发表时间: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也自然是用错了地方。 |
|
返回顶楼 | |
发表时间:2007-05-12
用真实的view来测,用mock框架造一个来测,自己手写一个假的来测。三者没有谁推出谁的关系。我只是要说明一,在这里用MVP没有你说的过度设计的问题。二,这里用mock框架很笨拙。
对于你提到的设计步骤的问题。我同意你的看法。下次写代码的时候会注意用小步骤前进。最终的view接口是这样的没有什么问题吧?难道小步骤TDD会出现另外一个版本的view接口?view是如何得出来的,不是本帖的重点。我当然可以把你所说的两个步骤给隐匿掉。 |
|
返回顶楼 | |
发表时间:2007-05-12
taowen 写道 用真实的view来测,用mock框架造一个来测,自己手写一个假的来测。三者没有谁推出谁的关系。我只是要说明一,在这里用MVP没有你说的过度设计的问题。二,这里用mock框架很笨拙。
对于你提到的设计步骤的问题。我同意你的看法。下次写代码的时候会注意用小步骤前进。最终的view接口是这样的没有什么问题吧?难道小步骤TDD会出现另外一个版本的view接口? 呵呵,我没说MVP是过渡设计,我说的是View interface...毕竟我受不了上来就一个接口...怎么也得有两个实现类吧... |
|
返回顶楼 | |
发表时间:2007-05-12
taowen 写道 view是如何得出来的,不是本帖的重点。我当然可以把你所说的两个步骤给隐匿掉。
哎...过程是重要的...没有过程就看出不来哪里做了决策,也就没有办法理解你这里为啥要用mock。如果按我上面列出的过程,看不出有用mock的必要,因此也就不用指责什么了。 个人意见觉得这里你硬说是mock的复杂有点论据不足。 |
|
返回顶楼 | |