文档章节

【第十三章】 测试 之 13.1 概述 13.2 单元测试 ——跟我学spring3

j
 jway
发布于 2015/05/21 16:08
字数 3709
阅读 10
收藏 0

13.1  概述

13.1.1  测试

软件测试的目的首先是为了保证软件功能的正确性,其次是为了保证软件的质量,软件测试相当复杂,已经超出本书所涉及的范围,本节将只介绍软件测试流程中前两个步骤:单元测试和集成测试。

 

Spring提供了专门的测试模块用于简化单元测试和集成测试,单元测试和集成测试一般由程序员实现。

 

13.2  单元测试

 

13.2.1  概述

       单元测试是最细粒度的测试,即具有原子性,通常测试的是某个功能(如测试类中的某个方法的功能)。

 

       采用依赖注入后我们的代码对Spring IoC容器几乎没有任何依赖,因此在对我们代码进行测试时无需依赖Spring IoC容器,我们只需要通过简单的实例化对象、注入依赖然后测试相应方法来测试该方法是否完成我们预期的功能。

 

       在本书中使用的传统开发流程,即先编写代码实现功能,然后再写测试来验证功能是否正确,而不是测试驱动开发,测试驱动开发是指在编写代码实现功能之前先写测试,然后再根据测试来写满足测试要求的功能代码,通过测试来驱动开发,如果对测试驱动开发感兴趣推荐阅读【测试驱动开发的艺术】。

 

       在实际工作中,应该只对一些复杂的功能进行单元测试,对于一些简单的功能(如数据访问层的CRUD)没有必要花费时间进行单元测试。

 

Spring对单元测试提供如下支持:

  • Mock对象:Spring通过Mock对象来简化一些场景的单元测试:

         JNDI测试支持:在org.springframework.mock.jndi包下通过了SimpleNamingContextBuilder来来创建JNDI上下文Mock对象,从而无需依赖特定Java EE容器即可完成JNDI测试。

         web测试支持:在org.springframework.mock.web包中提供了一组Servlet API的Mock对象,从而可以无需Web容器即可测试web层的类。

  • 工具类:通过通用的工具类来简化编写测试代码:

         反射工具类:在org.springframework.test.util包下的ReflectionTestUtils能通过反射完成类的非public字段或setter方法的调用;

         JDBC工具类:在org.springframework.test.util包下的SimpleJdbcTestUtils能读取一个sql脚本文件并执行来简化SQL的执行,还提供了如清空表、统计表中行数的简便方法来简化测试代码的编写。

 

 

接下来让我们学习一下开发过程中各层代码如何编写测试用例。

 

13.2.2  准备测试环境

       1、Junit安装:将Junit 4包添加到“pointShop”项目中,具体方法请参照【2.2.3  Hello World】。

 

       2、jMock安装:到jMock官网【http://www.jmock.org/】下载最新的jMock包,在本书中使用jMock2.5.1版本,将下载的“jmock-2.5.1-jars.zip ”包中的如下jar包拷贝到项目的lib目录下并添加到类路径:

objenesis-1.0.jar

jmock-script-2.5.1.jar

jmock-legacy-2.5.1.jar

jmock-junit4-2.5.1.jar

jmock-junit3-2.5.1.jar

jmock-2.5.1.jar

hamcrest-library-1.1.jar

hamcrest-core-1.1.jar

bsh-core-2.0b4.jar

    注:cglib包无需添加到类路径,因为我们之前已经提供。

 

3、添加Spring测试支持包:将下载的spring-framework-3.0.5.RELEASE-with-docs.zip包中的如下jar包拷贝到项目的lib目录下并添加到类路径:

 

    dist\org.springframework.test-3.0.5.RELEASE.jar

 

 

4、在“pointShop”项目下新建test文件夹并将其添加到【Java Build Path】中,该文件夹用于存放测试代码,从而分离测试代码和开发代码。

到此测试环境搭建完毕。

 

13.2.3  数据访问层

       数据访问层单元测试,目的是测试该层定义的接口实现方法的行为是否正确,其实本质是测试是否正确与数据库交互,是否发送并执行了正确的SQL,SQL执行成功后是否正确的组装了业务逻辑层需要的数据。

 

       数据访问层单元测试通过Mock对象与数据库交互的API来完成测试。

 

       接下来让我们学习一下如何进行数据访问层单元测试:

 

1、在test文件夹下创建如下测试类:

 

