- 浏览: 193895 次
- 性别:
- 来自: 北京
最新评论
-
bzhao:
开启了一个很牛逼的话题!
提高程序员的准入门槛? -
迷人阳光love:
不知两年多了,lz整理的从问题出发的模式是否还在??很是渴望得 ...
学习模式,不如先了解问题 -
迷人阳光love:
lz说到了我的心坎里,这是您10年发的文章,现在 也没找到一个 ...
学习模式,不如先了解问题 -
toafu:
我的理解是,持续集成和交付也解决不了人的问题。
为什么我的敏捷项目有如此多的问题? -
liaofeng_xiao:
《不可承受的生命之轻》 ,真没看出太多内容~
近期看的书和电影
今天早上一时兴起,去网上下载下来JMock,EasyMock最新版本来玩玩。用来测试的行为很简单。就是有一个窗体,上面有一个文本框,一个按钮。如果点击按钮,就会把文本框的内容设置为“Hello”。应用MVP模式,这个行为应该在Presenter中,而View接口应该是这样的:
我这里就不TDD了,直接给出Presenter的实现,然后我们来看JMock和EasyMock如何处理这个问题。
好的,我先来写一段伪测试代码:
创建MockView
创建Presenter,传以MockView做为参数
在MockView上触发事件
验证MockView的SetText方法被调用了
然后就用JMock来实现这段伪代码。
由于JMock没有对触发事件提供直接的支持,所以是自己写了一个ActionListenerMatcher来达到这个目的的。这个Matcher的实现如下:
可以看到之所以要这个Matcher是因为,我们需要有一个地方来保存Presenter传进来的listener,并且提供一个用来触发事件的方法。
EasyMock的实现与之非常类似,此处不再赘述。代码可以参见附件。
我们可以看到什么?我看到的是,测试时候的Intention(意图)完全掩盖在冗长复杂的代码之中了。如果不用Mock该怎么做?很简单:
这段代码是不是和上面的伪代码一模一样?MockView是这样的:
做完这个实验,我不得不说。Mock框架,搞什么搞!简单一点不好么?
哎...过程是重要的...没有过程就看出不来哪里做了决策,也就没有办法理解你这里为啥要用mock。如果按我上面列出的过程,看不出有用mock的必要,因此也就不用指责什么了。
个人意见觉得这里你硬说是mock的复杂有点论据不足。
复杂的论据有:
完全是为了mock框架引入的代码,什么是Mockery?
匿名类加静态构造函数?这是表达何意思?
为什么要去一个Matcher上触发一个事件。Matcher,顾名思义,那是用来检查参数是否匹配的啊。
所以说,你是对的。这里没有必要用mock框架。但是这里确实有必要造一个假的,而不是用真的view来测试。所以这里需要用mock框架之外的办法来造假的。
如果造个假的,问题是这个工作量具体如何呢? 如果是一个简单的仿造监听器注册器的stub,那似乎还可以接受,不过TDD开始的,针对这个需求,或许很多人写出的测试代码都可能如下:
不知道view的stub需要迎合多少这样的需求?
哎...过程是重要的...没有过程就看出不来哪里做了决策,也就没有办法理解你这里为啥要用mock。如果按我上面列出的过程,看不出有用mock的必要,因此也就不用指责什么了。
个人意见觉得这里你硬说是mock的复杂有点论据不足。
复杂的论据有:
完全是为了mock框架引入的代码,什么是Mockery?
匿名类加静态构造函数?这是表达何意思?
为什么要去一个Matcher上触发一个事件。Matcher,顾名思义,那是用来检查参数是否匹配的啊。
所以说,你是对的。这里没有必要用mock框架。但是这里确实有必要造一个假的,而不是用真的view来测试。所以这里需要用mock框架之外的办法来造假的。
呵呵,我没说MVP是过渡设计,我说的是View interface...毕竟我受不了上来就一个接口...怎么也得有两个实现类吧...
两个实现类是有的啊。一个是真实的View。一个是测试用的View。
哎...过程是重要的...没有过程就看出不来哪里做了决策,也就没有办法理解你这里为啥要用mock。如果按我上面列出的过程,看不出有用mock的必要,因此也就不用指责什么了。
个人意见觉得这里你硬说是mock的复杂有点论据不足。
呵呵,我没说MVP是过渡设计,我说的是View interface...毕竟我受不了上来就一个接口...怎么也得有两个实现类吧...
如果不用反射,很多控件的事件是很难触发的。所以才会有一个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也自然是用错了地方。
view.buttom.Click(DUMMY_SENDER, DUMMY_EVENT_ARGS)
这个是不行的。event(即delegate)在其所声明的类之外是不能触发的,只能通过+=来添加listener。如果不用反射,很多控件的事件是很难触发的。所以才会有一个view接口出现。之所以有这个view接口出现是因为TDD,而不是因为不TDD。如果不测试,根本不用去考虑如何触发一个界面上的事件这样的问题。因为要测试,所以才会弄出来一个view接口。然后把逻辑分离到presenter里面。通过mockView来驱动presenter的设计。如果东西都写到了view里面,而写界面的框架又很难去模拟鼠标点击之类的事件。这样做的结果就是导致所有的测试都是通过手工来做。
再ps一下,WinForms里不能掉Click吗?Click事件不是一个delegate吗?对于这个例子难道不可以
view.buttom.Click(DUMMY_SENDER, DUMMY_EVENT_ARGS) ?
当然我对.net没啥了解,这个是瞎猜的...
嘿嘿,这个我可没说。通篇我都没讨论MVP,我说的是Mock。我想说的是: 如果你按照TDD的方法来作,起码不会这么早就出现一个View interface,也就不会过早地去想要mock了。
p.s. 就算是WinForms吧,我相信其实WinForms可以更简单,且更不用mock,因为有delegate啊,你可以直接测delegate的方法,保证细力度行为正确,然后测试Present做了正确的装配。一样是连mock都不用要的啊
不是说我可以叫他功能测试,而是本来你写的测试粒度就是functional级别的,而且没有TDD,直接跳步了,如果TDD的话,用Fake的结论是可以自然推断出来的,而不用一开始就假象要用mock。
TDD第一步,你需要一个真实的View,而不是一开始就有一个interface,因为interface是抽象,没有具体而去抽象就是过渡设计,就是过早优化,因此你第一个测试可能是这样的:
in PresentTest
很简单,意图也很明显,基本和你写的是一样的。然后当你有了第二个View,你应该有这样的测试
in PresentTest
这里Present有重复的实现了(因为要针对不同的View类型作出实现),如果这时候如果你希望重构,你不会先动测试,而是改实现
YYYView同理,然后改Present,使之不依赖于具体View,然后跑测试,发现一切ok,这时候你可以选择重构测试,也可以选择不重构,如果你选择重构的话:
跑测试通过了,然后很容易发现这两个测试的相似性,然后提取共同的intention,就会发现,实际上我传去那个View是不重要的,因此用不用真正的实现无所谓,所以可以用一个Fake来代替
然后干掉其余的。
结论,使用mock的确要主义场合,但是如果严格TDD的话,保证每一步都不提前假设,不提前设计,不提前优化,不提前给出shortcut,大多数不需要用mock的场景都可以避免。
也没必要用到stub .写起来也很是麻烦。
直接针对分离的业务逻辑作测试。当然,前提是代码里面已经把界面和业务逻辑分离好了。
如果按照楼主这样写出来的测试代码,我感觉没法看了,即使看得也很费力。
public interface View { public void setText(String text); public void addActionListener(ActionListener actionListener); }
我这里就不TDD了,直接给出Presenter的实现,然后我们来看JMock和EasyMock如何处理这个问题。
public class Presenter { public Presenter(final View view) { view.addActionListener(new ActionListener() { public void actionPerformed() { view.setText("Hello"); } }); } }
好的,我先来写一段伪测试代码:
引用
创建MockView
创建Presenter,传以MockView做为参数
在MockView上触发事件
验证MockView的SetText方法被调用了
然后就用JMock来实现这段伪代码。
@Test public void test_click_button_should_set_text_hello() { Mockery mockery = new Mockery(); final View mockView = mockery.mock(View.class); final ActionListenerMatcher actionListenerMatcher = new ActionListenerMatcher(); mockery.checking(new Expectations() { { one(mockView).addActionListener( with(actionListenerMatcher)); one(mockView).setText("Hello"); } }); new Presenter(mockView); actionListenerMatcher.fireActionPerformed(); mockery.assertIsSatisfied(); }
由于JMock没有对触发事件提供直接的支持,所以是自己写了一个ActionListenerMatcher来达到这个目的的。这个Matcher的实现如下:
public class ActionListenerMatcher extends BaseMatcher<ActionListener> { private ActionListener actionListener; public boolean matches(Object item) { actionListener = (ActionListener) item; return true; } public void fireActionPerformed() { actionListener.actionPerformed(); } public void describeTo(Description description) { } }
可以看到之所以要这个Matcher是因为,我们需要有一个地方来保存Presenter传进来的listener,并且提供一个用来触发事件的方法。
EasyMock的实现与之非常类似,此处不再赘述。代码可以参见附件。
我们可以看到什么?我看到的是,测试时候的Intention(意图)完全掩盖在冗长复杂的代码之中了。如果不用Mock该怎么做?很简单:
@Test public void test_click_button_should_set_text_hello() { MockView mockView = new MockView(); new Presenter(mockView); mockView.fireActionPerformed(); Assert.assertEquals("Hello", mockView.getText()); }
这段代码是不是和上面的伪代码一模一样?MockView是这样的:
private class MockView implements View { private ActionListener actionListener; private String text; public void addActionListener(ActionListener actionListener) { this.actionListener = actionListener; } public void setText(String text) { this.text = text; } public String getText() { return text; } public void fireActionPerformed() { actionListener.actionPerformed(); } }
做完这个实验,我不得不说。Mock框架,搞什么搞!简单一点不好么?
- MockComparison.zip (4.8 KB)
- 描述: 测试代码
- 下载次数: 93
评论
23 楼
amonlei
2007-05-16
走路和开车上楼梯,骂车没有人快。。。。
22 楼
firebody
2007-05-12
taowen 写道
raimundox 写道
taowen 写道
view是如何得出来的,不是本帖的重点。我当然可以把你所说的两个步骤给隐匿掉。
哎...过程是重要的...没有过程就看出不来哪里做了决策,也就没有办法理解你这里为啥要用mock。如果按我上面列出的过程,看不出有用mock的必要,因此也就不用指责什么了。
个人意见觉得这里你硬说是mock的复杂有点论据不足。
复杂的论据有:
Mockery mockery = new Mockery();
完全是为了mock框架引入的代码,什么是Mockery?
mockery.checking(new Expectations() { { ... } });
匿名类加静态构造函数?这是表达何意思?
actionListenerMatcher.fireActionPerformed();
为什么要去一个Matcher上触发一个事件。Matcher,顾名思义,那是用来检查参数是否匹配的啊。
所以说,你是对的。这里没有必要用mock框架。但是这里确实有必要造一个假的,而不是用真的view来测试。所以这里需要用mock框架之外的办法来造假的。
如果造个假的,问题是这个工作量具体如何呢? 如果是一个简单的仿造监听器注册器的stub,那似乎还可以接受,不过TDD开始的,针对这个需求,或许很多人写出的测试代码都可能如下:
view.clickButton(); assertEquals("hello",view.getFieldText());
不知道view的stub需要迎合多少这样的需求?
21 楼
taowen
2007-05-12
raimundox 写道
taowen 写道
view是如何得出来的,不是本帖的重点。我当然可以把你所说的两个步骤给隐匿掉。
哎...过程是重要的...没有过程就看出不来哪里做了决策,也就没有办法理解你这里为啥要用mock。如果按我上面列出的过程,看不出有用mock的必要,因此也就不用指责什么了。
个人意见觉得这里你硬说是mock的复杂有点论据不足。
复杂的论据有:
Mockery mockery = new Mockery();
完全是为了mock框架引入的代码,什么是Mockery?
mockery.checking(new Expectations() { { ... } });
匿名类加静态构造函数?这是表达何意思?
actionListenerMatcher.fireActionPerformed();
为什么要去一个Matcher上触发一个事件。Matcher,顾名思义,那是用来检查参数是否匹配的啊。
所以说,你是对的。这里没有必要用mock框架。但是这里确实有必要造一个假的,而不是用真的view来测试。所以这里需要用mock框架之外的办法来造假的。
20 楼
taowen
2007-05-12
raimundox 写道
taowen 写道
用真实的view来测,用mock框架造一个来测,自己手写一个假的来测。三者没有谁推出谁的关系。我只是要说明一,在这里用MVP没有你说的过度设计的问题。二,这里用mock框架很笨拙。
对于你提到的设计步骤的问题。我同意你的看法。下次写代码的时候会注意用小步骤前进。最终的view接口是这样的没有什么问题吧?难道小步骤TDD会出现另外一个版本的view接口?
对于你提到的设计步骤的问题。我同意你的看法。下次写代码的时候会注意用小步骤前进。最终的view接口是这样的没有什么问题吧?难道小步骤TDD会出现另外一个版本的view接口?
呵呵,我没说MVP是过渡设计,我说的是View interface...毕竟我受不了上来就一个接口...怎么也得有两个实现类吧...
两个实现类是有的啊。一个是真实的View。一个是测试用的View。
19 楼
raimundox
2007-05-12
taowen 写道
view是如何得出来的,不是本帖的重点。我当然可以把你所说的两个步骤给隐匿掉。
哎...过程是重要的...没有过程就看出不来哪里做了决策,也就没有办法理解你这里为啥要用mock。如果按我上面列出的过程,看不出有用mock的必要,因此也就不用指责什么了。
个人意见觉得这里你硬说是mock的复杂有点论据不足。
18 楼
raimundox
2007-05-12
taowen 写道
用真实的view来测,用mock框架造一个来测,自己手写一个假的来测。三者没有谁推出谁的关系。我只是要说明一,在这里用MVP没有你说的过度设计的问题。二,这里用mock框架很笨拙。
对于你提到的设计步骤的问题。我同意你的看法。下次写代码的时候会注意用小步骤前进。最终的view接口是这样的没有什么问题吧?难道小步骤TDD会出现另外一个版本的view接口?
对于你提到的设计步骤的问题。我同意你的看法。下次写代码的时候会注意用小步骤前进。最终的view接口是这样的没有什么问题吧?难道小步骤TDD会出现另外一个版本的view接口?
呵呵,我没说MVP是过渡设计,我说的是View interface...毕竟我受不了上来就一个接口...怎么也得有两个实现类吧...
17 楼
taowen
2007-05-12
用真实的view来测,用mock框架造一个来测,自己手写一个假的来测。三者没有谁推出谁的关系。我只是要说明一,在这里用MVP没有你说的过度设计的问题。二,这里用mock框架很笨拙。
对于你提到的设计步骤的问题。我同意你的看法。下次写代码的时候会注意用小步骤前进。最终的view接口是这样的没有什么问题吧?难道小步骤TDD会出现另外一个版本的view接口?view是如何得出来的,不是本帖的重点。我当然可以把你所说的两个步骤给隐匿掉。
对于你提到的设计步骤的问题。我同意你的看法。下次写代码的时候会注意用小步骤前进。最终的view接口是这样的没有什么问题吧?难道小步骤TDD会出现另外一个版本的view接口?view是如何得出来的,不是本帖的重点。我当然可以把你所说的两个步骤给隐匿掉。
16 楼
raimundox
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也自然是用错了地方。
15 楼
taowen
2007-05-12
引用
view.buttom.Click(DUMMY_SENDER, DUMMY_EVENT_ARGS)
这个是不行的。event(即delegate)在其所声明的类之外是不能触发的,只能通过+=来添加listener。如果不用反射,很多控件的事件是很难触发的。所以才会有一个view接口出现。之所以有这个view接口出现是因为TDD,而不是因为不TDD。如果不测试,根本不用去考虑如何触发一个界面上的事件这样的问题。因为要测试,所以才会弄出来一个view接口。然后把逻辑分离到presenter里面。通过mockView来驱动presenter的设计。如果东西都写到了view里面,而写界面的框架又很难去模拟鼠标点击之类的事件。这样做的结果就是导致所有的测试都是通过手工来做。
14 楼
raimundox
2007-05-12
taowen 写道
to raimundox:
但是实际情况中,比如view是Windows Forms,你拿到了button也是无法click的,因为button控件没有提供这个方法。所以很多时候应用MVP就是让界面上的代码可测试。所以才会有MockView的出现。
但是实际情况中,比如view是Windows Forms,你拿到了button也是无法click的,因为button控件没有提供这个方法。所以很多时候应用MVP就是让界面上的代码可测试。所以才会有MockView的出现。
再ps一下,WinForms里不能掉Click吗?Click事件不是一个delegate吗?对于这个例子难道不可以
view.buttom.Click(DUMMY_SENDER, DUMMY_EVENT_ARGS) ?
当然我对.net没啥了解,这个是瞎猜的...
13 楼
raimundox
2007-05-12
taowen 写道
to raimundox:
我明白你的意思。就是没有两个相似的具体的view的情况下,不需要一个抽象的view。也就是说不要一上来就套什么MVP。也就是说你认为使用MVP的唯一原因是因为View要可替换。但是实际情况中,比如view是Windows Forms,你拿到了button也是无法click的,因为button控件没有提供这个方法。所以很多时候应用MVP就是让界面上的代码可测试。所以才会有MockView的出现。
我明白你的意思。就是没有两个相似的具体的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都不用要的啊
12 楼
taowen
2007-05-12
to raimundox:
我明白你的意思。就是没有两个相似的具体的view的情况下,不需要一个抽象的view。也就是说不要一上来就套什么MVP。也就是说你认为使用MVP的唯一原因是因为View要可替换。但是实际情况中,比如view是Windows Forms,你拿到了button也是无法click的,因为button控件没有提供这个方法。所以很多时候应用MVP就是让界面上的代码可测试。所以才会有MockView的出现。
to firebody:
这个还没有到分层那个层次吧。我同意你的观点,代码质量更高是最实在的标准。
我明白你的意思。就是没有两个相似的具体的view的情况下,不需要一个抽象的view。也就是说不要一上来就套什么MVP。也就是说你认为使用MVP的唯一原因是因为View要可替换。但是实际情况中,比如view是Windows Forms,你拿到了button也是无法click的,因为button控件没有提供这个方法。所以很多时候应用MVP就是让界面上的代码可测试。所以才会有MockView的出现。
to firebody:
这个还没有到分层那个层次吧。我同意你的观点,代码质量更高是最实在的标准。
11 楼
firebody
2007-05-12
那直接用view地实现来功能测试 也就足够了,没必要再多一个WhateverView来fake了。
这点倒似乎是最合理。
也不会违反taowen说的 “把listener放到view上的原则。”。
分层的好处虽然使得结构清晰,但是要真是TDD起来,mock那么多也是一种噩梦。
分层的好处更实在的作用倒不是“易于测试这点“,感觉还不如说使得 代码质量更高来的实在一些。
这点倒似乎是最合理。
也不会违反taowen说的 “把listener放到view上的原则。”。
分层的好处虽然使得结构清晰,但是要真是TDD起来,mock那么多也是一种噩梦。
分层的好处更实在的作用倒不是“易于测试这点“,感觉还不如说使得 代码质量更高来的实在一些。
10 楼
raimundox
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框架的场合。
这样的写法实际上就是把一个序列的两个动作分开做了。我先测试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的场景都可以避免。
9 楼
taowen
2007-05-12
to raimundox:
这样的写法实际上就是把一个序列的两个动作分开做了。我先测试addActionListener被调用了。然后再调用Listener的actionPerformed的逻辑是正确的。我不知道这样做好不好。你也可以说把这两个放在一起测是功能测试。所以我认同你的说法。拿mock框架放到做功能测试的场景下,不适合。这个是显而易见的。因为mock框架造出来的对象无法持有状态。
to firebody:
MVP模式的状态在view上,把listener放到presenter上是不行的。你的观点和raimundox一样,也就是只针对listener的逻辑进行测试好了。
这个例子不是我生造出来的。去年写的一个rich client的媒体上传客户端的测试代码里面就有不少这样的东西。只是那个时候的jMock还是1.x而Matcher还是叫Constrain。意图也就是一个,力图辨析出适合使用mock框架的场合。
这样的写法实际上就是把一个序列的两个动作分开做了。我先测试addActionListener被调用了。然后再调用Listener的actionPerformed的逻辑是正确的。我不知道这样做好不好。你也可以说把这两个放在一起测是功能测试。所以我认同你的说法。拿mock框架放到做功能测试的场景下,不适合。这个是显而易见的。因为mock框架造出来的对象无法持有状态。
to firebody:
MVP模式的状态在view上,把listener放到presenter上是不行的。你的观点和raimundox一样,也就是只针对listener的逻辑进行测试好了。
这个例子不是我生造出来的。去年写的一个rich client的媒体上传客户端的测试代码里面就有不少这样的东西。只是那个时候的jMock还是1.x而Matcher还是叫Constrain。意图也就是一个,力图辨析出适合使用mock框架的场合。
8 楼
firebody
2007-05-12
几个不协调的地方:
主要的逻辑是:
触发按钮事件--〉 调用view.setText()
测试代码如同楼主的代码:
view是一个接口,楼主的测试代码对view进行了mock .
然而,我们看楼主Presenter地实现:
这里面对于需要测试的逻辑:
“ 触发按钮事件--〉 调用view.setText() “
的骨干代码 :
这里的代码意味着逻辑的实现需要依赖界面view对注册的actionListener的调用。
但是测试依赖的功能类只有 Presenter,View .其中 view是接口,而Presenter仅仅调用了view的接口来注册listener ,这些代码逻辑仅仅完成了 需要测试的逻辑的功能代码的一部分。
而现在 为了让测试通过,却需要很多mock代码或者stub代码来完成这些逻辑。 本是功能代码需要实现的逻辑却让mock/stub代码来实现。 有点本末倒置了的感觉。
对于这样的测试,感觉还不如把Actionlistener注册的实现放到presenter来的好一些,也不用依赖于view了,当然这样可能带来一些问题,这个建议仅仅是针对这个例子而已。
如果不能把Listener脱离view,还不如直接针对Listener坐针对性的测试好了,至于集成的测试选择一个好一点界面验收框架,让这些验收测试对 Presenter ,View这里面“少而且简单“的代码做一个集成测试好了。
主要的逻辑是:
触发按钮事件--〉 调用view.setText()
测试代码如同楼主的代码:
@Test public void test_click_button_should_set_text_hello() { MockView mockView = new MockView(); new Presenter(mockView); mockView.fireActionPerformed(); Assert.assertEquals("Hello", mockView.getText()); }
view是一个接口,楼主的测试代码对view进行了mock .
然而,我们看楼主Presenter地实现:
public class Presenter { public Presenter(final View view) { view.addActionListener(new ActionListener() { public void actionPerformed() { view.setText("Hello"); } }); } }
这里面对于需要测试的逻辑:
“ 触发按钮事件--〉 调用view.setText() “
的骨干代码 :
view.addActionListener(new ActionListener() { public void actionPerformed() { view.setText("Hello"); } });
这里的代码意味着逻辑的实现需要依赖界面view对注册的actionListener的调用。
但是测试依赖的功能类只有 Presenter,View .其中 view是接口,而Presenter仅仅调用了view的接口来注册listener ,这些代码逻辑仅仅完成了 需要测试的逻辑的功能代码的一部分。
而现在 为了让测试通过,却需要很多mock代码或者stub代码来完成这些逻辑。 本是功能代码需要实现的逻辑却让mock/stub代码来实现。 有点本末倒置了的感觉。
对于这样的测试,感觉还不如把Actionlistener注册的实现放到presenter来的好一些,也不用依赖于view了,当然这样可能带来一些问题,这个建议仅仅是针对这个例子而已。
如果不能把Listener脱离view,还不如直接针对Listener坐针对性的测试好了,至于集成的测试选择一个好一点界面验收框架,让这些验收测试对 Presenter ,View这里面“少而且简单“的代码做一个集成测试好了。
7 楼
yananay
2007-05-11
我更愿意使用 httpunit
6 楼
raimundox
2007-05-11
就拿你这个例子说吧,你这个例子的一些特点都指向Fake而不是Mock:
首先,View是一个interface,且上面没有触发事件的方法,也就是说,仅仅mock interface根本没法触发事件,所以才有了你的matcher.
其次,ActionListener是inner class,action的实例都拿不到。
第三,显然你这个不是TDD出来的,测试的意图有很明显的功能测试的意思,而mock恰恰多数是用于unit test的。
因此用Fake是最直接的做法。
而利于mock的写法,是把测试分开,ActionListener和Present来测, 且不用inner class
in PresentTest
public void testShouldAddTextChangeBehaviourToView {
View mockView = controlMock(View.class);
ActionListener expectedListener = new TextChangeActionListener(DUMMY_VIEW);
mockView.addActionListener(expectedListener);
replay(mockView);
new Present(mockView, expectedListener); //一般来说如果Action出去了,就得DI了,这里假设constructor DI
verify(mockView);
}
in TextChangeActionListenerTest
public void testShouldChangeTextOfView() {
View mockView = controlMock(View.class);
mockView.setText("Hello");
replay(mockView);
new TextChangeActionListener(mockView).actionPerformed();
verify(mockView);
}
当然这个实现是否是好的,很难讲。不过个人不是很喜欢inner class的做法,不好TDD啊...
首先,View是一个interface,且上面没有触发事件的方法,也就是说,仅仅mock interface根本没法触发事件,所以才有了你的matcher.
其次,ActionListener是inner class,action的实例都拿不到。
第三,显然你这个不是TDD出来的,测试的意图有很明显的功能测试的意思,而mock恰恰多数是用于unit test的。
因此用Fake是最直接的做法。
而利于mock的写法,是把测试分开,ActionListener和Present来测, 且不用inner class
in PresentTest
public void testShouldAddTextChangeBehaviourToView {
View mockView = controlMock(View.class);
ActionListener expectedListener = new TextChangeActionListener(DUMMY_VIEW);
mockView.addActionListener(expectedListener);
replay(mockView);
new Present(mockView, expectedListener); //一般来说如果Action出去了,就得DI了,这里假设constructor DI
verify(mockView);
}
in TextChangeActionListenerTest
public void testShouldChangeTextOfView() {
View mockView = controlMock(View.class);
mockView.setText("Hello");
replay(mockView);
new TextChangeActionListener(mockView).actionPerformed();
verify(mockView);
}
当然这个实现是否是好的,很难讲。不过个人不是很喜欢inner class的做法,不好TDD啊...
5 楼
firebody
2007-05-11
raimundox 写道
你给的例子不是Mock,至多是Fake。各种区别见马大叔的Mocks Aren't Stubs:
* Dummy objects are passed around but never actually used. Usually they are just used to fill parameter lists.
* Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an in memory database is a good example).
* Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test. Stubs may also record information about calls, such as an email gateway stub that remembers the messages it 'sent', or maybe only how many messages it 'sent'.
* Mocks are what we are talking about here: objects pre-programmed with expectations which form a specification of the calls they are expected to receive.
因此实际上,不是JMock或EasyMock框架复杂,而是你给的例子中,Mock不是最佳选择,如果View里东西再多一点,Fake可能就不太合适了。
* Dummy objects are passed around but never actually used. Usually they are just used to fill parameter lists.
* Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an in memory database is a good example).
* Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test. Stubs may also record information about calls, such as an email gateway stub that remembers the messages it 'sent', or maybe only how many messages it 'sent'.
* Mocks are what we are talking about here: objects pre-programmed with expectations which form a specification of the calls they are expected to receive.
因此实际上,不是JMock或EasyMock框架复杂,而是你给的例子中,Mock不是最佳选择,如果View里东西再多一点,Fake可能就不太合适了。
也没必要用到stub .写起来也很是麻烦。
直接针对分离的业务逻辑作测试。当然,前提是代码里面已经把界面和业务逻辑分离好了。
如果按照楼主这样写出来的测试代码,我感觉没法看了,即使看得也很费力。
4 楼
taowen
2007-05-11
像我上面那样用jMock的就很多的。用mock框架写测试的时候,不知不觉地就写出了很多本来不适合用mock框架解决的问题。所以fake也好,stub也好。都是要告诉大家,用之前先考虑考虑什么是最简单的解决办法。这dummy,fake,stub与mock的分别太过于微妙。只要能够达到目的就好了,管它叫什么名字呢?我觉得可以拿各种测试中需要用到的“mock”的场景拿出来,用各种实现方式比较一下,看看jMock这些框架到底在什么情况下写出来的代码行数更少,而且更好理解。我觉得不会太多。
发表评论
-
Guice这高级货
2009-06-15 15:17 6940Guice在大部分时间都是很方便的,简单易用。Guice和Sp ... -
再论领域模型的困境
2009-06-03 18:54 2080距离上次发帖讨论领域 ... -
让AnnotationConfiguration支持自定义UserCollectionType
2008-12-09 17:45 2792在Hibernate的Jira上,这个两个issue已经放了很 ... -
领域模型的价值与困境
2008-11-27 23:23 3981很久以前大家就关于这个方面有很多讨论了。前两天我又挖了一个坑来 ... -
你所不知道的CommandBar
2008-10-29 08:40 2921Office能够让你写插件。2 ... -
关于Estimation的随笔
2008-10-27 09:04 1145Estimation有很多流派。 从数字的选择上来看:有的人喜 ... -
Domain Model - The Theory, The Reality, The Dream
2008-08-10 21:25 1287梦想照进现实 -
关于estimation的闲言碎语
2008-07-11 09:06 1513estimation只是一个开始,不是结束.好的estim ... -
let's placeBid
2008-05-12 10:11 1516这个例子很老啦,在之 ... -
贫血的Domain Model
2008-05-09 00:18 5317好老的话题啦。拿出来炒炒冷饭。各位见谅。 —————————— ... -
图形界面自动化测试
2008-05-04 22:16 2071Windows Win32 API (pywinauto, a ... -
the paint points of xaml
2008-01-16 08:56 1500Pain Point 1: XAML always creat ... -
lessons we have learnt about office integration
2008-01-16 08:54 1427Lesson 1: trust it Everything ... -
Outlook MAPIOBJECT
2008-01-09 18:06 2080Outlook的对象模型中,很多对象都有一个MAPIOBJEC ... -
.NET Remoting Callback
2007-10-12 20:42 2246有三个主要的障碍: 1、服务器解析不到客户端的assembly ... -
企业应用开发者使用WPF的三个理由
2007-05-16 23:02 5763让控件更灵活的Data Template ... -
用UIAutomation做验收测试
2007-05-16 22:19 4559这是被测的应用程序: 应用.NET 3.0提供的UIA ... -
主动重构 => 被动重构
2007-05-10 16:34 2015引言 最近杂七杂八地思考了不少东西。但是很惊异地发现这三三两 ... -
我的酒窝.NET
2007-04-30 16:59 2772ajoo同学的酒窝有.NET版本啦! 项目主页: http: ... -
Naive Container 发布1.0版本
2007-04-29 17:50 2593二进制文件和源代码可以从这里下载到: http://naive ...
相关推荐
在Java世界中,Mock框架则是单元测试的重要组成部分,它们帮助开发者模拟复杂的系统交互,以便在隔离环境中测试代码。"基于Java探针的Mock框架"可能是指一种结合了这两种技术的工具,它可能是为了提供更高级别的测试...
Mockcpp是C++编程语言中的一种模拟(mock)框架,用于单元测试。它不依赖于模板来实现其功能,这使得Mockcpp在某些特定场景下,比如对编译时复杂度有严格限制或者对编译速度有较高要求的项目中,成为一个理想的工具...
Moq (pronounced "Mock-you" or just "Mock") is the only mocking library for .NET developed from scratch to take full advantage of .NET 3.5 (i.e. Linq expression trees) and C# 3.0 features (i.e. lambda ...
本文件“用Java编写的最流行的单元测试mock框架.zip”主要包含了关于Mockito的相关资料,帮助开发者更好地理解和使用这个强大的工具。 Mockito是一个开源的Java库,专门设计用于创建模拟对象,以隔离被测试代码并...
5. **易于集成**:可以轻松地与现有的测试框架(如Jest、Mocha等)结合,方便进行单元测试和集成测试。 在项目"MockHttpRequest-master"中,可能包含了如下内容: 1. `src`目录:源代码文件,包含mock-xhr库的核心...
mod.dll是一款单元测试模拟框架,常常被用来进行单元测试 [TestMethod] public void Relocation() { var mockContext = new Mock(); mockContext.Setup(c => c.HttpContext.Response.Redirect(...
本文将深入探讨Mock的概念及其在Struts2与Spring框架中的应用,同时也会涉及到JUnit和EasyMock等单元测试工具。 Mock技术的核心是创建虚拟对象,这些对象的行为可以被精确地控制,从而模拟真实系统中的协作对象。在...
给大家介绍在测试中使用的利器googlemock,它是Google在2008年发布的一套针对C++的Mock框架,与googletest吸取JUnit的精华一样,的它灵感同样来自去Java社区的JMock、EasyMock等Mock思想。 更多关于google...
Swift-CuckooSwiftKit 是一个针对Swift开发的测试框架,其特点是无需编写额外的模拟(mock)代码,就能实现测试的高效与灵活。Cuckoo框架利用编译时的元编程技术,在目标代码中动态插入结构体和类,使得在运行时能够...
本文来自于51cto,文章主要讲解Mock框架三个版本的实现方法和优缺点以及单元测试展开方式等相关内容。对于单元测试中的单元,不同的人有不同的看法:可以理解为一个方法,可以理解为一个完整的接口实现,也可以理解...
Google Mock(通常称为gmock)是Google Test框架的一部分,专门用于创建和使用模拟对象,以便在测试中隔离被测试代码,确保其独立于其他组件进行评估。下面,我们将深入探讨这两个重要的测试工具及其应用。 **...
`Mock<T>` 是MoQ框架的核心类,用于创建和管理模拟对象。`T`代表你要模拟的接口或抽象类。例如,你可以通过`new Mock()`创建一个`ITest`接口的模拟对象。 2. **方法模拟**: 你可以使用`Setup`方法来定义模拟对象...
- GoMock可以与标准库`testing`无缝集成,也可以配合其他第三方测试框架如`go-testdeep`,增强测试能力。 5. **实际应用示例** - 在服务间的依赖模拟:如果你的代码依赖于外部服务,可以通过GoMock模拟这些服务...
Classes contained in spring-mock.jar: org.springframework.mock.jndi.ExpectedLookupTemplate.class org.springframework.mock.jndi.SimpleNamingContext.class org.springframework.mock.jndi....
Mock技术在软件开发中扮演着重要的角色,尤其是在测试领域。它允许开发者在不依赖实际环境或第三方服务的情况下,创建模拟对象来代替真实组件,以便进行单元测试和集成测试。这样可以提高测试效率,减少测试复杂性,...
MockServer 是一个强大的工具,主要用于在开发过程中模拟服务器行为,特别是在接口开发和测试阶段。它允许开发者在没有实际后端服务的情况下,创建模拟的HTTP和HTTPS服务器,以便于快速验证前端代码或者自动化测试。...
Python 提供了一个强大的库——`unittest.mock`,用于模拟(mock)对象、方法和类,以便在测试中隔离依赖关系,专注于测试目标代码的功能。本练习主要关注如何使用 `unittest.mock` 进行mock测试数据。 一、Mock...
Google Mock 是一个强大的 C++ 单元测试框架,它允许开发者创建模拟对象(Mock Objects)以测试复杂的系统中各个组件的交互。Mock 对象能够模拟真实对象的行为,并且可以预设它们在特定调用下的响应,这对于隔离测试...