jmock2.5 基本教程
目录 
第0章 概述 
第1章 jmock初体验 
第2章 期望 
第3章 返回值 
第4章 参数匹配 
第5章 指定方法调用次数 
第6章 指定执行序列 
第7章 状态机
第0章 概述
现在的dev不是仅仅要写code而已,UT已经变为开发中不可缺少的一环。JUnit的出现给javaer的UT编写提供了巨大的便利。但是JUnit并没有解决所有的问题。 
当我们要测试一个功能点的时候,需要把不需要我们关注的东西隔离开,从而可以只关注我们需要关注的行为。 
jmock通过mock对象来模拟一个对象的行为,从而隔离开我们不关心的其他对象,使得UT的编写变得更为可行,也使得TDD变得更为方便,自然而然的,也就成为敏捷开发的一个利器。
可以到http://www.jmock.org/download.html下载jmock. 
添加jar到classpath。 
添加的时候,注意把JUnit4的order放到最后。因为junit4它自己带了一个Hamcrest jar。 
要是不注意顺序的话,有可能报 
java.lang.SecurityException: class "org.hamcrest.TypeSafeMatcher"'s signer information does not match signer information of other classes in the same package。
Note: 
这里的类定义用来演示如何使用jmock,所以都是定义为public的。
Java代码
- publicclass UserManager {
 - public AddressService addressService;
 - public Address findAddress(String userName) {
 - return addressService.findAddress(userName);
 - }
 - public Iterator<Address> findAddresses(String userName) {
 - return addressService.findAddresses(userName);
 - }
 - }
 
我们有一个UserManager,要测试它的方法,但是,UserManager是依赖于AddressService的。这里我们准备mock掉AddressService。
第1章 jmock初体验
这个例子的作用在于像一个传统的hello world一样,给大家一个简明的介绍,可以有一个感觉,jmock可以做什么。 
AddressService本身太复杂,很难构建,这个时候,jmoc  k出场了。
Java代码
- @Test
 - publicvoid testFindAddress() {
 - // 建立一个test上下文对象。
 - Mockery context = new Mockery();
 - // 生成一个mock对象
 - final AddressService addressServcie = context
 - .mock(AddressService.class);
 - // 设置期望。
 - context.checking(new Expectations() {
 - {
 - // 当参数为"allen"的时候,addressServcie对象的findAddress方法被调用一次,并且返回西安。
 - oneOf(addressServcie).findAddress("allen");
 - will(returnValue(Para.Xian));
 - }
 - });
 - UserManager manager = new UserManager();
 - // 设置mock对象
 - manager.addressService = addressServcie;
 - // 调用方法
 - Address result = manager.findAddress("allen");
 - // 验证结果
 - Assert.assertEquals(Result.Xian, result);
 - }
 
那么这里做了什么事情呢? 
1 首先,我们建立一个test上下文对象。 
2 用这个mockery context建立了一个mock对象来mock AddressService. 
3 设置了这个mock AddressService的findAddress应该被调用1次,并且参数为"allen"。 
4 生成UserManager对象,设置addressService,调用findAddress。 
5 验证期望被满足。
基本上,一个简单的jmock应用大致就是这样一个流程。
最显著的优点就是,我们没有AddressService的具体实现,一样可以测试对AddressService接口有依赖的其他类的行为。也就是说,我们通过mock一个对象来隔离这个对象对要测试的代码的影响。
由于大致的流程是一样的,我们提供一个抽象类来模板化jmock的使用。
Java代码
- public abstract class TestBase {
 - // 建立一个test上下文对象。
 - protected Mockery context = new Mockery();
 - // 生成一个mock对象
 - protected final AddressService addressServcie = context
 - .mock(AddressService.class);
 - /**
 - * 要测试的userManager.
 - * */
 - protected UserManager manager;
 - /**
 - * 设置UserManager,并且设置mock的addressService。
 - * */
 - private void setUpUserManagerWithMockAddressService() {
 - manager = new UserManager();
 - // 设置mock对象
 - manager.addressService = addressServcie;
 - }
 - /**
 - * 调用findAddress,并且验证返回值。
 - *
 - * @param userName
 - * userName
 - * @param expected
 - * 期望返回的地址。
 - * */
 - protected void assertFindAddress(String userName, Address expected) {
 - Address address = manager.findAddress(userName);
 - Assert.assertEquals(expected, address);
 - }
 - /**
 - * 调用findAddress,并且验证方法抛出异常。
 - * */
 - protected void assertFindAddressFail(String userName) {
 - try {
 - manager.findAddress(userName);
 - Assert.fail();
 - } catch (Throwable t) {
 - // Nothing to do.
 - }
 - }
 - @Test
 - public final void test() {
 - setUpExpectatioin();
 - setUpUserManagerWithMockAddressService();
 - invokeAndVerify();
 - }
 - /**
 - * 建立期望。
 - * */
 - protected abstract void setUpExpectatioin();
 - /**
 - * 调用方法并且验证结果。
 - * */
 - protected abstract void invokeAndVerify();
 - }
 
