β

跟我学Spring3(13.1 & 13.2):测试之概述和单元测试

ImportNew 74 阅读
原文出处: 张开涛

13.1  概述

13.1.1  测试

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

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

13.2  单元测试

13.2.1  概述

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

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

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

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

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

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;
}
}

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);
}

以上测试方法其实是没有必要的,对于非常简单的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代码:
作者:ImportNew

发表评论