论坛首页 综合技术论坛

mock框架搞什么搞?

浏览 30172 次
该帖已经被评为良好帖
作者 正文
   发表时间:2007-05-11  
今天早上一时兴起,去网上下载下来JMock,EasyMock最新版本来玩玩。用来测试的行为很简单。就是有一个窗体,上面有一个文本框,一个按钮。如果点击按钮,就会把文本框的内容设置为“Hello”。应用MVP模式,这个行为应该在Presenter中,而View接口应该是这样的:

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框架,搞什么搞!简单一点不好么?
   发表时间:2007-05-11  
搞明白哪些地方可以使用Mock先再骂娘, OK?
0 请登录后投票
   发表时间:2007-05-11  
简单问题是怎么被mock框架搞复杂的,这个例子难道还不明白吗?静态构造函数,泛型。构造出奇技淫巧的语法。结果缺并没有把事情搞简单。这还不能够说明问题?
0 请登录后投票
   发表时间:2007-05-11  
你给的例子不是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可能就不太合适了。
0 请登录后投票
   发表时间:2007-05-11  
像我上面那样用jMock的就很多的。用mock框架写测试的时候,不知不觉地就写出了很多本来不适合用mock框架解决的问题。所以fake也好,stub也好。都是要告诉大家,用之前先考虑考虑什么是最简单的解决办法。这dummy,fake,stub与mock的分别太过于微妙。只要能够达到目的就好了,管它叫什么名字呢?我觉得可以拿各种测试中需要用到的“mock”的场景拿出来,用各种实现方式比较一下,看看jMock这些框架到底在什么情况下写出来的代码行数更少,而且更好理解。我觉得不会太多。
0 请登录后投票
   发表时间: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可能就不太合适了。


也没必要用到stub .写起来也很是麻烦。

直接针对分离的业务逻辑作测试。当然,前提是代码里面已经把界面和业务逻辑分离好了。

如果按照楼主这样写出来的测试代码,我感觉没法看了,即使看得也很费力。



0 请登录后投票
   发表时间: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啊...
0 请登录后投票
   发表时间:2007-05-11  
我更愿意使用 httpunit
0 请登录后投票
   发表时间:2007-05-12  
几个不协调的地方:

主要的逻辑是:
   触发按钮事件--〉 调用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这里面“少而且简单“的代码做一个集成测试好了。





0 请登录后投票
   发表时间: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框架的场合。
0 请登录后投票
论坛首页 综合技术版

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