这样一来,我们以后的例子中只用关心setUpExpectatioin()和invokeAndVerify()方法就好了。
第2章 期望
好了,让我们来看看一个期望的框架。
Java代码
- invocation-count (mock-object).method(argument-constraints);
 - inSequence(sequence-name);
 - when(state-machine.is(state-name));
 - will(action);
 - then(state-machine.is(new-state-name));
 
invocation-count 调用的次数约束 
mock-object mock对象 
method 方法 
argument-constraints 参数约束 
inSequence 顺序 
when 当mockery的状态为指定的时候触发。 
will(action) 方法触发的动作 
then 方法触发后设置mockery的状态
这个稍微复杂一些,一下子不明白是正常的,后面讲到其中的细节时,可以回来在看看这个框架。
第3章 返回值
调用一个方法,可以设置它的返回值。即设置will(action)。
Java代码
- @Override
 - protected void setUpExpectatioin() {
 - context.checking(new Expectations() {
 - {
 - // 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
 - allowing(addressServcie).findAddress("allen");
 - will(returnValue(Para.BeiJing));
 - // 当参数为null的时候,抛出IllegalArgumentException异常。
 - allowing(addressServcie).findAddress(null);
 - will(throwException(new IllegalArgumentException()));
 - }
 - });
 - }
 - @Override
 - protected void invokeAndVerify() {
 - assertFindAddress("allen", Result.BeiJing);
 - assertFindAddressFail(null);
 - }
 
这里演示了两种调用方法的结果,返回值和抛异常。 
使用jmock可以返回常量值,也可以根据变量生成返回值。 
抛异常是同样的,可以模拟在不同场景下抛的各种异常。
对于Iterator的返回值,jmock也提供了特殊支持。
Java代码
- @Override
 - protected void setUpExpectatioin() {
 - // 生成地址列表
 - final List<Address> addresses = new ArrayList<Address>();
 - addresses.add(Para.Xian);
 - addresses.add(Para.HangZhou);
 - final Iterator<Address> iterator = addresses.iterator();
 - // 设置期望。
 - context.checking(new Expectations() {
 - {
 - // 当参数为"allen"的时候,addressServcie对象的findAddresses方法用returnvalue返回一个Iterator<Address>对象。
 - allowing(addressServcie).findAddresses("allen");
 - will(returnValue(iterator));
 - // 当参数为"dandan"的时候,addressServcie对象的findAddresses方法用returnIterator返回一个Iterator<Address>对象。
 - allowing(addressServcie).findAddresses("dandan");
 - will(returnIterator(addresses));
 - }
 - });
 - }
 - @Override
 - protected void invokeAndVerify() {
 - Iterator<Address> resultIterator = null;
 - // 第1次以"allen"调用方法
 - resultIterator = manager.findAddresses("allen");
 - // 断言返回的对象。
 - assertIterator(resultIterator);
 - // 第2次以"allen"调用方法,返回的与第一次一样的iterator结果对象,所以这里没有next了。
 - resultIterator = manager.findAddresses("allen");
 - Assert.assertFalse(resultIterator.hasNext());
 - // 第1次以"dandan"调用方法
 - resultIterator = manager.findAddresses("dandan");
 - // 断言返回的对象。
 - assertIterator(resultIterator);
 - // 第2次以"dandan"调用方法,返回的是一个全新的iterator。
 - resultIterator = manager.findAddresses("dandan");
 - // 断言返回的对象。
 - assertIterator(resultIterator);
 - }
 - /** 断言resultIterator中有两个期望的Address */
 - private void assertIterator(Iterator<Address> resultIterator) {
 - Address address = null;
 - // 断言返回的对象。
 - address = resultIterator.next();
 - Assert.assertEquals(Result.Xian, address);
 - address = resultIterator.next();
 - Assert.assertEquals(Result.HangZhou, address);
 - // 没有Address了。
 - Assert.assertFalse(resultIterator.hasNext());
 - }
 
