`

如何优雅的去做DAL层的UT

 
阅读更多

引子

UT的重要性不言而喻,这里不用多说。但是,码农都知道,一段逻辑往往涉及到很多外部系统调用(不同的数据源、不同的服务等等),配合完成一段code真正想要完成的逻辑。
而UT(Unit Test)本身的重要思想之一,就是测试本单元的核心逻辑。我测试A,你却因为A依赖B,而导致测试的code跑不了,这个不科学。
于是,有了各种各样的mock技术,来模拟B的行为,按照你的需要,返回你期望的数据。这种测试方式,很好的实现了自身业务逻辑接触对外部系统的依赖,在Service层面非常有意义。

但是,在对DAL层面的测试上,Mock技术却不太合适。原因很明显,DAL层面上,通常没有负责的业务逻辑,code本身更多的是CRUD的操作,测试本身更多的是关注如下几点:

  • SQL本身是否有语法错误
  • SQL本身的语义是否正确,比如:SQL是否按照我预设的查询条件返回了正确的结果。
  • SQL执行过程中,原生的结果集和使用的ORM框架有映射关系的配置错误。

鉴于以上特点,你会发现,对于这层的测试,我们就是希望SQL真实的去跑。Mock掉这部分的代码执行逻辑,测试本身的意义也就不大了。但是,真的执行SQL,不就又使UT跟DB这个外部环境挂上勾了吗?这个不是特别蛋疼。。。

针对这种情况,码农常见的处理手法有两种:

  1. 找一个可以运行的测试环境的DB实例,连接上去,真跑一下。这个实例可能是测试环境本身的,也可能是开发同学自己本地启动的。总之,这时代码的执行,还是依赖了一个外部系统。
  2. 爷特么不测了。直接测试环境或者预发布环境跑起来看。。。额,对于这种,下面的文字就基本没意义了。。。

本文主要的目的,是从实战的角度,介绍一个可行的方案去避免上面提到的问题,同时达到测试的目的。

做好DAL层UT需要解决的问题

既然要稍微优雅点的完成上面的任务,那么,必须解决如下的几个问题:
也是本文后面会介绍的几个使用工具。

  1. 如何隔离对外部DB的依赖:HSQLDB是一个纯java实现轻量级DB,其中提供的内存运行模式,非常适合UT这种场景。
  2. 如果内存DB随着UT启动,那么何时以及如何建立我需要的DB Table呢:Apache DdlUtils是个轻量级的DDL Java工具,非常适合结合HSQLDB一块使用,完成对DB table schema的执行创建过程。
  3. 如何初始化你需要的测试数据呢:DbUnit是个基于JUnit扩展出来的专门focus在帮你准备测试数据集的小项目,可以节省你很多通过JDBC方式准备数据的时间。

以上工具,其实都是单独项目,完全可以单独使用,不过组合一块,也威力无穷,实在是居家旅行、DB测试必备之利器。

基于Spring和ibatis的DAL层测试方案

上面蛋扯完了,该来点干货了。鉴于阿里系内部的DAL层绝大部分使用spring+ibatis实现,以下实例会基于这些框架的基础进行。

依赖引入

首先,在你的工程pom.xml中加入如下test scope的配置,用于测试期间的类库准备。
如下版本号,仅供参考,可以自行选择。

 

<!--Test scope-->
        <!--Used to set up a memory DB-->
        <dependency>
            <groupId>org.hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <version>2.0.0</version>
            <scope>test</scope>
        </dependency>
        <!--Used to execute the DDL to create the table schema to create the target table in the hsqldb-->
        <dependency>
            <groupId>org.apache.ddlutils</groupId>
            <artifactId>ddlutils</artifactId>
            <version>1.0</version>
            <scope>test</scope>
        </dependency>
        <!--Used to prepare your test data on your table-->
        <dependency>
            <groupId>org.dbunit</groupId>
            <artifactId>dbunit</artifactId>
            <version>2.5.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.8</version>
            <scope>test</scope>
        </dependency>

 

 

准备测试datasource

基于Spring的配置文件中,真实的数据源无非是datasource的配置,所以,测试前我们只要将datasource替换成我们准备的hsqldb即可。这里,准备一份test-datasource.xml,放到test/resources路径下的任何子目录中(后面的TestCase读的到即可),代码如下:

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="test-dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
        <!--use memory mode to perform the test-->
        <property name="url" value="jdbc:hsqldb:mem:uic-testdb"/>
        <property name="username" value="sa"/>
        <property name="password" value=""/>
    </bean>

    <!-- Used for Ibatis -->
    <bean id="sqlMapClient"
          class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"
          abstract="true">
        <property name="dataSource" ref="test-dataSource" />
    </bean>
</beans>

 

 

以上配置中,需要注意如下几点:

  • test-dataSourcebean配置只要照抄就好,username、password均为hsqldb的默认配置,url中的格式以冒号分割,第三段的mem会告诉hsqldb启动内存DB模式,最后一节是你的DB实例的名字,你可以随便改。driverClassName可以看出,这里启用的driver是hsqldb自己的JDBC驱动。
  • sqlMapClient中直接注入这个测试用的datasource就行啦。

准备建表

这里,表本身的schema完全取决于你自己的实际项目。因为我选择了使用DdlUtils来完成建表的任务,所以按照他的解析方式,准备一份他识别的建表文件,test-create-table-ddl.xml放到test/resources路径下的任何子目录中(后面的TestCase读的到即可)。以我的项目为例,文件长成下面这个样子(关于这个文件的各个元素的详细描述,可以参考这里,但大致的用法,看下面的例子基本也就懂了。)

 

<?xml version="1.0"?>
<!DOCTYPE database SYSTEM "http://db.apache.org/torque/dtd/database.dtd">
<!--This will be used by DdlUtils to created table schema-->
<database name="uic-testdb">

    <table name="uic_partner_relation">
        <column name="id" type="BIGINT" required="true" primaryKey="true" size="20"/>
        <column name="gmt_create" type="TIMESTAMP" required="true" />
        <column name="gmt_modified" type="TIMESTAMP" required="true"/>
        <column name="account_id" type="BIGINT" size="20" required="true"/>
        <column name="attribute1" type="VARCHAR" size="128" required="true" />
        <column name="attribute2" type="VARCHAR" size="128"  required="true"/>
        <column name="attribute3" type="CHAR" size="1"  required="true"/>
        <!--You can create indexes with index tag-->
        <index name="ind_primary_key">
            <index-column name="id" />
        </index>
        <index name="ind_pa_pa">
            <index-column name="attribute1" />
            <index-column name="attribute2" />
        </index>
    </table>

</database>

 

这里,database标签的name属性就是你上面配置的DB实例的name,保持一致就好。

 

准备测试数据

因为使用DbUnit,所以按照他的规范,配置好你需要的数据即可,xml的格式比较简单,每行代表一条数据,DbUnit会自动帮你完成数据初始化的过程。假设这份文件叫prepare-test-data.xml,放到test/resources路径下的任何子目录中(后面的TestCase读的到即可)。以我的项目为例,文件长成下面这个样子:

 

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
    <test_db_table id='1' gmt_create='2001-01-01' gmt_modified='2001-01-01' account_id='100' attribute1='facebook' attribute2="200" attribute3="Y"/>
    <test_db_table id='2' gmt_create='2001-01-01' gmt_modified='2001-01-01' account_id='101' attribute1='facebook' attribute2="201" attribute3="Y"/>
    <test_db_table id='3' gmt_create='2001-01-01' gmt_modified='2001-01-01' account_id='101' attribute1='twitter' attribute2="201" attribute3="N"/>
</dataset>

 

万事具体,只欠TestClass啦

上面已经把所有需要的配置文件都准备好了,以我的UTClass适当简化为例,代码如下:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/biz/ibatis/test-datasource.xml", "/biz/spring/xxx_dal.xml"})
public class MyDaoImplTest {

    @Autowired
    MyDao myDao;

    @Autowired
    DataSource testDataSource;

    @Before
    public void setUp() throws Exception {
        // prepare the table first
        Platform platform = PlatformFactory.createNewPlatformInstance(testDataSource);

        Database database = new DatabaseIO().read(new InputStreamReader(
                getClass().getResourceAsStream("/biz/ibatis/test-create-table-ddl.xml")));
        platform.alterTables(database, false);
        // prepare test data
        DataSourceDatabaseTester databaseTester = new DataSourceDatabaseTester(testDataSource);
        databaseTester.setSetUpOperation(DatabaseOperation.CLEAN_INSERT);
        databaseTester.setDataSet(readDataSet());
        databaseTester.onSetup();
    }

    private IDataSet readDataSet() throws Exception {
        return new FlatXmlDataSetBuilder().build(Resources.getResource("biz/ibatis/testdata/prepare-test-data.xml"));
    }

    @Test
    public void testListPartnerRelationFullRecords() throws Exception {
        // case 1 : no record for 123 in FB
        List<PartnerRelationFullRecord> fullRecords = myDao.listPartnerRelationFullRecords("123", "facebook");
        assertTrue(CollectionUtils.isEmpty(fullRecords));
        // case 2 : only one record for 200 in FB
        fullRecords = myDao.listPartnerRelationFullRecords("200", "facebook");
        assertEquals(1, fullRecords.size());
        // case 3 : 4 records for 201 in vk
        fullRecords = myDao.listPartnerRelationFullRecords("201", "vk");
        assertEquals(4, fullRecords.size());
    }

}

 

大概解释一下上面的code:

  • 使用SpringJUnit4ClassRunner作为TestRunner运行这个TestCase,直接帮你完成依赖注入,充分利用Spring IOC的优势。
  • 直接通过@ContextConfiguration注解,标识出你需要准备Spring容器。这里,第一份配置是上面介绍过的,你精心准备的测试环境的datasource的配置,第二份文件是生产直接使用的Dao的配置,代码不贴了,就是code中MyDao的配置,这就完成了测试datasource的注入。
  • 参考setUp方法中的实现和注释,你基本就可以看出来如果通过DdlUtils和DbUnit完成table的建立以及测试数据的初始化。

完成了以上几步配置,你就可以开始你的逻辑测试了。不依赖网络,不依赖DB,运行全靠你自己了。

 

总结

总算写完了,如果你能看到这里,也算我没白码这些字。
老实说,整个过程弄下来也并不算简单,但是UT的编写是一个一劳永逸的过程,我想大家提高代码测试的意识才是最重要的。
毕竟,意识最重要,工具只是实现的手段而已。

分享到:
评论
1 楼 TonyLian 2014-09-28  
从实用的角度出发。
DDL的XML可以由读取DB设计(固定格式的Excel或直接连接真实DB)自动完成

DML不是自己写XML而是写固定格式的Excel

这样就更接地气了。

相关推荐

    DAL层(三层架构中的)

    下面将详细探讨DAL层、三层架构以及与数据库的交互。 **数据访问层(DAL)** DAL是三层架构中的一层,主要任务是为业务逻辑层(Business Logic Layer, BLL)提供透明的数据访问服务。它封装了对数据库的所有操作,...

    C#代码模板生成器;Model层;DAL层;BLL层代码自动生成

    本文将深入探讨"C#代码模板生成器",特别是针对Model层、DAL(数据访问层)层和BLL(业务逻辑层)层的代码自动生成。 首先,我们来理解Model层。在C#应用中,Model层是业务对象的容器,它代表了应用程序的数据模型...

    自动生成DAL层的MODEL的C#代码、以及通用存储过程

    Code Generation Tool(C#)是专门针对SQL SERVER 2005以上数据库实现自动生成DAL层的MODEL的C#代码。在分层开发中,将数据库表实现类操作是常用的方法,但是枯燥和繁琐的代码编写占用了我们大量的时间,于是通过使用...

    自动生成DAL. 和Model层的代码工具,源码,CodeGen

    《CodeGen:自动生成DAL与Model层代码的利器》 在软件开发过程中,数据访问层(DAL)和模型层(Model)的代码编写是一项基础且耗时的工作。为提高开发效率,许多开发者会采用代码生成工具,例如我们今天讨论的...

    Model/BLL/DAL 三层小例子

    它将应用程序分为三个主要的逻辑层次:表现层(Presentation Layer)、业务逻辑层(Business Logic Layer)和数据访问层(Data Access Layer),简称MVC(Model-View-Controller)中的M、BLL和DAL。这个“Model/BLL/...

    三层结构生成器根据建好的数据库生成Modle层与DAL层

    三层结构生成器正是基于这样的架构设计,它可以根据已有的数据库模型自动生成对应的Model层(对应数据访问层)和DAL层(数据访问层)的代码。Model层通常包含了数据库表的实体类,而DAL层则包含了对数据库进行操作的...

    .net三层结构生成器,帮助你自动生成Model,Dal,Bell层代码

    .NET三层架构生成器是一款高效的开发工具,专为.NET开发者设计,旨在自动化生成Model、Dal(数据访问层)和Bell(业务逻辑层)的代码。这一工具显著提升了开发效率,让开发者可以把精力更多地集中在核心业务逻辑和...

    C# 生成多种DAL层次代码

    在IT行业中,DAL(Data Access Layer,数据访问层)是应用程序与数据库交互的关键部分,它封装了所有关于数据库操作的代码,确保业务逻辑与数据存储的分离,提高代码的可维护性和可重用性。本资源"**C# 生成多种DAL...

    简单三层代码生成器(Models DAL BLL)

    本资源"简单三层代码生成器(Models DAL BLL)"正是为开发者提供了一个工具,能够自动生成这三个层次的代码,从而提高开发效率,减少重复工作。 首先,我们来详细解释一下这三个层次: 1. **Models(模型层)**:...

    三层架构 c# BLL DAL MODEL

    在IT领域,尤其是在软件开发中,三层架构是一种广泛采用的设计模式,它将应用程序分为三个主要层次:表示层(UI Layer)、业务逻辑层(Business Logic Layer, BLL)和数据访问层(Data Access Layer, DAL)。...

    将Maps内的XML文件放在DAL层

    标题中的"将Maps内的XML文件放在DAL层"指的是在设计软件架构时,特别是采用分层架构(如DAL、BLL和UI层)的系统中,将数据访问层(DAL)中用于映射数据库操作的XML配置文件放置在DAL层内部。这种做法的主要目的是...

    java 通用数据访问层dal

    java dal 封装的通用dao 数据访问层,如果你不喜欢用Hibernate、Mybaits这类ORM框架,喜欢Spring JdbcTemplate或DbUtils,那么可以试试这个封装的通用dal

    三层架构入门讲解(c#)UIL、BLL、DAL

    ### 三层架构入门讲解(C#):UIL、BLL、DAL #### 一、什么是三层架构? 在软件开发领域,三层架构是一种常见的设计模式,它将应用程序分为三个不同的层次,每个层次负责不同的任务,从而使得整个系统更加模块化、...

    在ASP.NET 2.0中操作数据:在ASP_NET页面中处理BLL-DAL层的异常

    在ASP.NET 2.0中操作数据:在ASP_NET页面中处理BLL-DAL层的异常

    LinqToSql类生成工具 自动生成Model层 和DAL层的代码,并且带有注释

    这个工具能够自动生成Model层和DAL(Data Access Layer)层的代码,极大地简化了数据库交互的工作流程,同时也提高了开发效率。 1. **Linq**: LINQ是.NET Framework 3.5引入的一项强大特性,全称为Language ...

    在ASP.NET 2.0中操作数据之十八:在ASP.NET页面中处理BLL/DAL层的异常

    然而,当BLL或DAL层发生异常时,如何在页面级别优雅地处理这些异常并展示友好错误信息是关键问题。本文将探讨如何在ASP.NET页面中处理BLL和DAL层的异常,并在DAL层抛出自定义异常。 首先,了解ASP.NET页面生命周期...

    petshop3层架构范例,供新手学习,Model+DAL+IDAL+DALFactory+BLL

    DAL层通过接口`IDAL`定义了操作数据的方法,这样业务逻辑层就可以使用这些接口而不必关心具体的数据存储技术。 5. **接口层(IDAL)**:"IDAL"目录包含接口文件,定义了数据访问层需要实现的方法。使用接口可以使...

    三层架构 DAL BLL UIL 三层架构图解

    三层架构 DAL BLL UIL 三层架构图解 在软件开发中,三层架构是最常见的设计模式之一,主要将整个业务应用划分为表现层(UI)、业务逻辑层(BLL)、数据访问层(DAL)和数据存储层(DBL)。这种架构设计的目的是为了...

    三层架够EntityFramework应用于DAL层 + 抽象工厂模式

    在这个场景中,我们看到"EntityFramework应用于DAL层",这意味着Entity Framework将作为数据访问层的核心技术,用于与数据库进行交互。 Entity Framework(EF)是微软提供的一个对象关系映射(ORM)框架,它允许...

Global site tag (gtag.js) - Google Analytics