使用JMock来辅助你的单体测试

在项目的单体测试 ( 以下称 UT) 阶段,我们经常使用 JUnit 来协助应用程序代码的测试,它为我们提供了一种很好的测试解决方案。

但是在一个运行环境比较复杂的应用程序的UT阶段,为了要测试目标应用程序,我们往往需要花费很多时间去构筑一个包含诸如Application Server, Message Server, Database Server等服务的可运行环境。这对于系统ITa或者ITb而言并不是什么问题,但是很多时候对于UT来说却是一项不必要的工作。(在很多Global性质的开发项目中,每一个location都构筑这样一个环境更是一项巨大的开销)

Mock Ojbect的出现很好的解决了这一难题。例如,你写了一个封装了JMS API包的补助类来提供对Message Server的访问。示例代码如下:

public class JmsAgent extends JmsAgentBase implements JmsAgentIF {

  private Connection connection;

  public void sendMessage(Session session, Destination destination,

                                     Object object) throws DomainException {

                   MessageProducer producer = null;

                   producer = session.createProducer(destination);

 

                   //performing send action within a loop in case of the message content object is an array

                   Object[] msgContents = new Object[] {};

                   if (object instanceof Object[]) {

                                     msgContents = (Object[]) object;

                   } else {

                                     msgContents[0] = object;

                   }

                   Message message  = null ;

                   for (int i = 0; i < msgContents.length; i++) {

                                     message = msgCreator.createMessage(msgContents[i],

                                                                           session);

                                     if (logger.isDebugEnabled() == true) {

                                                        logger.debug("Message[" + message.getJMSMessageID()

                                                                                             + "] is to be sent to Destination["

                                                                                             + destination.toString() + "].");

                                     }

                                     producer.send(message);

                   }

                   //transaction management processing

                   if(this.isMethodTransacted() == true){

                                     session.commit();

                   }

 

public Object receiveMessage(Session session, Destination destination)

                                     throws DomainException {

                   Object result = null ;

                   if (logger.isDebugEnabled() == true) {

                                     logger.debug("Receive Message from Destination["

                                                                           + destination.toString() + "].");

                   }

                  

                   MessageConsumer consumer = null;

                   consumer = session.createConsumer(destination);

                   result = getMsgVisitor().visit(consumer.receive(receiveTimeout));

                  

                   //transaction management processing

                   if(this.isMethodTransacted() == true){

                                     session.commit();

                   }

                   return result ;           

                   }

 

  public void setConnection(Connection connection){

                   this.connection = connection;

                   }

  省略

}

当要对你的程序逻辑代码进行单体测试的时候,我们并不关心JMS API的实现部分,而是将重点放在应用程序的逻辑代码块的准确性上。但是为了让应用程序能够执行起来,我们必须保证程序所调用的JMS API可以正常使用,这就需要有一个JMS API服务商提供的实装包,以及响应该实装的Message服务。(可以是Active MQ, WebSphere MQ 或者其他服务商所提供的)。在系统配置比较复杂的情况时,比如说需要Cluster构成,需要配置很多Channel, Queue等等,往往配置系统的时间要比我们做UT的时间花费更加大。

这个时候我们会想到Mock Object来模拟JMS API实现以及相应的Message服务。

我们最容易想到的方式就是,对上述补助类里使用了的每一个JMS API,我们都为其生成一个Mock Ojbect,并模拟相应方法的实现。在下面的示例代码中,我们就采用了这种方式,并且在Message服务的模拟实现中,我们将用一个类的属性LinkedList来保存和获取Message

public class JmsAgentTest extends TestCase {

                   private LinkedList messageContainer = new LinkedList();

                   private Object[] testMsg = new String[]{"hello world_1!","hello world_2!"};

                   private JmsAgent jmsAgent ;

                   protected void setUp() throws Exception {

                                     jmsAgent = new JmsAgent();

                                     jmsAgent.setConnection(new MockConnection());
                    }

                   public void testSendMessage() throws Exception {

                                     jmsAgent.sendMessage(jmsAgent.getSession(),new MockDestination(),testMsg);

                   }

                   public void testReceiveMessage() throws Exception {

                                     Object mse = null ;

                                     for(int i=0;i<testMsg.length;i++){

                                     msg = jmsAgent.receiveMessage(jmsAgent.getSession(),new MockDestination());

                                     asertNotNull(msg);

                                     System.out.println(msg.toString());

                                     }

                   }

 