从这个例子可以看到对于Iterator,returnValue和returnIterator的不同。
Java代码
- @Override
 - protected void setUpExpectatioin() {
 - // 设置期望。
 - context.checking(new Expectations() {
 - {
 - // 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
 - allowing(addressServcie).findAddress("allen");
 - will(new Action() {
 - @Override
 - public Object invoke(Invocation invocation)
 - throws Throwable {
 - return Para.Xian;
 - }
 - @Override
 - public void describeTo(Description description) {
 - }
 - });
 - }
 - });
 - }
 - @Override
 - protected void invokeAndVerify() {
 - assertFindAddress("allen", Result.Xian);
 - }
 
其实这里要返回一个Action,该Action负责返回调用的返回值。既然知道了这个道理,我们自然可以自定义Action来返回方法调用的结果。 
而returnValue,returnIterator,throwException只不过是一些Expectations提供的一些static方法用来方便的构建不同的Action。
除了刚才介绍的 
ReturnValueAction 直接返回结果 
ThrowAction 抛出异常 
ReturnIteratorAction 返回Iterator 
还有 
VoidAction 
ReturnEnumerationAction 返回Enumeration 
DoAllAction 所有的Action都执行,但是只返回最后一个Action的结果。 
ActionSequence 每次调用返回其Actions列表中的下一个Action的结果。 
CustomAction 一个抽象的Action,方便自定义Action。
举个例子来说明DoAllAction和ActionSequence的使用。
Java代码
- @Override
 - protected void setUpExpectatioin() {
 - // 设置期望。
 - context.checking(new Expectations() {
 - {
 - // doAllAction
 - allowing(addressServcie).findAddress("allen");
 - will(doAll(returnValue(Para.Xian), returnValue(Para.HangZhou)));
 - // ActionSequence
 - allowing(addressServcie).findAddress("dandan");
 - will(onConsecutiveCalls(returnValue(Para.Xian),
 - returnValue(Para.HangZhou)));
 - }
 - });
 - }
 - @Override
 - protected void invokeAndVerify() {
 - assertFindAddress("allen", Result.HangZhou);
 - assertFindAddress("dandan", Result.Xian);
 - assertFindAddress("dandan", Result.HangZhou);
 - }
 
第4章 参数匹配
即设置argument-constraints
Java代码
- @Override
 - protected void setUpExpectatioin() {
 - // 设置期望。
 - context.checking(new Expectations() {
 - {
 - // 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
 - allowing(addressServcie).findAddress("allen");
 - will(returnValue(Para.Xian));
 - // 当参数为"dandan"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
 - allowing(addressServcie).findAddress(with(equal("dandan")));
 - will(returnValue(Para.HangZhou));
 - // 当参数包含"zhi"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
 - allowing(addressServcie).findAddress(
 - with(new BaseMatcher<String>() {
 - @Override
 - public boolean matches(Object item) {
 - String value = (String) item;
 - if (value == null)
 - return false;
 - return value.contains("zhi");
 - }
 - @Override
 - public void describeTo(Description description) {
 - }
 - }));
 - will(returnValue(Para.BeiJing));
 - // 当参数为其他任何值的时候,addressServcie对象的findAddress方法返回一个Adress对象。
 - allowing(addressServcie).findAddress(with(any(String.class)));
 - will(returnValue(Para.ShangHai));
 - }
 - });
 - }
 - @Override
 - protected void invokeAndVerify() {
 - // 以"allen"调用方法
 - assertFindAddress("allen", Result.Xian);
 - // 以"dandan"调用方法
 - assertFindAddress("dandan", Result.HangZhou);
 - // 以包含"zhi"的参数调用方法
 - assertFindAddress("abczhidef", Result.BeiJing);
 - // 以任意一个字符串"abcdefg"调用方法
 - assertFindAddress("abcdefg", Result.ShangHai);
 - }
 