java代码:
package cn.javass.point.dao.hibernate;
//省略import
public class GoodsHibernateDaoUnitTest {
    //1、Mock对象上下文,用于创建Mock对象
    private final Mockery context = new Mockery() {{
        //1.1、表示可以支持Mock非接口类,默认只支持Mock接口
        setImposteriser(ClassImposteriser.INSTANCE);
    }};
    //2、Mock HibernateTemplate类
    private final HibernateTemplate mockHibernateTemplate = context.mock(HibernateTemplate.class);
    private IGoodsDao goodsDao = null; 
 
    @Before
    public void setUp() {
        //3、创建IGoodsDao实现
        GoodsHibernateDao goodsDaoTemp = new GoodsHibernateDao();
        //4、通过ReflectionTestUtils注入需要的非public字段数据
        ReflectionTestUtils.setField(goodsDaoTemp, "entityClass", GoodsModel.class);
        //5、注入mockHibernateTemplate对象
        goodsDaoTemp.setHibernateTemplate(mockHibernateTemplate);
        //6、赋值给我们要使用的接口
        goodsDao = goodsDaoTemp;
}
}
  • Mockery:jMock核心类,用于创建Mock对象的,通过其mock方法来创建相应接口或类的Mock对象。
  • goodsDaoTemp:需要测试的IGoodsDao实现,通过ReflectionTestUtils注入需要的非public字段数据。

 

2、测试支持写完后,接下来测试一下IGoodsDao的get方法是否满足需求:

 

java代码:
@Test
public void testSave () {
    //7、创建需要的Model数据
    final GoodsModel expected = new GoodsModel();
    //8、定义预期行为,并在后边来验证预期行为是否正确
    context.checking(new org.jmock.Expectations() {
        {
            //9、表示需要调用且只调用一次mockHibernateTemplate的get方法,
            //且get方法参数为(GoodsModel.class, 1),并将返回goods
            one(mockHibernateTemplate).get(GoodsModel.class, 1);
            will(returnValue(expected));
         }
    });
    //10、调用goodsDao的get方法,在内部实现中将委托给
    //getHibernateTemplate().get(this.entityClass, id);
    //因此按照第8步定义的预期行为将返回goods
    GoodsModel actual = goodsDao.get(1);
    //11、来验证第8步定义的预期行为是否调用了
   context.assertIsSatisfied();
    //12、验证goodsDao.get(1)返回结果是否正确
    Assert.assertEquals(goods, expected);
}

 

  • context.checking()该方法中用于定义预期行为,其中第9步定义了需要调用一次且只调用一次mockHibernateTemplate的get方法,且get方法参数为(GoodsModel.class, 1),并将返回goods对象。
  • goodsDao.get(1)调用goodsDao的get方法,在内部实现中将委托给“getHibernateTemplate().get(this.entityClass, id)”。
  • context.assertIsSatisfied()来验证前边定义的预期行为是否执行,且是否正确。
  • Assert.assertEquals(expected, actual)用于验证“goodsDao.get(1) ”返回的结果是否是预期结果。

 

以上测试方法其实是没有必要的,对于非常简单的CRUD没有必要写单元测试,只有相当复杂的方法才有必要写单元测试。

这种通过Mock对象来测试数据访问层代码其实一点意义没有,因为这里没有与数据库交互,无法验证真实环境中与数据库交互是否正确,因此这里只是告诉你如何测试数据访问层代码,在实际工作中一般通过集成测试来完成数据访问层测试。

 

13.2.4  业务逻辑层

业务逻辑单元测试,目的是测试该层的业务逻辑是否正确并通过Mock 数据访问层对象来隔离与数据库交互,从而无需连接数据库即可测试业务逻辑是否正确。

 

接下来让我们学习一下如何进行业务逻辑层单元测试:

 

 

1、在test文件夹下创建如下测试类:

 

java代码:
package cn.javass.point.service.impl;
//省略import
public class GoodsCodeServiceImplUnitTest {
    //1、Mock对象上下文,用于创建Mock对象
    private final Mockery context = new Mockery() {{
        //1.1、表示可以支持Mock非接口类,默认只支持Mock接口
        setImposteriser(ClassImposteriser.INSTANCE);
    }};
   
    //2、Mock IGoodsCodeDao接口
    private IGoodsCodeDao goodsCodeDao = context.mock(IGoodsCodeDao.class);;
   
    private IGoodsCodeService goodsCodeService;
 
