关于单元测试及项目质量管理的总结
摘要:如果没写单元测试,如若在branch中对之前代码重构的话,则没有移回trunck上的勇气,有了单元测试,全部运行通过后则有信心合并。互联网公司更是需要重视单元测试,因为版本迭代比较迅速。因此一个好的单元测试框架及一个好的项目质量管理非常重要。本文即是我对这些的心得体会。
关键词:java, 单元测试, TestNG, DbUnit, Spring, 项目管理, 质量管理, PMP
解决问题:单元测试该如何实施?项目质量管理该如何执行?
在开发数据访问对象DAO层时,我们需要直接对数据层进行增删改查CRUD操作。单元测试非常重要,因为在开发期间经常需要进行代码重构,怎样才能保证代码重构的正确性呢,怎样给代码重构者以信心让他放手去做呢,需要执行单元测试,只要能保证接口功能不发生任何变化,与代码重构前完全相同,并且能直观的感受到这一一致性,开发者便能大胆地去干了。功能开发容易,单元测试难做。第一,单元测试间相互影响:单元测试1对数据A新增的一条数据可能会影响到单元测试2验证的正确性,你可能会想到在每个单元测试的起始时手动做一遍初始化,比如清理一遍表中数据,以清除其他单元测试的影响,这样笨且增加工作量。第二,开发者间相互影响:如果有多人同时需要执行测试用例,使用以上的办法后还是会发生问题,这时我们可能会选择每个开发者自己搭建一套数据库(内存或大型),以避免多人间的干扰,这样还是过于麻烦,且消耗资源。
下面是基于TestNG和DbUnit的单元测试框架,它的基本思想是管理事务,在单元测试起始时使用事务,在单元测试逻辑的最后将事务回滚,因此每个单元测试内对数据库的操作将不会实际对数据库有实质性影响,这样在单元测试中既可以测试逻辑的正确性,又避免影响到了其他单元测试和其他开发者,并且只需要依赖于统一的开发数据库即可,使用还很方便。以下还使用MyBatis的功能抽象出了一个统一平台,该统一平台提供了大多数的公共接口,如增删改查及批量操作等,大部分的基础操作可以通过调用这些接口就能完成,不通用的操作传入SQL语句也可执行。
一、MyBatis统一平台:MyBatisAngelWang.class
-
@Repository publicclassMyBatisAngelWangimplementsIRepository{ @Autowired privateGeneralDAO generalDAO; public<T extendsBase> T get(Class<T> clz,Long id){ HashMap hashMap = generalDAO.getLogically(clz, id); T ret =this.convert(hashMap, clz); return ret; } }
上面的代码即对数据库直接进行了操作,我们需要对此接口编写单元测试。具体的MyBatis使用方法,及MyBatisAngelWang统一平台的实现办法,需要另抽专门章节进行详细讨论。在这里就不做更深入研究了。
二、统一平台的单元测试:MyBatisAngelWangTest
-
import org.testng.annotations.Test; //@DatabaseSetup(value= "/dbunitData/TestAngelEntity.xml") publicclassMyBatisAngelWangTestextendsAbstractRollbackTest{ @Autowired privateMyBatisAngelWang myBatisAngelWang; @Test(enabled =false) publicvoid testGet(){ } }
可以看到此单元测试MyBatisAngelWangTest.class继承自一个抽象类:AbstractRollbackTest。
-
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; @ContextConfiguration(locations ={"classpath:spring-datasource-dbunit.xml", "classpath*:spring-services.xml"}) @TestExecutionListeners({DependencyInjectionTestExecutionListener.class, TransactionDbUnitTestExecutionListener.class,TransactionalTestExecutionListener.class}) @Transactional publicclassAbstractRollbackTestextendsAbstractTestNGSpringContextTests{ }
此抽象类是由我们自己定义的,其继承自抽象类:AbstractTestNGSpringContextTests,它由Springframework提供。我们可以通过ContextConfiguration注解来注入spring配置文件。
或者这样也可以。单元测试MyBatisAngelWangTest.class直接继承自AbstractTestNGSpringContextTests。减少了一层。
-
@ContextConfiguration("/config/Spring-db.xml") @Transactional @ActiveProfiles("test") publicclassMyBatisAngelWangTestextends AbstractTransactionalTestNGSpringContextTests{}
三、供给单元测试的专用spring配置文件:spring-datasource-dbunit.xml
-
<?xml version="1.0" encoding="UTF-8"?> <beansxmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd" default-autowire="byName"> <description>spring-datasource-configuration</description> <beanclass="com.angel.context.ApplicationContextAwareHelper"/> <!-- 定义事务管理器(声明式的事务) --> <beanid="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <propertyname="dataSource"ref="dataSource"/> </bean> <tx:annotation-driventransaction-manager="transactionManager"/> <beanid="propertyConfigurer"class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer"> <propertyname="locations"> <list> <value>classpath*:props/datasource_dev.properties</value> </list> </property> </bean> <beanid="dataSource"class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <propertyname="driverClassName"value="${jdbc.driver}"/> <propertyname="url" value="${jdbc.dbunit.url}"/> <propertyname="username"value="${jdbc.user}"/> <propertyname="password"value="${jdbc.password}"/> </bean> <!-- MyBatis 配置 --> <beanclass="org.mybatis.spring.mapper.MapperScannerConfigurer"> <propertyname="basePackage"value="com.angel.*.dao"/> <propertyname="sqlSessionFactoryBeanName"value="xSqlSessionFactory"/> </bean> <beanid="xSqlSessionFactory"class="org.mybatis.spring.SqlSessionFactoryBean"> <propertyname="dataSource"ref="dataSource"/> <propertyname="typeAliasesPackage"value="com.angel.*.entities"/> <propertyname="configLocation"value="classpath:mybatis/mybatis-config.xml"/> <propertyname="mapperLocations"value="classpath:/com/angel/dao/*.xml"/> <propertyname="plugins"> <array> <!--page interceptor--> <beanclass="com.angel.orm.db.QueryInterceptor"/> </array> </property> </bean> <tx:annotation-driventransaction-manager="transactionManager"/> </beans>
这样大家测试的数据库都是同一个了,也不会有任何的相互影响了。因为事务回滚了,不信的话可以提交一条Insert测试哦,执行完后查看数据库中并没有插入任何数据。然而,在一个单元测试中,先Insert再get是可以取到数据的,神奇吧?!
四、其它:ApplicationContextAwareHelper.class
-
publicclassApplicationContextAwareHelperimplementsApplicationContextAware{ privatestaticApplicationContext context; @Override publicvoid setApplicationContext(ApplicationContext applicationContext){ context = applicationContext; } publicstaticApplicationContext getContext(){ return context; } }
当我们需要比较动态的获取某些bean时,需要ApplicationContextAwareHelper类。比如说,我要自己拼接一个bean的名称,还要获得该bean,则可以使用下面的代码来获取:
-
DruidDataSource dataSource =ApplicationContextAwareHelper.getBean("dataSource_"+ dataSources[i]);
当然,这不属于单元测试的范畴了,有点跑题,但是蛮有用的,在这里记一下。
五、项目质量管理
通过上面数步就能够很好的实施单元测试了。
然而单元测试说来容易,执行难,有方法了,但推进它又是另外一件事了。在互联网公司中,很多个小项目并发进行,同时存在,项目成员亦流动性较大,相近的项目会分布在各项目组中。这样,每个小项目组可能有其自己的规范或是没有。规范就像法律一样,是个人素质的最底线、最低层约束。项目开发成员素质较高还好,可能不会引起混乱,当项目组成员多了,素质参差不齐就麻烦了。这时就需要执行项目经理职能的角色出现了。这时项目经理可以且应该具体要求各小组的开发流程、规范。在互联网项目中没有项目经理存在的情况下,可以由行政层面或配置项目管理专员来实现。
当然项目质量管理除了要规范单元测试以外,还有很多其他方法,具体可以查看我的这篇文章:http://www.cnblogs.com/wgp13x/p/4101314.html。其中的B图-质量管理即体现了项目质量管理的实施办法,可能不够细致,有空再详细叙述一下。
多谢大家的鼓励!
王安琪,英文名Angel,南京邮电大学计算机应用技术硕士学位。 熟悉Java、C#编程语言。专注于WebService、海量数据处理、搜索引擎技术、消息中间件技术、分布式文件存储、.NET应用程序开发、系统架构设计。主要从事大数据管理系统的研发,项目经理,系统架构师,就职于江苏金陵科技集团有限公司。
Email:aitanjupt@hotmail.com
QQ:289770363