测试演示了直接匹配,equal匹配,自定义匹配,任意匹配。 
其实,这些都是为了给参数指定一个Matcher,来决定调用方法的时候,是否接收这个参数。 
在Expectations中提供了一些便利的方法方便我们构造Matcher. 
其中 
equal判断用equal方法判断是否相等。 
same判断是否是同一个引用。 
any,anything接收任意值。 
aNull接收null。 
aNonNull接收非null.
jmock提供了很多有用的匹配。可以用来扩展写出更多的Matcher。 
基本Matcher 
IsSame 引用相等。 
IsNull 
IsInstanceOf 
IsEqual 考虑了数组的相等(长度相等,内容equals) 
IsAnything always return true.
逻辑Matcher 
IsNot 
AnyOf 
AllOf
其他 
Is 装饰器模式的Matcher,使得可读性更高。
第5章 指定方法调用次数
可以指定方法调用的次数。即对invocation-count进行指定。 
exactly 精确多少次 
oneOf 精确1次 
atLeast 至少多少次 
between 一个范围 
atMost 至多多少次 
allowing 任意次 
ignoring 忽略 
never 从不执行
可以看出,这些range都是很明了的。只有allowing和ignoring比较特殊,这两个的实际效果是一样的,但是关注点不一样。当我们允许方法可以任意次调用时,用allowing,当我们不关心一个方法的调用时,用ignoring。
第6章 指定执行序列
Java代码
- @Override
 - protected void setUpExpectatioin() {
 - final Sequence sequence = context.sequence("mySeq_01");
 - // 设置期望。
 - context.checking(new Expectations() {
 - {
 - // 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
 - oneOf(addressServcie).findAddress("allen");
 - inSequence(sequence);
 - will(returnValue(Para.Xian));
 - // 当参数为"dandan"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
 - oneOf(addressServcie).findAddress("dandan");
 - inSequence(sequence);
 - will(returnValue(Para.HangZhou));
 - }
 - });
 - }
 - @Override
 - protected void invokeAndVerify() {
 - assertFindAddress("allen", Result.Xian);
 - assertFindAddress("dandan", Result.HangZhou);
 - }
 
这里指定了调用的序列。使得调用必须以指定的顺序调用。 
来看一个反例
Java代码
- @Override
 - protected void setUpExpectatioin() {
 - final Sequence sequence = context.sequence("mySeq_01");
 - // 设置期望。
 - context.checking(new Expectations() {
 - {
 - // 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
 - oneOf(addressServcie).findAddress("allen");
 - inSequence(sequence);
 - will(returnValue(Para.Xian));
 - // 当参数为"dandan"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
 - oneOf(addressServcie).findAddress("dandan");
 - inSequence(sequence);
 - will(returnValue(Para.HangZhou));
 - }
 - });
 - }
 - @Override
 - protected void invokeAndVerify() {
 - assertFindAddressFail("dandan");
 - }
 