    @Before
    public void setUp() {
        GoodsCodeServiceImpl goodsCodeServiceTemp = new GoodsCodeServiceImpl();
        //3、依赖注入
        goodsCodeServiceTemp.setDao(goodsCodeDao);
        goodsCodeService = goodsCodeServiceTemp;
}
}

    以上测试支持代码和数据访问层测试代码非常类似,在此不再阐述。

 

 

2、测试支持写完后,接下来测试一下购买商品Code码是否满足需求:

测试业务逻辑时需要分别测试多种场景,即如在某种场景下成功或失败等等,即测试应该全面,每个功能点都应该测试到。

 

2.1、测试购买失败的场景:

 

java代码:
@Test(expected = NotCodeException.class)
public void testBuyFail() {
    final int goodsId = 1;
    //4、定义预期行为,并在后边来验证预期行为是否正确
    context.checking(new org.jmock.Expectations() {
      {
          //5、表示需要调用goodsCodeDao对象的getOneNotExchanged一次且仅以此
          //且返回值为null
          one(goodsCodeDao).getOneNotExchanged(goodsId);
          will(returnValue(null));
      }
    });
    goodsCodeService.buy("test", goodsId);
    context.assertIsSatisfied();
}
  • context.checking()该方法中用于定义预期行为,其中第5步定义了需要调用一次且只调用一次goodsCodeDao的getOneNotExchanged方法,且getOneNotExchanged方法参数为(goodsId),并将返回null。
  • goodsCodeService.buy("test", goodsId)调用goodsCodeService的buy方法,由于调用goodsCodeDao的getOneNotExchanged方法将返回null,因此buy方法将抛出“NotCodeException”异常,从而表示没有Code码。
  • context.assertIsSatisfied()来验证前边定义的预期行为是否执行,且是否正确。
  • 由于我们在预期行为中调用getOneNotExchanged将返回null,因此测试将失败且抛出NotCodeException异常。

2.2、测试购买成功的场景:

 

 

java代码:
@Test()
public void testBuySuccess () {
    final int goodsId = 1;
    final GoodsCodeModel goodsCode = new GoodsCodeModel();
    //6、定义预期行为,并在后边来验证预期行为是否正确
    context.checking(new org.jmock.Expectations() {
      {
          //7、表示需要调用goodsCodeDao对象的getOneNotExchanged一次且仅以此
          //且返回值为null
          one(goodsCodeDao).getOneNotExchanged(goodsId);
          will(returnValue(goodsCode));
          //8、表示需要调用goodsCodeDao对象的save方法一次且仅一次
          //且参数为goodsCode
          one(goodsCodeDao).save(goodsCode);
        }
      });
    goodsCodeService.buy("test", goodsId);
    context.assertIsSatisfied();
    Assert.assertEquals(goodsCode.isExchanged(), true);
}
  • context.checking()该方法中用于定义预期行为,其中第7步定义了需要调用一次且只调用一次goodsCodeDao的getOneNotExchanged方法,且getOneNotExchanged方法参数为(goodsId),并将返回goodsCode对象;第8步定义了需要调用goodsCodeDao对象的save一次且仅一次。
  • goodsCodeService.buy("test", goodsId)调用goodsCodeService的buy方法,由于调用goodsCodeDao的getOneNotExchanged方法将返回goodsCode,因此buy方法将成功执行。
  • context.assertIsSatisfied()来验证前边定义的预期行为是否执行,且是否正确。
  • Assert.assertEquals(goodsCode.isExchanged(), true)表示goodsCode已经被购买过了。
  • 由于我们在预期行为中调用getOneNotExchanged将返回一个goodsCode对象,因此测试将成功,如果失败说明业务逻辑写错了。

 

到此业务逻辑层测试完毕,在进行业务逻辑层测试时我们只关心业务逻辑是否正确,而不关心底层数据访问层如何实现,因此测试业务逻辑层时只需Mock 数据访问层对象,然后定义一些预期行为来满足业务逻辑测试需求即可。

 

 

13.2.5  表现层

       表现层测试包括如Struts2的Action单元测试、拦截器单元测试、JSP单元测试等等,在此我们只学习Struts2的Action单元测试。

 

       Struts2的Action测试相对业务逻辑层测试相对复杂一些,因为牵扯到使用如Servlet API、ActionContext等等,这里需要通过stub(桩)实现或mock对象来模拟如HttpServletRequest等对象。

 

一、首先学习一些最简单的Action测试:

 

1、在test文件夹下创建如下测试类:

 

