`
hlxiong
  • 浏览: 33247 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

单元测试之初步实践

阅读更多

 断断续续地学习了一些单元测试的知识,在最近的编码过程中有意识地进行了实践,勉强能达到一点测试的既定目的,但感觉疑惑仍然不少。

在javaeye上也拜读了诸多高人们关于单元测试、TDD方面的文章,获益良多,但是感觉很多文章起点有些高,像我这样比较笨的人读多次都不一定能领悟,适合入门一级的测试文章不太多。因此我想将自己实施单元测试的一些实践整理出来,尽量表述出我的想法,尽量提供比较详细的代码,希望初次接触单元测试的朋友能从中受益,从而少走一些弯路。另外,我在学习和实施单元测试的过程中也有很多不解和困惑,希望可以得到大家的指点。

 先列出一个测试代码实例吧。
 业务逻辑:对员工信息的增删改查;
 业务对象:EmpBO;
 业务对象接口代码(部分):

java 代码
  1. /**     
  2.  * Title:员工BO接口    
  3.  * Description:维护员工信息    
  4.  * @author xhl     
  5.  * @version 1.0     
  6.  * @date 2007-9-18     
  7.  */      
  8. public interface EmpBOI {       
  9.  /**新增一条员工信息     
  10.   * @param fmCau 员工form     
  11.   * @return ArrayList     
  12.   * 0 String true/false     
  13.   * 1 String 成功/失败信息     
  14.   * @author xhl     
  15.   * @date 2007-9-29     
  16.   */      
  17.  public ArrayList saveNewEmp(CcpAclsUserExtForm fmCau);       
  18.       
  19.  /**修改员工信息     
  20.   * @param fmCau 员工form     
  21.   * @return ArrayList     
  22.   * 0 String true/false     
  23.   * 1 String 成功/失败信息     
  24.   * @author xhl     
  25.   * @date 2007-9-29     
  26.   */      
  27.  public ArrayList saveEmp(CcpAclsUserExtForm fmCau);       
  28.         
  29.  /**删除员工信息     
  30.   * @param ccpUserGuid 员工guid     
  31.   * @return ArrayList     
  32.   * 0 String true/false     
  33.   * 1 String 成功/失败信息     
  34.   * @author xhl     
  35.   * @date 2007-9-18     
  36.   */      
  37.  public ArrayList removeEmp(String ccpUserGuid);       
  38.         
  39.  /**根据员工guid取员工form     
  40.   * @param ccpUserGuid 员工guid     
  41.   * @return ArrayList     
  42.   * CcpAclsUserExtForm     
  43.   * @author xhl     
  44.   * @date 2007-9-29     
  45.   */      
  46.  public ArrayList getEmpById(String ccpUserGuid);       
  47.         
  48. }     

 

 这段代码反映了典型的CRUD业务逻辑操作,对实现类的测试基本说明了我在实践单元测试时遇到的问题。
 对于写代码的顺序,我一般是先根据概设文档定义出业务逻辑操作类接口,测试类,业务逻辑类,然后针对每一个接口方法,先写该方法的测试用例,然后在业务逻辑类中实现该方法,运行测试,修改或重构实现代码。
 下面来看测试代码。
 首先定义了一个测试基类,扩展自Spring提供的JUnit封装类AbstractDependencyInjectionSpringContextTests。
 测试基类代码:  

 

java 代码
  1. public abstract class SpringUnitTest extends AbstractDependencyInjectionSpringContextTests{       
  2.         
  3.   /* (non-Javadoc)     
  4.    * @see org.springframework.test.AbstractSingleSpringContextTests#getConfigLocations()     
  5.    */      
  6.   protected String[] getConfigLocations() {       
  7.    // TODO Auto-generated method stub       
  8.    setAutowireMode(AUTOWIRE_BY_NAME);       
  9.    return new String[]{Constants.DEFAULT_SPRING_CONTEXT_HIB,       
  10.      Constants.DEFAULT_SPRING_CONTEXT_SER,       
  11.      Constants.DEFAULT_SPRING_CONTEXT_RES,       
  12.      Constants.DEFAULT_TEST_SPRING_CONTEXT_CCP};       
  13.   }       
  14.  }   

 SpringUnitTest类扩展AbstractDependencyInjectionSpringContextTests,实现了其抽象方法getConfigLocations,将Spring对bean的匹配方式设为AUTOWIRE_BY_NAME,根据名称而非type查找bean,然后返回Spring相关 配置文件的路径。配置文件中定义了DataSource数据源,测试代码运行时连接数据库。
 对员工管理BO的测试类EmpBOTest定义:

java 代码
  1. public class EmpBOTest extends SpringUnitTest {       
  2.   protected EmpBOI empBO;       
  3.         
  4.   public void setEmpBO(EmpBOI empBO) {       
  5.    this.empBO = empBO;       
  6.   }       
  7.  }     

 

 测试类中注入了待测试的业务类。

首先,写新增员工方法saveNewEmp的测试代码:

java 代码
  1. public void testSaveNewEmp(){       
  2.     CcpAclsUserExtForm fmCau = new CcpAclsUserExtForm);                                 
  3.     fmCau.setCauGuid("9DDC036A177088F0FAE833CEA0971DF0");       
  4.     fmCau.setName("吕南");       
  5.     fmCau.setSex("02");       
  6.     fmCau.setBirth("1989-11-24");       
  7.     fmCau.setAddress("北京市大兴区");       
  8.     fmCau.setMobile("13810384254");       
  9.     fmCau.setWorkUnit("威天软件");       
  10.     fmCau.setTel("01055669235");       
  11.     ArrayList retlist = this.ccpUserBO.saveNewEmp(fmCau);       
  12.     assertEquals("true",(String)retlist.get(0));       
  13. }    

 

然后在业务实现类EmpBO中具体实现该方法的业务逻辑。

说明一下,CcpAclsUserExtForm是对员工pojo对象的封装,员工对象的主键是cauGuid,这是一个由程序依据一定规则随机产生的32位字符串。

OK,现在业务代码有了,测试代码也已就位,数据库已运行,配置文件也在正确路径上,一切准备工作均已就绪,可以运行测试了。

Run测试代码,发现bug,修改业务代码,再次run,直到绿条出现,一个方法测试完成,一路有惊无险,很顺利。

当然上面的测试代码只测试了程序正常执行的分支,没有覆盖其它可能出现异常的分支,完全可以加入对异常分支的测试代码,不过这个业务比较简单,对正常分支测试通过基本就OK了。个人觉得,单元测试没有必要太看重测试覆盖率,够用就行了。

这样,测试类中就有了一个测试方法了:

java 代码
  1. public class EmpBOTest extends SpringUnitTest {       
  2.     protected EmpBOI empBO;       
  3.       
  4.     public void setEmpBO(EmpBOI empBO) {       
  5.         this.empBO = empBO;       
  6.     }       
  7.       
  8.     public void testSaveNewEmp(){          
  9.     CcpAclsUserExtForm fmCau = new CcpAclsUserExtForm);                                   
  10.     fmCau.setCauGuid("9DDC036A177088F0FAE833CEA0971DF0");          
  11.     fmCau.setName("吕南");          
  12.     fmCau.setSex("02");          
  13.     fmCau.setBirth("1989-11-24");          
  14.     fmCau.setAddress("北京市大兴区");          
  15.     fmCau.setMobile("13810384254");          
  16.     fmCau.setWorkUnit("威天软件");          
  17.     fmCau.setTel("01055669235");          
  18.     ArrayList retlist = this.ccpUserBO.saveNewEmp(fmCau);          
  19.     assertEquals("true",(String)retlist.get(0));          
  20.     }          
  21. }     

 

测试代码运行后,数据库中就有了员工信息记录。接下来是实现员工信息的修改逻辑。测试代码如下:

java 代码
  1. public void testSaveEmp(){       
  2.     CcpAclsUserExtForm fmCau = new CcpAclsUserExtForm();       
  3.     fmCau.setCauGuid("FD705E2FA08E95956241040BE3D83D69");       
  4.     fmCau.setName("吕南修改");       
  5.     fmCau.setSex("02");       
  6.     fmCau.setBirth("1968-10-15");       
  7.     fmCau.setAddress("北京市昌平区");       
  8.     fmCau.setMobile("13999999999");       
  9.     fmCau.setWorkUnit("join-cheer");       
  10.     fmCau.setTel("01058561199");       
  11.     ArrayList retlist = this.ccpUserBO.saveEmp(fmCau);       
  12.     assertEquals("true",(String)retlist.get(0));               
  13. }     

然后实现具体业务逻辑代码,运行测试,OK,绿条出现,测试通过。完事大吉了吗?

貌似没有问题,但是上面的测试代码实际是很脆弱。请注意这一句:

fmCau.setCauGuid("FD705E2FA08E95956241040BE3D83D69");

这是设置待修改的员工主键,问题就在这儿。主键是随机产生的,每个员工的主键值都是不同的。此处我将主键写死,该主键代表运行新增员工的测试代码testSaveNewEmp得到的员工记录。那么,我下次运行新增员工方法testSaveNewEmp时,得到的员工记录主键发生了变化,为了使修改员工的测试方法testSaveEmp正确运行,我发布修改测试方法中的代码,将待修改的员工对象主键值设为这次得到的员工记录的主键值。

如此一来,我的测试类无法运行,只能每次运行其中的一个测试方法。这种笨方法在业务逻辑开发阶段还能承受,但测试的自动化运行就没办法了。测试结果无法再现,每次运行都要修改测试代码,太恐怖了!

经过N久的痛苦之后,我决定改变测试基类。现在的基类扩展自AbstractDependencyInjectionSpringContextTests,我将其改为继承自AbstractTransactionalDataSourceSpringContextTests,这样我的测试类就自动具有事务功能,即在每个测试方法执行后Spring会自动回滚事务,将数据库还原为初始状态。然后在我的测试类里定义一个私有方法,用于向数据库中插入测试数据,每个测试方法运行时都要调用这个方法产生数据,因为测试类具有自动回滚功能,每个方法运行完成后,数据库会还原,将测试数据清空,以待下次运行时插入。这样一来,每次测试类运行时,可以保证测试数据都是相同的。这样我就可以随时运行测试而不用担心修改测试代码了。

修改后的测试基类如下:

java 代码
  1. public class SpringTransactUnitTest extends     
  2.         AbstractTransactionalDataSourceSpringContextTests {      
  3.      
  4.     /* (non-Javadoc)    
  5.      * @see org.springframework.test.AbstractSingleSpringContextTests#getConfigLocations()    
  6.      */     
  7.     protected String[] getConfigLocations() {      
  8.         // TODO Auto-generated method stub      
  9.         setAutowireMode(AUTOWIRE_BY_NAME);      
  10.         return new String[]{Constants.DEFAULT_SPRING_CONTEXT_HIB,      
  11.                 Constants.DEFAULT_SPRING_CONTEXT_SER,      
  12.                 Constants.DEFAULT_SPRING_CONTEXT_RES,      
  13.                 Constants.DEFAULT_TEST_SPRING_CONTEXT_CCP,      
  14.                 Constants.DEFAULT_TEST_SPRING_CONTEXT_APP      
  15.         };      
  16.     }      
  17. }    

 

修改后的测试类如下:

java 代码
  1. public class EmpBOTest extends SpringTransactUnitTest {          
  2.     protected EmpBOI empBO;          
  3.          
  4.     public void setEmpBO(EmpBOI empBO) {          
  5.         this.empBO = empBO;          
  6.     }       
  7.           
  8.     private ArrayList add(){      
  9.     CcpAclsUserExtForm fmCau = new CcpAclsUserExtForm);                                     
  10.     fmCau.setCauGuid("9DDC036A177088F0FAE833CEA0971DF0");             
  11.     fmCau.setName("吕南");             
  12.     fmCau.setSex("02");             
  13.     fmCau.setBirth("1989-11-24");             
  14.     fmCau.setAddress("北京市大兴区");             
  15.     fmCau.setMobile("13810384254");             
  16.     fmCau.setWorkUnit("威天软件");             
  17.     fmCau.setTel("01055669235");             
  18.     ArrayList retlist = this.ccpUserBO.saveNewEmp(fmCau);             
  19.     return retlist;   
  20.     }      
  21.          
  22.     public void testSaveNewEmp(){             
  23.     ArrayList retlist = add();      
  24.     assertEquals("true",(String)retlist.get(0));             
  25.     }             
  26.      
  27.     public void testSaveEmp(){      
  28.     add();      
  29.     CcpAclsUserExtForm fmCau = new CcpAclsUserExtForm();      
  30.     fmCau.setCauGuid("9DDC036A177088F0FAE833CEA0971DF0");      
  31.     fmCau.setName("王小二");      
  32.     ArrayList retlist = this.ccpUserBO.saveEmp(fmCau);      
  33.     assertEquals("true",(String)retlist.get(0));      
  34.     }      
  35. }   


业务类中的删除与查询方法就不在此赘述了。
一个小技艺:私有方法add用于产生测试数据,其实也可能通过AbstractTransactionalDataSourceSpringContextTests提供的jdbcTemplate执行原生SQL语句来向数据库中插入数据。这在测试业务类没有提供新增方法时非常实用。

另外,我认为,测试时最好使用一个单独的测试库,不要用项目组公用的开发库,否则会产生很多麻烦。

以上是我在具体项目中实施单元测试的一点心得体会,贴出来给大家参考,也欢迎大家提出批评意见。

分享到:
评论
2 楼 seekgirl 2008-04-01  
测试用例其实还是要跟需求结合起来,验证系统是否满足了需求
1 楼 雁行 2008-03-11  
呵呵,单元测试我也是新手
不过,看了楼主的代码,我有些疑问
这样的测试有意义吗?

上面测试代码完全和开发时候一个思维模式,简单的只是表明了正常情况下代码是正确的。
而且hibernate提供的基本增删存功能我觉得根本就不需要你去测试。
若检查参数完整,非空的情况,也不应该是在数据持久化这一层才进行,而应该是在之前就对此做出处理的。

从前有专门从事软件测试的从业人员(似乎现在也有),从很专业的角度进行软件方面测试,程序员为什么不能替代他们?有人说,因为程序编写者有一个固定的思维模式或者说设计模式,从而不容易发现软件自身的问题。
单元测试的用例好写,问题是真正写出有效有意义的测试,我觉得还是有很多要考虑的地方的。

相关推荐

    嵌入式C语言单元测试的初步研究.pdf

    本文对嵌入式C语言的单元测试进行初步研究,探讨了软件测试模式、工具选择以及用例设计方法,以期提高软件测试的效率与质量,为相关领域的技术实践提供参考。 首先,本文界定了单元测试的含义和重要性。单元测试是...

    PHP单元测试入门

    ### PHP单元测试入门 #### 一、什么是单元测试? 单元测试是一种软件开发过程中的测试方法,主要...通过以上内容的学习和实践,相信你已经对PHP单元测试有了初步的了解,并能够着手为自己的项目编写有效的单元测试。

    软件测试实用教程——方法与实践(第2版)[武剑洁][电子教案和教学指南].rar

    围绕软件测试的核心概念,介绍了软件测试的基本方法和过程,并通过丰富的案例予以...最后提供了综合应用案例实践,从自动化测试的角度,结合单元测试工具、功能测试工具和性能测试工具,讨论自动化测试的设计与实施。

    新部编版小学一年级下册语文第七单元测试卷及答案.docx

    最后,写作训练环节,通过妙笔生花的练习,要求学生根据画面进行详细描述,这不仅锻炼了学生的观察力和想象力,也是写作技巧的初步实践。学生通过这样的训练,能够逐步掌握如何使用恰当的词汇和句式来表达自己的思想...

    01_单元测试规范.docx

    你好,这里是关于“01_单元测试规范”的详细解析,主要涵盖了单元测试的基本概念、重要性、使用框架以及一些初步的规范。 单元测试是软件开发过程中的一种重要质量保证手段,它针对的是代码中的最小可测试单元,...

    iOS测试实践

    **3.2 单元测试实践** - **实践项目介绍**: 介绍具体的单元测试项目背景。 - **Model的单元测试**: 对数据模型进行单元测试。 - **Controller和View的单元测试**: 对控制器和视图组件进行单元测试。 **3.3 单元...

    第二学期北师大版一年级数学第四单元测试卷及答案精选.doc

    这份文档是针对小学一年级学生设计的数学第四单元的测试卷,涵盖了基础的几何形状认知、图形判断、计数以及简单的加减法等知识点。以下是详细的内容解析: 1. **几何形状识别**:这部分主要考察学生对基本几何形状...

    一年级数学下册位置单元测试题(一).pdf

    综上所述,这份《一年级数学下册位置单元测试题(一).pdf》是一套综合性强、富有实践性的练习,它将方位识别、数量和次序、顺序逻辑、空间位置关系、序列和定位等知识点巧妙地结合在一起,旨在全面考察和提高学生对于...

    图形的初步认识单元测试卷【华师大版】精选.doc

    华师大版教材中的图形初步单元测试卷,就是对学生这一阶段学习成效的检验。 试卷开篇的选择题部分,以基础概念为考察点,要求学生对于点、线、面、体等几何基本元素有清晰的认识。例如,试卷中指出平角是一个180度...

    人教版五年级数学下册全册单元测试题及答案.docx

    为了巩固这一部分的学习成果,本单元测试题集围绕立体图形的不同视图、性质、搭建方法和计数技巧等方面设计了一系列的问题,旨在全面检测学生对相关知识点的掌握程度,并通过实践来提高他们解决实际问题的能力。...

    年北师大版小学一年级数学上册第一单元测试题及答案.doc

    总的来说,这份测试题覆盖了一年级数学的基本内容,包括数的认识、比较、操作以及简单的逻辑推理,旨在帮助孩子们建立初步的数学概念,提升他们的计算能力和问题解决技巧。通过这些练习,学生可以在愉快的学习氛围中...

    一年级数学下册第八、九单元测试题精选.doc

    这份文档是一份针对一年级学生设计的数学下册第八、九单元的测试题,主要涵盖了长度单位、比较大小、连线、计算、图形认识、数据分析和解决实际问题等多个知识点。 1. **长度单位**:题目中提到了不同的长度单位,...

    北师大版一年级上册数学第七单元测试一.pdf

    因此,北师大版一年级上册数学第七单元测试一.pdf不仅是一次对学生学习成果的检测,更是教育者反思教学方法、评估教学效果的重要工具。 测试的起始部分聚焦于“数的认识与书写”,这是学生与数字初次邂逅的阶段。在...

    第二学期北师大版一年级数学第三单元测试卷及答案精选.doc

    总而言之,北师大版一年级数学第三单元测试卷及答案精选,是一个高质量的数学学习资源,它不仅涵盖了一年级数学学习所需掌握的关键知识点,更是通过实际操作和应用,培养了学生的数学逻辑思维和实践能力。...

    北师大版二年级数学上册第四单元测试卷及答案.doc

    综上所述,这份北师大版二年级数学上册第四单元测试卷,是一个内容丰富、结构合理的综合评估工具。它不仅包括了基础的数学计算,还融入了图形认知、逻辑推理、问题解决等多方面的考察。通过这样全面的测试,教师和...

    人教版二年级上册数学第三单元测试卷(角的初步认识).docx

    在人教版二年级上册的数学教学中,第三单元“角的初步认识”是关键的教学内容之一。这一单元不仅让学生对角的基本概念有了初步的认识,而且涉及到了角的分类、性质和实际应用,为学生后续学习平面几何奠定了基础。 ...

    人教版一年级上册数学第一二三四五六七八九单元测试题.pdf

    本篇内容将围绕人教版小学一年级上册数学的单元测试题,详细阐述测试题涵盖的知识点及其教育意义。 首先,数的认识与比较是本测试题的第一部分,主要目的是帮助学生认识数字,并建立数字之间的基本关系。学生将通过...

    Visual Unit (C/C++单元测试工具) V2.8

    真正解决了单元测试实践的诸多核心难题,能有效帮助程序员提高代码质量和开发效率。 试用方法:下载安装本程序后即为演示版,可以测试示例代码,初步了解VU软件基本功能和使用方法;然后在VU“帮助”菜单,单击...

    小数报五年级数学上册第五单元测试题精选.doc

    本单元的知识点包括小数的概念、小数的读写、小数的比较、小数的加减法以及与分数的初步联系。以下是对这些知识点的详细说明: 1. **小数的概念**:小数是一种表示数的方法,它将数值分为整数部分和小数部分。...

Global site tag (gtag.js) - Google Analytics