                   class MockConnection implements Connection {

public Session createSession(boolean arg0, int arg1) throws JMSException {

                   return new MockSession();

}

省略
                   }

                   class MockSession implements Session{

                                     public TextMessage createTextMessage(String arg0) throws JMSException {

                                                        return new MockTextMessage(arg0);

                                     }

                                     public MessageProducer createProducer(Destination arg0) throws JMSException {

                                                        return new MockProducer();

                                     }

                          public MessageConsumer createConsumer(Destination arg0) throws JMSException {

                                                        return new MockConsumer();

                                     }

                                     省略

                   }

                   class MockTextMessage implements TextMessage{

                                     String text;

                                     public MockTextMessage(String text){

                                                        this.text = text;

                                     }

                                      public String getText() throws JMSException {

                                                        return this.text;

                                     }

                                     省略

                   }

                   class MockProducer implements MessageProducer{

                                     public void send(Message arg0) throws JMSException {

                                                        messageContainer.add(arg0);

}

省略

                   }

                   class MockConsumer implements MessageConsumer{

                                     public Message receive() throws JMSException {

                                                        MockTextMessage message = messageContainer.getFirst();

                                                        messageContainer.removeFirst();

                                                        return message;

                                     }

省略

                   }

                   class MockDestination implements Destination{

省略

                   }

}

在这个例子里,我们可以看到使用Mock Object手法,即使没有JMS API实装包及其提供Message服务,我们也能够很好的测试该补助类的逻辑代码。但是同时我们也可以看到,大量的Mock Object的实现也给我们带来了不必要的麻烦,即使是很单纯的模拟内容,也需要我们实现大量的相关类和接口,不仅增加了我们需要维护的代码量,而且在对应变更或者对代码进行重构时也会增加很多工作量。

为了解决这个问题,我们引入的是Mock Object的生成工具这一利器。

Java中,动态代理的实现已经是非常的普及了,先是由JDK1.3.1开始提供了针对目标接口的动态代理机制,随后由开源项目CGLib提供了针对目标类的动态代理机制,两者形成互补,已经在很多其他的开源项目中得到了广泛运用。同样,从我们上面的例子也可以看得出来,在Mock Object的生成上动态代理机制也可以起到很好的作用。利用东带代理机制来协助生成Mock Object的开源项目中,EasyMockJMock是比较出名的两个。不管是在实现上,还是在提供的功能上两者都很相似。JMock在方法参数测试的灵活性有更大的优势,并且在使用时的写法上,对于Java程序员来说更具有亲和力,因此在接下来的内容中我会简单介绍一下JMock的使用方法。

JMock的官方网站是http://www.jmock.org, 可以从那里下载到最新的程序源代码以及编译后的jar包,上面还有一些使用说明以及实例可以参照。

1.首先我们要知道JMock可以为我们做什么。大体上可以分为下面两点:

1-1.通过动态代理机制为对象接口或者对象类的生成Mock Object

1-2.在测试过程中控制与检查生成的Mock Object的行为,例如可以事先指定什么方法会被调用多少次,什么参数会被传递,被调用时执行什么逻辑,调用的顺序等等。当实际运行结果与事先指定的内容有出入时JMock会抛出例外并结束测试过程。

 

2.其次要了解的是JMock中的几个概念。

1)      Matcher: Macher是用来制定测试测试前后的调用结果之间的匹配规则的一组类。主要有下面两种用法。

1)-1。用户直接使用InvocationMatcher来制定调用时的匹配规则,比如目标方法会被调用1次,或是至少/至多1次,指定的n次等内容。例如:

mockSession.expects(once()).method("createTextMessage")

就表示在测试过程中Session对象的createTextMessage方法要求被调用一次。

1)-2。在运行期,JMock内部使用了InvocationMatcher子类,基于用户事先指定好的各种条件判断运行前后的调用结果是否匹配。如ArgumentsMatcher,

MethodNameMatcher等。

2)      Constraints: JMock中,Constraint是用来限制,或是指定传递到某些方法的参数类型时使用到的一组类。此外也可以用于限制,或是指定目标方法。

  

1在如下的代码中就使用了IsInstanceOf这一constraint