java代码:
package cn.javass.point.web.front;
import cn.javass.point.service.IGoodsCodeService;
import cn.javass.point.web.front.action.GoodsAction;
//省略部分import
public class GoodsActionUnitTest {
    //1、Mock对象上下文,用于创建Mock对象
    private final Mockery context = new Mockery() {{
        //1.1、表示可以支持Mock非接口类,默认只支持Mock接口
        setImposteriser(ClassImposteriser.INSTANCE);
    }};
   
    //2、Mock IGoodsCodeService接口
    private IGoodsCodeService goodsCodeService = context.mock(IGoodsCodeService.class);
 
    private GoodsAction goodsAction;
 
    @Before
    public void setUp() {
        goodsAction = new GoodsAction();
        //3、依赖注入
        goodsAction.setGoodsCodeService(goodsCodeService);
}
}

    以上测试支持代码和业务逻辑层测试代码非常类似,在此不再阐述。

 

2、测试支持写完后,接下来测试一下前台购买商品Code码是否满足需求:

类似于测试业务逻辑时需要分别测试多种场景,测试Action同样需要分别测试多种场景。

 

2.1、测试购买失败的场景:

 

java代码:
@Test
public void testBuyFail() {
    final int goodsId = 1;
    //4、定义预期行为,并在后边来验证预期行为是否正确
    context.checking(new org.jmock.Expectations() {
      {
          //5、表示需要调用goodsCodeService对象的buy方法一次且仅一次
          //且抛出NotCodeException异常
          one(goodsCodeService).buy("test", goodsId);
      will(throwException(new NotCodeException()));
      }
    });
    //6、模拟Struts注入请求参数
    goodsAction.setGoodsId(goodsId);
    String actualResultCode = goodsAction.buy();
    context.assertIsSatisfied();
    Assert.assertEquals(ReflectionTestUtils.getField(goodsAction, "BUY_RESULT"), actualResultCode);
    Assert.assertTrue(goodsAction.getActionErrors().size() > 0);
}
  • context.checking()该方法中用于定义预期行为,其中第5步定义了需要调用goodsCodeService对象的buy方法一次且仅一次且将抛出NotCodeException异常。
  • goodsAction.setGoodsId(goodsId):用于模拟Struts注入请求参数,即完成数据绑定。
  • goodsAction.buy()调用goodsAction的buy方法,该方法将委托给IGoodsCodeService实现完成,返回值用于定位视图。
  • context.assertIsSatisfied()来验证前边定义的预期行为是否执行,且是否正确。
  • Assert.assertEquals(ReflectionTestUtils.getField(goodsAction, "BUY_RESULT"), actualResultCode):验证返回的Result是否是我们指定的。
  • Assert.assertTrue(goodsAction.getActionErrors().size() > 0):表示执行Action时有错误,即Action动作错误。如果条件不成立,说明我们Action功能是错误的,需要修改。

 

2.2、测试购买成功的场景:

 

java代码:
@Test
public void testBuySuccess() {
    final int goodsId = 1;
    final GoodsCodeModel goodsCode = new GoodsCodeModel();
    //7、定义预期行为,并在后边来验证预期行为是否正确
    context.checking(new org.jmock.Expectations() {
      {
          //8、表示需要调用goodsCodeService对象的buy方法一次且仅一次
          //且返回goodsCode对象
          one(goodsCodeService).buy("test", goodsId);
          will(returnValue(goodsCode));
      }
    });
    //9、模拟Struts注入请求参数
    goodsAction.setGoodsId(goodsId);
    String actualResultCode = goodsAction.buy();
    context.assertIsSatisfied();
    Assert.assertEquals(ReflectionTestUtils.getField(goodsAction, "BUY_RESULT"), actualResultCode);
    Assert.assertTrue(goodsAction.getActionErrors().size() == 0);
}
  • context.checking()该方法中用于定义预期行为,其中第5步定义了需要调用goodsCodeService对象的buy方法一次且仅一次且将返回goodsCode对象。
  • goodsAction.setGoodsId(goodsId):用于模拟Struts注入请求参数,即完成数据绑定。
  • goodsAction.buy()调用goodsAction的buy方法,该方法将委托给IGoodsCodeService实现完成,返回值用于定位视图。
  • context.assertIsSatisfied()来验证前边定义的预期行为是否执行,且是否正确。
  • Assert.assertEquals(ReflectionTestUtils.getField(goodsAction, "BUY_RESULT"), actualResultCode):验证返回的Result是否是我们指定的。
  • Assert.assertTrue(goodsAction.getActionErrors().size() == 0):表示执行Action时没有错误,即Action动作正确。如果条件不成立,说明我们Action功能是错误的,需要修改。

 