当指定序列的第一个调用没有触发的时候,直接调用第2个,则会抛异常。 
Note:指定序列的时候注意方法调用次数这个约束,如果是allowing那么在这个序列中,它是可以被忽略的。
第7章 状态机 
状态机的作用在于模拟对象在什么状态下调用才用触发。
Java代码
- @Override
 - protected void setUpExpectatioin() {
 - final States states = context.states("sm").startsAs("s1");
 - // 设置期望。
 - context.checking(new Expectations() {
 - {
 - // 状态为s1参数包含allen的时候返回西安
 - allowing(addressServcie).findAddress(
 - with(StringContains.containsString("allen")));
 - when(states.is("s1"));
 - will(returnValue(Para.Xian));
 - // 状态为s1参数包含dandan的时候返回杭州,跳转到s2。
 - allowing(addressServcie).findAddress(
 - with(StringContains.containsString("dandan")));
 - when(states.is("s1"));
 - will(returnValue(Para.HangZhou));
 - then(states.is("s2"));
 - // 状态为s2参数包含allen的时候返回上海
 - allowing(addressServcie).findAddress(
 - with(StringContains.containsString("allen")));
 - when(states.is("s2"));
 - will(returnValue(Para.ShangHai));
 - }
 - });
 - }
 - @Override
 - protected void invokeAndVerify() {
 - // s1状态
 - assertFindAddress("allen", Result.Xian);
 - assertFindAddress("allen0", Result.Xian);
 - // 状态跳转到 s2
 - assertFindAddress("dandan", Result.HangZhou);
 - // s2状态
 - assertFindAddress("allen", Result.ShangHai);
 - }
 