mockSession.expects(once()).method("createTextMessage").

with(new IsInstanceOf (String.class));

在调用Session实例的createTextMessage方法时限制传递给该方法的参数必须是String类型的。

 

2.除了限制方法参数之外,在指定目标方法时,constraint也可以起到一定作用。

mockSession.expects(once()).method(IsAnything).withAnyArgument().will(xxx);

这行代码中用到了IsAnything这个constraint,表示在测试过程中要求mockSession对象的某个方法必须要被调用一次,而且被调用时会执行xxx指定的逻辑。这一用法在下面谈到Stub的用法时会有所帮助。

3)      Stub: JMock中,Stub和下面的Expectation具有相同意义。都是用来事先设置Mock Object在测试过程中会被调用的方法。不同之处在于用Stub设置的方法在测试过程中允许被调用0次或者多次,而用Expectation设置的方法则不同,需要在设置时使用InvocationMatcher实例指定方法会被调用的次数。也就是说有两者在使用方式上存在不同。

此外,Stub的另外一个重要的用法是作为一种处理被设置到目标方法上,在运行期间该方法被调用时会自动执行Stub内的处理。在4)我们会详细说明这个用法。

 

1.下面的例子指定了Connection类的start方法可以在测试过程中被任意的调用。

mockConnection.stubs().method("start").withNoArguments();

 

2.有很多时候,在应用程序大量使用了某个对象的各式各样的方法,我们并不想知道它的什么方法被调用过,以及被调用过多少次,对于这样的对象的Mock Object,在测试过程中可以用这种方式设置:

mockSession.stubs().method(IsAnything).withAnyArguments();

4)      Expectation: 关于Expectation,在上面的Stub的内容中已经有所描述,这里举例补充一下对于目标方法,我们可以设置什么处理。

  

1.不设置,设置返回值,设定抛出例外等简单处理。

mockSession.expects(once()).method(“createTextMessage”).with (new IsInstanceOf (String.class).will(new ReturnStub((TextMessage)mockTextMessage.proxy())));

在这个例子中,Session对象的createTextMessage方法被调用时将会返回一个TextMessage对象的MockObject.其中的Stub: ReturnStub是由JMock自己实现的。

 

2.当某个方法会被多次调用时,设置对每次调用时的处理按照某个顺序执行;

mock.expects(atLeastOnce()).method(m).with(...)

   .will( onConsecutiveCalls(

       returnValue(10),

       returnValue(20),

       throwException(new IOException("end of stream")) ) );

这个例子是JMock官方网站上提供的,当方法m被第一次调用时,它会返回一个指定值10,同样的,第二次被调用时会返回20,第三次时会抛出指定的例外。

 

3.设置定制化的处理。首先需要实现这么一个定制化处理,然后像上面的例子那样将其指定为Mock Object的目标方法的处理即可。

   首先实现一个Stub类,

private class HelloWorldStub implements Stub {

public Object invoke(Invocation invocation) throws Throwable {

                      return new MockTextMessage(“HelloWorld”);

}

public StringBuffer describeTo(StringBuffer buffer) {

              return buffer.append("returns ").append(“HelloWorld”);

}

}

使用该Stub