通过模拟ActionContext对象内容从而可以非常容易的测试Action中各种与http请求相关情况,无需依赖web服务器即可完成测试。但对于如果我们使用htpp请求相关对象的该如何测试?如果我们需要使用ActionContext获取值栈数据应该怎么办?这就需要Struts提供的junit插件支持了。我们会在集成测试中介绍。

 

对于表现层其他功能的单元测试本书不再介绍,如JSP单元测试、拦截器单元测试等等。

 

 

原创内容,转载请注明私塾在线【http://sishuok.com/forum/blogPost/list/0/2555.html

本文转载自:http://jinnianshilongnian.iteye.com/blog/1465582

j
粉丝 4
博文 55
码字总数 0
作品 0
长沙
私信 提问
疯狂ios讲义之疯狂连连看游戏简介

《连连看》是一款广受玩家欢迎的小游戏,它具有玩法简单、耗时少等特征,尤其适合广大白领女性在办公室里休闲娱乐。图13.1显示了该游戏的游戏界面。 从图13.1可以看出,在《连连看》的游戏界...

博文视点
2014/02/12
289
0
大数据实验室(大数据基础培训)——Zeppelin的安装、配置及基础使用

为某企业做的培训,完整文档见:http://gudaoxuri.github.io/bd-lab/ 13. Zeppelin 13.1. 安装 tar -zxf zeppelin-0.5.6-incubating-bin-all.tgzrm -rf zeppelin-0.5.6-incubating-bin-all.t......

孤岛旭日
2016/05/07
396
0
《解析 Google Android DSK-智能手机开发范例手册》.(薛显亮).[PDF]&...

图书作者: 薛显亮 图书编号: 9787113143596 图书格式: PDF 出 版 社: 中国铁道出版社 出版年份: 2012 图书页数: 400-500 [内容简介] 《解析google android sdk——智能手机开发范例手册》基于...

ckook
2013/06/12
153
0
Struts2+Spring3+Hibernate——整合byMaven

在平时的JavaEE开发中,为了能够用最快的速度开发项目,一般都会选择使用Struts2,SpringMVC,Spring,Hibernate,MyBatis这些开源框架来开发项目,而这些框架一般不是单独使用的,经常是Str...

凡尘里的一根葱
2015/11/10
247
0
[iOS Animation]-CALayer 绘图效率

绘图 不必要的效率考虑往往是性能问题的万恶之源。 ——William Allan Wulf 在第12章『速度的曲率』我们学习如何用Instruments来诊断Core Animation性能问题。在构建一个iOS app的时候会遇到...

浩浩老师
2015/09/17
90
3

没有更多内容

加载失败,请刷新页面

加载更多

uni app 零基础小白到项目实战

$emit 子组件传给父组件$ref 父组件操作子组件 公用模板 uni-app全局变量的几种实现方法 const websiteUrl = 'http'const now = Date.now || function() { return new Date().getTime......

达达前端小酒馆
32分钟前
7
0
Tomcat是如何实现异步Servlet的

前言 通过我之前的Tomcat系列文章,相信看我博客的同学对Tomcat应该有一个比较清晰的了解了,在前几篇博客我们讨论了Tomcat在SpringBoot框架中是如何启动的,讨论了Tomcat的内部组件是如何设...

木木匠
56分钟前
31
0
mysql中间件分享(Mysql-prxoy,Atlas,DBProxy,Amoeba,cobar,TDDL)

hello 各位小伙伴大家好,我是小栈君,这期我们分享关于mysql中间件的研究,也就是数据层的读写分离和负载均衡,希望能够在实际的应用中能够帮助到各位小伙伴。 下期我们将继续分享go语言的系...

IT干货栈
今天
15
0
OSChina 周一乱弹 —— 人生,还真是到处是意外

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @这次装个文艺青年吧 :#今日歌曲推荐# 分享lil peep的单曲《High School》 《High School》- lil peep 手机党少年们想听歌,请使劲儿戳(这里...

小小编辑
今天
1K
13
Spring使用ThreadPoolTaskExecutor自定义线程池及实现异步调用

多线程一直是工作或面试过程中的高频知识点,今天给大家分享一下使用 ThreadPoolTaskExecutor 来自定义线程池和实现异步调用多线程。 一、ThreadPoolTaskExecutor 本文采用 Executors 的工厂...

CREATE_17
今天
12
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部