一种简便无需代码的Mock方案

 (作者:朱士松,新浪微博: http://weibo.com/treesong 转载须注明出处)

在软件测试过程 中,通常会遇到这样的一些问题:

  •  某些对象很难构造或获取;
  •  对象行为的结果难以预测;
  •  依赖的服务不能提供服务;
  •  需要控制某些对象的性能;

对于上述一些问题,通常使用Mock对象来代替真实对象来工作。比较流行的Mock方案,在Java中有Mockito, EasyMock, JMock, MockCreater, MockRunner等,在.Net中有NMock, NetMock等,在C++中有Google Mock等。大多Mock方案的工作方式是:为用户指定的接口生成一个Proxy对象,再设定这个Proxy对象的一些行为及其对应结果,然后就使用这个Proxy对象作为Mock对象来代替真实对象来工作。以下以Java中最为流行的两个Mockito与EasyMock为例,展示它们的工作方式。

假设测试时需要用到如下的两个接口:

 

public interface UserInfo{

   

 public abstract Date getBirthday();

   

 public abstract String getNickName();

 

}

 

public interface SocialNetworkService {

   

 public abstract String getGreeting(Date date, UserInfo user);

 

}

 

其中getGreeting用于生成一个跟时间相关的对用户的问候语,即:

  1. 如果给定的时间刚好是用户的生日,则发出对它的生日祝福;
  2. 如果给定的时间是其它的公共节日,则发出相应的节日祝福;
  3. 否则按时间给出“早日好/中午好/下午好/晚上好”等问候语; 

当然实际中的问题会复杂得多,使得SocialNetworkService迟迟都不能交付,对它测试的代码及依赖它的服务都无法运行。如果使用Mock来解决这样的问题,Mockito及EasyMock为别是如下工作的:

Mockito代码:

 

// create mock object

 

UserInfo user = mock(UserInfo.class);

 

SocialNetworkService sns = mock(SocialNetworkService.class);

 


 

// set the behavior of mock object

 

 

Date birthday = new Date(1990, 11, 03, 0, 0, 0);

 

when(user.getNickName()).thenReturn("元芳");

 

when(user.getBirthday()).thenReturn(birthday);

 

Date now = new Date(2012, 11, 03, 13, 15, 16);

 

when(sns.getGreeting(now, user)).thenReturn("生日快乐,元芳")

 


 

// using the mock object to do more works

 

 

String actual = sns.getGreeting(now, user);

assertEqual("元芳,生日快乐", actual);

 

EasyMock代码:

// create mock object 

MockControl snsMock = MockControl.createControl(SocialNetworkService.class); 

SocialNetworkService sns =(SocialNetworkService)snsMock.getMock(); 

MockControl userMock = MockControl.createControl(UserInfo.class); 

UserInfo user =(UserInfo)control.getMock();


 

// set the behavior of mock object

Date birthday = new Date(1990, 11, 03, 0, 0, 0); 

 

EasyMock.expect(user.getBirthday()).andReturn(birthday);

 

EasyMock.expect(user.getNickName()).andReturn("元芳");

 

EasyMock.replay(user);

 

Date now = new Date(2012, 11, 03, 13, 15, 16);

EasyMock.expect(sns.getGreeting(now, user)).andReturn("生日快乐,元芳");

EasyMock.replay(sns);


 

// using the mock object to do more works

 

String actual = sns.getGreeting(now, user);

assertEquals("生日快乐,元芳", actual);


Mock对象代替了真实的对象,使得后续的代码能够运行。当然Mockito/EasyMock等都还有许多方法提供更多样的功能及特点,这些特性非常适用于完成单元测试。但是某些场景中使用起来可能非常繁琐,比如当参数或返回值非常复杂时,需要编辑大量的代码来构造它们;尤其是当模块测试/系统测试时,如果依赖着的服务仍不能工作,这样的Mock方式就不再适用了,因为被测对象为产品代码,而且还面临非常多样的输入。

为解决这个问题,我们提供了这样的方案:

  1. 仍然创建一个Proxy对象,用它来处理调用请求;
  2. Proxy对象将调用请求RPC到某一个服务器上;
  3. 服务器根据请求的入参给出相匹配的返回值;
  4. 使用Web管理服务器上的匹配信息与返回值;

首先,1. 代码使用Spring注入的方式创建对象,这样就能屏蔽真实对象与虚拟对象的区别;

其次,2. 虚拟对象自动将调用请求PRC服务上进行处理了,就无需编写代码设置它的行为;

最终,3. 服务器负责了响应调用请求,且管理着返回值数据及它们与请求输入参数的关系;

总之,在不改变产品代码而且也不需用编写代码的同时,就使得模块/系统测试能够运行;当应用在单元测试时,也省去编写代码构造对象及设定行为等步骤。

整个方案的难点在于:服务及方法有很多种,返回值类型纷繁多样,参数也同样千变万化,怎么能够在“界面”上编辑返回值,以及设定匹配的入参呢?

最终,利用反射机制,编写出了用于Web上的“对象编辑器”,用它为不同返回值的类型生成Web界面,使得用户能够在Web上编辑一个返回值的具体值,如下图所示:

一种简便无需代码的Mock方案

“对象编辑器”支持原始类型的编辑、以及普通类的展开,还支持数组、List、Set、Map,以及泛型等。

同样,扩展这个“对象编辑器”的功能,我们得到一个“参数编辑器”,如下图所示:

一种简便无需代码的Mock方案

勾选参数值的字段,作为的匹配条件。当服务器处理调用请求时,如果入参满足这些条件,则相应的返回值就被选择和返回。

适用对象:同样的参数总是产生同样的返回值;

适用范围:单元测试/模块测试/系统测试。

你可能感兴趣的:(java,rpc,mock,PropertyGrid)