mockSession.expects(once()).method(“createTextMessage”).with (new IsInstanceOf (String.class).will(new HelloWorldStub());

 

4.限制相同或者不同Mock Object之间调用的先后顺序

MockConnection.expects(once()).method(“start”).withAnyArguments().id(“startC”);

MockConsumer.expects(once()).method(“receive”).withAnyArguments().after(“startC”).will(new HelloWorldStub());

MockConnection.expects(once()).method(“stop”).withAnyArguments().after(“startC”);

 

3.最后,回到我们最开始的那个例子中,现在我们已经对JMock的使用有了一定的了解,下面我们要用JMock来改善一下这个测试代码。

: MockObjectTestCaseJMock实现的TestCase的一个子类,

其中提供了很多方便的可以获取现成的Stub,Constraint,Mather类的方法。并且,使用该类提供的方法来获取目标接口或目标类的Mock Object的话,不需要在测试方法用调用相应的MockControl实例的verify()方法即可将设置好的内容反映到测试过程中。

 

public class JmsAgentTest extends MockObjectTestCase {

                   private LinkedList messageContainer = new LinkedList();

                   private Object[] testMsg = new String[]{"hello world_1!","hello world_2!"};

                   private JmsAgent jmsAgent ;

                   Mock mockCon ;

                   Mock mockSession ;

                   Mock mockTextMessage ;

                   Mock mockProducer ;

                   Mock mockConsumer ;

                   Mock mockDestination ;

                  

                   protected void setUp() throws Exception {

                                     jmsAgent = new JmsAgent();

                                     mockCon = mock(Connection.class);

                                      mockSession = mock(Session.class);

                                      mockTextMessage = mock(TextMessage.class);

                                      mockProducer = mock(MessageProducer.class);

                                      mockConsumer = mock(MessageConsumer.class);

                                      mockDestination = mock(Destination.class);

 

                                     mockCon.stubs().method(new IsAnything()).withAnyArguments();

mockSession.expects(atLeastOnce()).method("createTextMessage").with(isA(String.class)).will(returnValue((TextMessage)mockTextMessage.proxy()));

                                     mockSession.stubs().method("commit").withNoArguments();

                                     mockSession.stubs().method("rollback").withNoArguments();

                                     mockSession.stubs().method("close").withNoArguments();

                                     省略….

                                     jmsAgent.setConnection(mockCon.proxy());

                                    

                   }

                   public void testSendMessage() throws Exception {

mockProducer.expects(exactly(testMsg.length)).method("send").with(isA(Message.class)).will(new ActionStub());

                                      mockTextMessage.expects(exactly(testMsg.length)).method("setText").

with(isA(String.class)).will(new MessageStub());

                                      省略

                                     jmsAgent.sendMessage(jmsAgent.getSession(),mockDestination.proxy(),testMsg);

                   }

                   public void testReceiveMessage() throws Exception {

                                     mockConsumer.expects(exactly(testMsg.length)).method("receive").

with(ANYTHING).will(new ActionStub());

mockTextMessage.expects(exactly(testMsg.length)).method("getText").withNoArguments().will(new MessageStub());

省略

                                     Object mse = null ;

                                     for(int i=0;i<testMsg.length;i++){

                                     msg = jmsAgent.receiveMessage(jmsAgent.getSession(),mockDestination.proxy());

                                     asertNotNull(msg);

                                     System.out.println(msg.toString());

                                     }

                   }

 

private class ActionStub implements Stub {

                   private String strSendMethod = "send";

                   private String strReceiveMethod = "receive";

                                    

                   public Object invoke(Invocation invocation) throws Throwable {

                                     //Class className = invocation.invokedObject.getClass();

                                     String  methodName = invocation.invokedMethod.getName();

                                     if(methodName.startsWith(strSendMethod)){                                            

messageContainer.add((TextMessage)invocation.parameterValues.get(0));

                                     return null;

                                     }else if(methodName.startsWith(strReceiveMethod)){

                                     //If return the MockTextMessage instances set by sendMessage method,

                                     //the expected setting like setText will be effective.

                                     //TextMessage msg = (TextMessage) messageContainer.getLast();

                                     //messageContainer.removeLast();

                                     return mockTextMessage.proxy();

                                     }else {

                                     return null;

                                     }

                   }

                   public StringBuffer describeTo(StringBuffer buffer) {

                                     return null;

                   }

                                    

}

                  

private class MessageStub implements Stub {

                   private int messageBodyIndex ;

                   private String strSetMethod = "set";

                   //private String strGetMethod = "get";

                  

                   public MessageStub(int messageBodyIndex) {

                                     this.messageBodyIndex = messageBodyIndex;

                   }

                   public MessageStub() {

                   }

 

                   public Object invoke(Invocation invocation) throws Throwable {

                                     String  methodName = invocation.invokedMethod.getName();

                                     if(methodName.startsWith(strSetMethod)){                                               

messageBodyContainer.add(invocation.parameterValues.get(0));

                                     return null;

                                     }else {

                                     String messageBody = (String) messageBodyContainer.getFirst();

                                     messageBodyContainer.removeFirst();

                                     return messageBody;

                                     }

                   }

 

                   public StringBuffer describeTo(StringBuffer buffer) {

                                     return null;

                   }

                                    

}

}

如果需要详细代码的话,请到下面连接处下载相应的压缩文件。

http://comefos.sourceforge.net/articles/index_articles.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值