- 浏览: 193511 次
- 性别:
- 来自: 北京
最新评论
-
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 6934Guice在大部分时间都是很方便的,简单易用。Guice和Sp ... -
再论领域模型的困境
2009-06-03 18:54 2077距离上次发帖讨论领域 ... -
让AnnotationConfiguration支持自定义UserCollectionType
2008-12-09 17:45 2787在Hibernate的Jira上,这个两个issue已经放了很 ... -
领域模型的价值与困境
2008-11-27 23:23 3950很久以前大家就关于这个方面有很多讨论了。前两天我又挖了一个坑来 ... -
你所不知道的CommandBar
2008-10-29 08:40 2913Office能够让你写插件。2 ... -
关于Estimation的随笔
2008-10-27 09:04 1140Estimation有很多流派。 从数字的选择上来看:有的人喜 ... -
Domain Model - The Theory, The Reality, The Dream
2008-08-10 21:25 1285梦想照进现实 -
关于estimation的闲言碎语
2008-07-11 09:06 1507estimation只是一个开始,不是结束.好的estim ... -
let's placeBid
2008-05-12 10:11 1510这个例子很老啦,在之 ... -
贫血的Domain Model
2008-05-09 00:18 5296好老的话题啦。拿出来炒炒冷饭。各位见谅。 —————————— ... -
图形界面自动化测试
2008-05-04 22:16 2064Windows Win32 API (pywinauto, a ... -
the paint points of xaml
2008-01-16 08:56 1495Pain Point 1: XAML always creat ... -
lessons we have learnt about office integration
2008-01-16 08:54 1425Lesson 1: trust it Everything ... -
Outlook MAPIOBJECT
2008-01-09 18:06 2076Outlook的对象模型中,很多对象都有一个MAPIOBJEC ... -
.NET Remoting Callback
2007-10-12 20:42 2238有三个主要的障碍: 1、服务器解析不到客户端的assembly ... -
企业应用开发者使用WPF的三个理由
2007-05-16 23:02 5760让控件更灵活的Data Template ... -
用UIAutomation做验收测试
2007-05-16 22:19 4555这是被测的应用程序: 应用.NET 3.0提供的UIA ... -
主动重构 => 被动重构
2007-05-10 16:34 2009引言 最近杂七杂八地思考了不少东西。但是很惊异地发现这三三两 ... -
我的酒窝.NET
2007-04-30 16:59 2767ajoo同学的酒窝有.NET版本啦! 项目主页: http: ... -
Naive Container 发布1.0版本
2007-04-29 17:50 2592二进制文件和源代码可以从这里下载到: http://naive ...
相关推荐
《Spring框架测试实践详解》 在Java开发领域,Spring框架以其强大的功能和灵活性深受开发者喜爱。本示例“spring-demo15-测试”主要聚焦于Spring框架中的测试部分,通过一系列的实例,深入探讨如何有效地进行Spring...
记得以前面试的时候,面试官问我,...我们常使用的就是 Junit 框架,说到测试,我之前一直没搞清楚,直到现在才有点头绪,不知道你们会不会遇到这种问题,在测试中若是涉及到 find 方法还好,但是涉及到修改数据的操作
在前台,我们需要搞一个按钮,当用户点击这个按钮的时候,我们使用 JavaScript 的 window.location.href 将页面重定向到我们处理下载的 URL。这时候,我们可以传递一些参数,例如卷号等业务相关的参数。 Step 3:...
iOS版微信抢红包Tweak.zip小程序
该资源内项目源码是个人的课程设计、毕业设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过严格测试运行成功才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。
基于springboot社区停车信息管理系统.zip
基于springboot南皮站化验室管理系统源码数据库文档.zip
## 数据指标说明 全要素生产率(TFP)也可以称之为系统生产率。指生产单位(主要为企业)作为系统中的各个要素的综合生产率,以区别于要素生产率(如技术生产率)。测算公式为:全要素生产率=产出总量/全部资源投入量。 数据测算:包含OL、FE、LP、OP、GMM共五种TFP测算方法!数据结果包括excel和dta格式,其中重要指标包括证券代码,固定资产净额,营业总收入,营业收入,营业成本,销售费用,管理费用,财务费用,购建固定资产无形资产和其他长期资产支付的现金,支付给职工以及为职工支付的现金,员工人数,折旧摊销,行业代码,上市日期,AB股交叉码,退市日期,年末是否ST或PT等变量指标分析。文件包括计算方法说明及原始数据和代码。 数据名称:上市公司全要素生产率TFP数据及测算方法(OL、FE、LP、OP、GMM) 数据年份:2000-2023年 数据指标:证券代码、year、TFP_OLS、TFP_FE、TFP_LP1、TFP_OP、TFP_OPacf、TFP_GMM
内容概要:本文详细总结了多种编程语言下常用的算法实现资源,涵盖Python、C++、Java等流行编程语言及其相关的开源平台、在线课程和权威书籍。对于每种语言而言,均提供了具体资源列表,包括开源项目、标准库支持、在线课程及专业书籍推荐。 适合人群:适用于所有希望深入研究并提高特定编程语言算法能力的学习者,无论是编程新手还是有一定经验的技术人员。 使用场景及目标:帮助开发者快速定位到合适的算法学习资料,无论是出于个人兴趣自学、面试准备或是实际工作中遇到的具体算法问题,都能找到合适的解决方案。 其他说明:文中提及多个在线学习平台和社区网站,不仅限于某一特定语言,对于跨学科或多元化技能培养也具有很高的参考价值。
基于springboot的交通旅游订票系统源码数据库文档.zip
内容概要:本文档是一份详细的GO语言教程,涵盖了Go语言的基础语法、数据类型、控制结构、函数、结构体、接口以及并发编程等多个方面。主要内容包括Go语言的基本概念和历史背景、环境配置、基本语法(如变量、数据类型、控制结构)、函数定义与调用、高级特性(如闭包、可变参数)、自定义数据类型(如结构体、接口)以及并发编程(如goroutine、channel、select)等内容。每部分内容都附有具体的代码示例,帮助读者理解和掌握相关知识点。 适合人群:具备一定编程基础的开发者,尤其是希望深入学习和应用Go语言的技术人员。 使用场景及目标:①初学者通过本教程快速入门Go语言;②有一定经验的开发者系统复习和完善Go语言知识;③实际项目开发中利用Go语言解决高性能、高并发的编程问题。 阅读建议:本文档全面介绍了Go语言的各项基础知识和技术细节,建议按章节顺序逐步学习,通过动手实践代码示例加深理解。对于复杂的概念和技术点,可以通过查阅更多资料或进行深入研究来巩固知识。
GEE训练教程
memcached笔记资料,配套视频:https://www.bilibili.com/list/474327672?sid=4486766&spm_id_from=333.999.0.0&desc=1
基于springboot校内跑腿业务系统源码数据库文档.zip
计算机控制光感自动窗帘控制系统设计.doc
基于SpringBoot的校园服务系统源码数据库文档.zip
基于SpringBoot+Vue的美容店信息管理系统源码数据库文档.zip
基于springboot程序设计基础课程辅助教学系统源码数据库文档.zip
这是一个原生的JS网页版斗地主小游戏,代码注释全。带有斗地主游戏基本的地主、选牌、提示、出牌、倒计时等功能。简单好玩,欢迎下载
基于springboot亚运会志愿者管理系统源码数据库文档.zip