可以看到,如果序列一样,状态也为期望的执行设置了约束,这里就是用状态来约束哪个期望应该被执行。 
可以用is或者isNot来限制状态。
状态机有一个很好的用处。 
当我们建立一个test执行上下文的时候,如果建立的时候和执行的时候,我们都需要调用mock ojbect的方法,那么我们可以用状态机把这两部分隔离开。让他们在不同的状态下执行。
JMock在Junit4中的应用
@RunWith(JMockit.class)
public class DemoTest {
    @Tested
    private TPPPlayScheduleAPIImpl tPPPlayScheduleAPIImpl;
@Injectable
    private PlayScheduleRateService playScheduleRateservice;
@Test
    public void testGetAllPlayScheduleByCinemaId() {
        new Expectations() {
            {
                Map<Long, Map<String, PlayScheduleRate>> resultMap = new HashMap<Long, Map<String, PlayScheduleRate>>();
                Map<String, PlayScheduleRate> playScheduleRateMap = new HashMap<String, PlayScheduleRate>();
                Map<Integer, Integer> playNumCheckTypeMap = new HashMap<Integer, Integer>();
                PlayScheduleRate playScheduleRate = new PlayScheduleRate();
                playScheduleRate.setPlayNumCheckTypeMap(playNumCheckTypeMap);
                Map<String, Integer> scheduleRateMap = new HashMap<String, Integer>();
                mockScheduleRateMap(scheduleRateMap);
                playScheduleRate.setScheduleRateMap(scheduleRateMap);
                playScheduleRateMap.put("2017-02-23",playScheduleRate);
                playNumCheckTypeMap.put(1,99);
                playNumCheckTypeMap.put(2,98);
                resultMap.put(25L, playScheduleRateMap);
                playScheduleRateservice.queryAllPlayScheduleRateByCinemaId(anyLong);
                result = resultMap;
            }
        };
        ResultMapModel<Long, Map<String, PlayScheduleRate>> resultMapModel = tPPPlayScheduleAPIImpl.getAllPlayScheduleByCinemaId(25L);
        Assert.assertNotNull(resultMapModel);
        Map<Long, Map<String, PlayScheduleRate>> playScheduleRateMap = resultMapModel.getReturnValue();
        Assert.assertNotNull(playScheduleRateMap);
    }
jmock2.5 基本教程的更多相关文章
- jmock2.5基本教程(转)
		
原文:http://www.cnblogs.com/zfc2201/archive/2011/12/30/2307970.html jmock2.5基本教程 目录 第0章 概述 第1章 jmock初体 ...
 - Angular2入门系列教程7-HTTP(一)-使用Angular2自带的http进行网络请求
		
上一篇:Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数 感觉这篇不是很好写,因为涉及到网络请求,如果采用真实的网络请求,这个例子大家拿到手估计还要自己写一个web ...
 - Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数
		
上一篇:Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数 之前介绍了简单的路由以及传参,这篇文章我们将要学习复杂一些的路由以及传递其他附加参数.一个好的路由系统可以使我们 ...
 - Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数
		
上一篇:Angular2入门系列教程-服务 上一篇文章我们将Angular2的数据服务分离出来,学习了Angular2的依赖注入,这篇文章我们将要学习Angualr2的路由 为了编写样式方便,我们这篇 ...
 - Angular2入门系列教程4-服务
		
上一篇文章 Angular2入门系列教程-多个组件,主从关系 在编程中,我们通常会将数据提供单独分离出来,以免在编写程序的过程中反复复制粘贴数据请求的代码 Angular2中提供了依赖注入的概念,使得 ...
 - Angular2入门系列教程1-使用Angular-cli搭建Angular2开发环境
		
一直在学Angular2,百忙之中抽点时间来写个简单的教程. 2016年是前端飞速发展的一年,前端越来越形成了(web component)组件化的编程模式:以前Jquery通吃一切的田园时代一去不复 ...
 - wepack+sass+vue 入门教程(三)
		
十一.安装sass文件转换为css需要的相关依赖包 npm install --save-dev sass-loader style-loader css-loader loader的作用是辅助web ...
 - wepack+sass+vue 入门教程(二)
		
六.新建webpack配置文件 webpack.config.js 文件整体框架内容如下,后续会详细说明每个配置项的配置 webpack.config.js直接放在项目demo目录下 module.e ...
 - wepack+sass+vue 入门教程(一)
		
一.安装node.js node.js是基础,必须先安装.而且最新版的node.js,已经集成了npm. 下载地址 node安装,一路按默认即可. 二.全局安装webpack npm install ...
 
随机推荐
- Qt ------ QWidget 自定义子类使用信号与槽(Q_OBJECT)后 stylesheet 失效
			
这个应该属于 Qt 的一个bug,Qt assistant 给出相应的解决办法:重写函数“void paintEvent(QPaintEvent *event);”,添加下面截图中的一段代码
 - 利用Snapshot快速跨Region迁移服务器
			
当你需要对现有的网站进行跨区域迁移,或者是部署DR Site的时候,又不希望重新部署应用,有什么好办法呢?其实你可以利用Azure的磁盘snapshot进行磁盘级的复制,这样可以减少很多部署应用的时间 ...
 - Swarm使用原生的overlay网络
			
一.Swarm Overlay Network Swarm有Service的概念.一个Service是指使用相同镜像.同时运行的多个容器,多个容器同时一起对外提供服务,多个容器之间负载均衡.每个Ser ...
 - python 多线程中的同步锁 Lock Rlock Semaphore Event Conditio
			
摘要:在使用多线程的应用下,如何保证线程安全,以及线程之间的同步,或者访问共享变量等问题是十分棘手的问题,也是使用多线程下面临的问题,如果处理不好,会带来较严重的后果,使用python多线程中提供Lo ...
 - 科学计算三维可视化---TVTK管线与数据加载(可视化管线和图像管线了解)
			
一:TVTK的管线 使用管线技术将TVTK中各个对象穿连起来,几乎所有渲染引擎都会提到管线技术 在TVTK中,每个对象只需要实现相对简单的任务,整个管线则能根据用户的需求,实现复杂的数据可视化处理. ...
 - PHP运算符的规律
			
^异或的规律:只有真真和假假位假 !非 判断失误的另一名比如true是false &只要有真都为真就是真其他都是假 $$就是左边是真的就不判断了 规律是一样的 |或只有假假都为假其他都是真
 - Nginx跳转Tomcat
			
conf配置: server { listen 80; server_name www.-------.com; server_name_i ...
 - [2009国家集训队]小Z的袜子(hose)    浅谈莫队
			
浅谈莫队 推荐学习博客 http://foreseeable97.logdown.com/posts/158522-233333 借用题目: bzoj 2038 2009 国家集训队 小Z的袜子htt ...
 - [转]extern与头文件(*.h)的区别和联系
			
用#include可以包含其他头文件中变量.函数的声明,为什么还要extern关键字? 如果我想引用一个全局变量或函数a,我只要直接在源文件中包含#include<xxx.h> (xxx. ...
 - 解析html与xhtml的神器——HTMLParser与SGMLParser
			
有时候你要把抓回来的数据进行提取,过大篇幅的html标签,你若使用正则表达式进行匹配的话,显然是低效的,这时使用python的HTMLParser模块会显得非常方便.据说还有个比较好用的解析器叫:Be ...