断断续续地学习了一些单元测试的知识,在最近的编码过程中有意识地进行了实践,勉强能达到一点测试的既定目的,但感觉疑惑仍然不少。
在javaeye上也拜读了诸多高人们关于单元测试、TDD方面的文章,获益良多,但是感觉很多文章起点有些高,像我这样比较笨的人读多次都不一定能领悟,适合入门一级的测试文章不太多。因此我想将自己实施单元测试的一些实践整理出来,尽量表述出我的想法,尽量提供比较详细的代码,希望初次接触单元测试的朋友能从中受益,从而少走一些弯路。另外,我在学习和实施单元测试的过程中也有很多不解和困惑,希望可以得到大家的指点。
先列出一个测试代码实例吧。
业务逻辑:对员工信息的增删改查;
业务对象:EmpBO;
业务对象接口代码(部分):
java 代码
-
-
-
-
-
-
-
- public interface EmpBOI {
-
-
-
-
-
-
-
-
- public ArrayList saveNewEmp(CcpAclsUserExtForm fmCau);
-
-
-
-
-
-
-
-
-
- public ArrayList saveEmp(CcpAclsUserExtForm fmCau);
-
-
-
-
-
-
-
-
-
- public ArrayList removeEmp(String ccpUserGuid);
-
-
-
-
-
-
-
-
- public ArrayList getEmpById(String ccpUserGuid);
-
- }
这段代码反映了典型的CRUD业务逻辑操作,对实现类的测试基本说明了我在实践单元测试时遇到的问题。
对于写代码的顺序,我一般是先根据概设文档定义出业务逻辑操作类接口,测试类,业务逻辑类,然后针对每一个接口方法,先写该方法的测试用例,然后在业务逻辑类中实现该方法,运行测试,修改或重构实现代码。
下面来看测试代码。
首先定义了一个测试基类,扩展自Spring提供的JUnit封装类AbstractDependencyInjectionSpringContextTests。
测试基类代码:
java 代码
- public abstract class SpringUnitTest extends AbstractDependencyInjectionSpringContextTests{
-
-
-
-
- protected String[] getConfigLocations() {
-
- setAutowireMode(AUTOWIRE_BY_NAME);
- return new String[]{Constants.DEFAULT_SPRING_CONTEXT_HIB,
- Constants.DEFAULT_SPRING_CONTEXT_SER,
- Constants.DEFAULT_SPRING_CONTEXT_RES,
- Constants.DEFAULT_TEST_SPRING_CONTEXT_CCP};
- }
- }
SpringUnitTest类扩展AbstractDependencyInjectionSpringContextTests,实现了其抽象方法getConfigLocations,将Spring对bean的匹配方式设为AUTOWIRE_BY_NAME,根据名称而非type查找bean,然后返回Spring相关 配置文件的路径。配置文件中定义了DataSource数据源,测试代码运行时连接数据库。
对员工管理BO的测试类EmpBOTest定义:
java 代码
- public class EmpBOTest extends SpringUnitTest {
- protected EmpBOI empBO;
-
- public void setEmpBO(EmpBOI empBO) {
- this.empBO = empBO;
- }
- }
测试类中注入了待测试的业务类。
首先,写新增员工方法saveNewEmp的测试代码:
java 代码
- public void testSaveNewEmp(){
- CcpAclsUserExtForm fmCau = new CcpAclsUserExtForm);
- fmCau.setCauGuid("9DDC036A177088F0FAE833CEA0971DF0");
- fmCau.setName("吕南");
- fmCau.setSex("02");
- fmCau.setBirth("1989-11-24");
- fmCau.setAddress("北京市大兴区");
- fmCau.setMobile("13810384254");
- fmCau.setWorkUnit("威天软件");
- fmCau.setTel("01055669235");
- ArrayList retlist = this.ccpUserBO.saveNewEmp(fmCau);
- assertEquals("true",(String)retlist.get(0));
- }
然后在业务实现类EmpBO中具体实现该方法的业务逻辑。
说明一下,CcpAclsUserExtForm是对员工pojo对象的封装,员工对象的主键是cauGuid,这是一个由程序依据一定规则随机产生的32位字符串。
OK,现在业务代码有了,测试代码也已就位,数据库已运行,配置文件也在正确路径上,一切准备工作均已就绪,可以运行测试了。
Run测试代码,发现bug,修改业务代码,再次run,直到绿条出现,一个方法测试完成,一路有惊无险,很顺利。
当然上面的测试代码只测试了程序正常执行的分支,没有覆盖其它可能出现异常的分支,完全可以加入对异常分支的测试代码,不过这个业务比较简单,对正常分支测试通过基本就OK了。个人觉得,单元测试没有必要太看重测试覆盖率,够用就行了。
这样,测试类中就有了一个测试方法了:
java 代码
- public class EmpBOTest extends SpringUnitTest {
- protected EmpBOI empBO;
-
- public void setEmpBO(EmpBOI empBO) {
- this.empBO = empBO;
- }
-
- public void testSaveNewEmp(){
- CcpAclsUserExtForm fmCau = new CcpAclsUserExtForm);
- fmCau.setCauGuid("9DDC036A177088F0FAE833CEA0971DF0");
- fmCau.setName("吕南");
- fmCau.setSex("02");
- fmCau.setBirth("1989-11-24");
- fmCau.setAddress("北京市大兴区");
- fmCau.setMobile("13810384254");
- fmCau.setWorkUnit("威天软件");
- fmCau.setTel("01055669235");
- ArrayList retlist = this.ccpUserBO.saveNewEmp(fmCau);
- assertEquals("true",(String)retlist.get(0));
- }
- }
测试代码运行后,数据库中就有了员工信息记录。接下来是实现员工信息的修改逻辑。测试代码如下:
java 代码
- public void testSaveEmp(){
- CcpAclsUserExtForm fmCau = new CcpAclsUserExtForm();
- fmCau.setCauGuid("FD705E2FA08E95956241040BE3D83D69");
- fmCau.setName("吕南修改");
- fmCau.setSex("02");
- fmCau.setBirth("1968-10-15");
- fmCau.setAddress("北京市昌平区");
- fmCau.setMobile("13999999999");
- fmCau.setWorkUnit("join-cheer");
- fmCau.setTel("01058561199");
- ArrayList retlist = this.ccpUserBO.saveEmp(fmCau);
- assertEquals("true",(String)retlist.get(0));
- }
然后实现具体业务逻辑代码,运行测试,OK,绿条出现,测试通过。完事大吉了吗?
貌似没有问题,但是上面的测试代码实际是很脆弱。请注意这一句:
fmCau.setCauGuid("FD705E2FA08E95956241040BE3D83D69");
这是设置待修改的员工主键,问题就在这儿。主键是随机产生的,每个员工的主键值都是不同的。此处我将主键写死,该主键代表运行新增员工的测试代码testSaveNewEmp得到的员工记录。那么,我下次运行新增员工方法testSaveNewEmp时,得到的员工记录主键发生了变化,为了使修改员工的测试方法testSaveEmp正确运行,我发布修改测试方法中的代码,将待修改的员工对象主键值设为这次得到的员工记录的主键值。
如此一来,我的测试类无法运行,只能每次运行其中的一个测试方法。这种笨方法在业务逻辑开发阶段还能承受,但测试的自动化运行就没办法了。测试结果无法再现,每次运行都要修改测试代码,太恐怖了!
经过N久的痛苦之后,我决定改变测试基类。现在的基类扩展自AbstractDependencyInjectionSpringContextTests,我将其改为继承自AbstractTransactionalDataSourceSpringContextTests,这样我的测试类就自动具有事务功能,即在每个测试方法执行后Spring会自动回滚事务,将数据库还原为初始状态。然后在我的测试类里定义一个私有方法,用于向数据库中插入测试数据,每个测试方法运行时都要调用这个方法产生数据,因为测试类具有自动回滚功能,每个方法运行完成后,数据库会还原,将测试数据清空,以待下次运行时插入。这样一来,每次测试类运行时,可以保证测试数据都是相同的。这样我就可以随时运行测试而不用担心修改测试代码了。
修改后的测试基类如下:
java 代码
- public class SpringTransactUnitTest extends
- AbstractTransactionalDataSourceSpringContextTests {
-
-
-
-
- protected String[] getConfigLocations() {
-
- setAutowireMode(AUTOWIRE_BY_NAME);
- return new String[]{Constants.DEFAULT_SPRING_CONTEXT_HIB,
- Constants.DEFAULT_SPRING_CONTEXT_SER,
- Constants.DEFAULT_SPRING_CONTEXT_RES,
- Constants.DEFAULT_TEST_SPRING_CONTEXT_CCP,
- Constants.DEFAULT_TEST_SPRING_CONTEXT_APP
- };
- }
- }
修改后的测试类如下:
java 代码
- public class EmpBOTest extends SpringTransactUnitTest {
- protected EmpBOI empBO;
-
- public void setEmpBO(EmpBOI empBO) {
- this.empBO = empBO;
- }
-
- private ArrayList add(){
- CcpAclsUserExtForm fmCau = new CcpAclsUserExtForm);
- fmCau.setCauGuid("9DDC036A177088F0FAE833CEA0971DF0");
- fmCau.setName("吕南");
- fmCau.setSex("02");
- fmCau.setBirth("1989-11-24");
- fmCau.setAddress("北京市大兴区");
- fmCau.setMobile("13810384254");
- fmCau.setWorkUnit("威天软件");
- fmCau.setTel("01055669235");
- ArrayList retlist = this.ccpUserBO.saveNewEmp(fmCau);
- return retlist;
- }
-
- public void testSaveNewEmp(){
- ArrayList retlist = add();
- assertEquals("true",(String)retlist.get(0));
- }
-
- public void testSaveEmp(){
- add();
- CcpAclsUserExtForm fmCau = new CcpAclsUserExtForm();
- fmCau.setCauGuid("9DDC036A177088F0FAE833CEA0971DF0");
- fmCau.setName("王小二");
- ArrayList retlist = this.ccpUserBO.saveEmp(fmCau);
- assertEquals("true",(String)retlist.get(0));
- }
- }
业务类中的删除与查询方法就不在此赘述了。
一个小技艺:私有方法add用于产生测试数据,其实也可能通过AbstractTransactionalDataSourceSpringContextTests提供的jdbcTemplate执行原生SQL语句来向数据库中插入数据。这在测试业务类没有提供新增方法时非常实用。
另外,我认为,测试时最好使用一个单独的测试库,不要用项目组公用的开发库,否则会产生很多麻烦。
以上是我在具体项目中实施单元测试的一点心得体会,贴出来给大家参考,也欢迎大家提出批评意见。
分享到:
相关推荐
本文对嵌入式C语言的单元测试进行初步研究,探讨了软件测试模式、工具选择以及用例设计方法,以期提高软件测试的效率与质量,为相关领域的技术实践提供参考。 首先,本文界定了单元测试的含义和重要性。单元测试是...
### PHP单元测试入门 #### 一、什么是单元测试? 单元测试是一种软件开发过程中的测试方法,主要...通过以上内容的学习和实践,相信你已经对PHP单元测试有了初步的了解,并能够着手为自己的项目编写有效的单元测试。
围绕软件测试的核心概念,介绍了软件测试的基本方法和过程,并通过丰富的案例予以...最后提供了综合应用案例实践,从自动化测试的角度,结合单元测试工具、功能测试工具和性能测试工具,讨论自动化测试的设计与实施。
最后,写作训练环节,通过妙笔生花的练习,要求学生根据画面进行详细描述,这不仅锻炼了学生的观察力和想象力,也是写作技巧的初步实践。学生通过这样的训练,能够逐步掌握如何使用恰当的词汇和句式来表达自己的思想...
你好,这里是关于“01_单元测试规范”的详细解析,主要涵盖了单元测试的基本概念、重要性、使用框架以及一些初步的规范。 单元测试是软件开发过程中的一种重要质量保证手段,它针对的是代码中的最小可测试单元,...
**3.2 单元测试实践** - **实践项目介绍**: 介绍具体的单元测试项目背景。 - **Model的单元测试**: 对数据模型进行单元测试。 - **Controller和View的单元测试**: 对控制器和视图组件进行单元测试。 **3.3 单元...
这份文档是针对小学一年级学生设计的数学第四单元的测试卷,涵盖了基础的几何形状认知、图形判断、计数以及简单的加减法等知识点。以下是详细的内容解析: 1. **几何形状识别**:这部分主要考察学生对基本几何形状...
综上所述,这份《一年级数学下册位置单元测试题(一).pdf》是一套综合性强、富有实践性的练习,它将方位识别、数量和次序、顺序逻辑、空间位置关系、序列和定位等知识点巧妙地结合在一起,旨在全面考察和提高学生对于...
华师大版教材中的图形初步单元测试卷,就是对学生这一阶段学习成效的检验。 试卷开篇的选择题部分,以基础概念为考察点,要求学生对于点、线、面、体等几何基本元素有清晰的认识。例如,试卷中指出平角是一个180度...
为了巩固这一部分的学习成果,本单元测试题集围绕立体图形的不同视图、性质、搭建方法和计数技巧等方面设计了一系列的问题,旨在全面检测学生对相关知识点的掌握程度,并通过实践来提高他们解决实际问题的能力。...
总的来说,这份测试题覆盖了一年级数学的基本内容,包括数的认识、比较、操作以及简单的逻辑推理,旨在帮助孩子们建立初步的数学概念,提升他们的计算能力和问题解决技巧。通过这些练习,学生可以在愉快的学习氛围中...
这份文档是一份针对一年级学生设计的数学下册第八、九单元的测试题,主要涵盖了长度单位、比较大小、连线、计算、图形认识、数据分析和解决实际问题等多个知识点。 1. **长度单位**:题目中提到了不同的长度单位,...
因此,北师大版一年级上册数学第七单元测试一.pdf不仅是一次对学生学习成果的检测,更是教育者反思教学方法、评估教学效果的重要工具。 测试的起始部分聚焦于“数的认识与书写”,这是学生与数字初次邂逅的阶段。在...
总而言之,北师大版一年级数学第三单元测试卷及答案精选,是一个高质量的数学学习资源,它不仅涵盖了一年级数学学习所需掌握的关键知识点,更是通过实际操作和应用,培养了学生的数学逻辑思维和实践能力。...
综上所述,这份北师大版二年级数学上册第四单元测试卷,是一个内容丰富、结构合理的综合评估工具。它不仅包括了基础的数学计算,还融入了图形认知、逻辑推理、问题解决等多方面的考察。通过这样全面的测试,教师和...
在人教版二年级上册的数学教学中,第三单元“角的初步认识”是关键的教学内容之一。这一单元不仅让学生对角的基本概念有了初步的认识,而且涉及到了角的分类、性质和实际应用,为学生后续学习平面几何奠定了基础。 ...
本篇内容将围绕人教版小学一年级上册数学的单元测试题,详细阐述测试题涵盖的知识点及其教育意义。 首先,数的认识与比较是本测试题的第一部分,主要目的是帮助学生认识数字,并建立数字之间的基本关系。学生将通过...
真正解决了单元测试实践的诸多核心难题,能有效帮助程序员提高代码质量和开发效率。 试用方法:下载安装本程序后即为演示版,可以测试示例代码,初步了解VU软件基本功能和使用方法;然后在VU“帮助”菜单,单击...
本单元的知识点包括小数的概念、小数的读写、小数的比较、小数的加减法以及与分数的初步联系。以下是对这些知识点的详细说明: 1. **小数的概念**:小数是一种表示数的方法,它将数值分为整数部分和小数部分。...