论坛首页 综合技术论坛

JUnit测试的粒度问题

浏览 13045 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2007-03-13  
对于JUnit测试和TDD实践中有如下的疑问,请各位解惑:
JUnit测试的粒度如何把握?
简单的说是针对public的方法写测试就OK了呢?还是说要具体针对public方法中执行逻辑的每个步骤来写测试方法?
先说一下为什么会有这种困惑:
业务逻辑比较简单时,当然只针对Public方法的业务流程来设计案例,并只对public方法写test方法就好。
但最近做一个保险的项目,计算超复杂的那种,用户点一个Button后台要操作十几张表,数据Copy来Copy去
中间还有各种各样的计算,设计的业务Interface方法中接受User的输入,然后执行整个操作。
现在谈一下两种实现的方式:
1.按TDD的方式,先写测试代码,再写实现代码,实现过程不断重构(未完整了解过TDD,只是皮毛,如有误解见谅)
这种方式实现起来很有难度。首先测试代码的覆盖度很难保证:当复杂的业务逻辑揉在一个方法中(即使重构拆成若干小方法),流程分支成幂增长,很难一开始就把所有的情形都考虑清楚,即使都考虑到了,写出来的TestCase也可能是超复杂的,反而会成为一种负担。
另外,这样来做实际上也就相当于大块大块的Coding,然后测试,偏离了TDD的本意,Coding过程中没办法保证做的每一步都是正确的,而是将这个测试推迟到完成了整个实现之后。
2.对整个业务逻辑的实现大致上先分为几个步骤,每个步骤的实现可以放在protected方法中以便测试,然后再针对每一步来实践TDD,这样没有上述的两个问题,而且最终程序员对自己代码的信心会大增。但这样来做也有一些问题。
首先,每一步骤的方法都是protected才能保证测试,这样破坏了封装
其次,测试代码是针对接口实现的过程来写的,而不是针对接口的功能,所以测试代码可能会很脆弱,实现过程稍作变化测试代码也可能要做修改

所以,最根本的问题也就是单元测试是应该针对接口实现的过程还是接口的功能?
   发表时间:2007-03-13  
问题不是出在测试上,而是出在设计上。
你们的设计中一定有很多这样的情况:模块A把数据放到一堆数据表里去,然后模块B从这一堆数据表里面取数据做工作。于是你必须测试A实现这个功能的过程。
好一点的设计是,假如A负责把数据放进去,A也要负责把数据拿出来,总之这些数据的存储和解释完全应该由A来负责。这样就不必测试A的过程了,只需要测试A的接口。即使A的过程根本不对,数据完全存错了,反正他看起来还是一个A,内部错翻天了也影响不到别的模块。
单元测试并不是可以无条件实行的,良好的设计,清晰的层次是必须的。对一团乱麻搞单元测试,人基本要累死。
0 请登录后投票
   发表时间:2007-03-13  
lane_cn 写道
假如A负责把数据放进去,A也要负责把数据拿出来,总之这些数据的存储和解释完全应该由A来负责。这样就不必测试A的过程了,只需要测试A的接口。即使A的过程根本不对,数据完全存错了,反正他看起来还是一个A,内部错翻天了也影响不到别的模块。

这也许是涉及Requirement的问题了,假设就有这样复杂的Req,比如不同粒度的数据转换
0 请登录后投票
   发表时间:2007-03-13  
run_xiao 写道
lane_cn 写道
假如A负责把数据放进去,A也要负责把数据拿出来,总之这些数据的存储和解释完全应该由A来负责。这样就不必测试A的过程了,只需要测试A的接口。即使A的过程根本不对,数据完全存错了,反正他看起来还是一个A,内部错翻天了也影响不到别的模块。

这也许是涉及Requirement的问题了,假设就有这样复杂的Req,比如不同粒度的数据转换

这个和req有什么关系,完全是设计问题。
0 请登录后投票
   发表时间:2007-03-13  
测试永远是针对功能进行测试.
0 请登录后投票
   发表时间:2007-03-16  
simohayha 写道
测试永远是针对功能进行测试.


没错,绝对不要为了测试而测试。如果一个测试几乎是用来摆看的,那就把它去掉。
0 请登录后投票
   发表时间:2007-03-16  
引用
如果一个测试几乎是用来摆看的,那就把它去掉。

针对接口的功能来测试的话,是黑盒测试
对接口的实现过程来的测试话,是白盒测试
两种都是有其意义的,我想问的是两个方式对Coding来说更方便
楼上说的用来摆设的测试,能否说清楚一点?
0 请登录后投票
   发表时间:2007-03-16  
run_xiao 写道
引用
如果一个测试几乎是用来摆看的,那就把它去掉。

针对接口的功能来测试的话,是白盒测试
对接口的实现过程来的测试话,是黑盒测试
两种都是有其意义的,我想问的是两个方式对Coding来说更方便
楼上说的用来摆设的测试,能否说清楚一点?


单元测试重在细粒度,它不是用来测试一个包,也不是用来测试一个类,而是用来测试一个一个的方法。如果一个测试把整个包十几个类都走了一遍,出了问题也很难找到在那。所以说几乎是用来摆看的。单元测试就是白盒测试,把所有的单元测试运行一道,我就知道系统的每一小步每一小步是不是都走得稳。如果一个方法仅仅是用来转换日期格式,它的单元测试又能有多复杂呢?
0 请登录后投票
   发表时间:2007-03-19  
引用
单元测试重在细粒度,它不是用来测试一个包,也不是用来测试一个类,而是用来测试一个一个的方法。如果一个测试把整个包十几个类都走了一遍,出了问题也很难找到在那。所以说几乎是用来摆看的。

Of course,JUNIT是用来测试每个方法,但也只能测试public的方法,顶多也还可以算上protected。
没这点共识就没法做UT了。
引用

单元测试就是白盒测试,把所有的单元测试运行一道,我就知道系统的每一小步每一小步是不是都走得稳。如果一个方法仅仅是用来转换日期格式,它的单元测试又能有多复杂呢?

如果我一个public方法要实现的业务逻辑巨复杂,你说的方式就是我提到的第二种方法,这实际上并非白盒测试,因为已经深入到该方法实现的过程中了,是对每一步的测试
0 请登录后投票
   发表时间:2007-03-19  
你对单元测试粒度的问题,说到底其实不是测试的问题,而是一个设计的问题。在一个内聚性良好的系统里你的问题自然是不会成立的。

“假如A负责把数据放进去,A也要负责把数据拿出来,总之这些数据的存储和解释完全应该由A来负责。这样就不必测试A的过程了,只需要测试A的接口。即使A的过程根本不对,数据完全存错了,反正他看起来还是一个A,内部错翻天了也影响不到别的模块。”

我把这个情况更加具体的说明一下。设想有一个系统,他有两个功能:
1、操作员在前台界面上输入客户的资料;
2、经理在后台界面上查询客户的资料。

按照这个功能点,一个程序员设计出了这样的代码,他有两个对象:
1、对象A负责接受操作员的输入,把资料拼成一个SQL,在数据库上执行,把数据保存到数据库表里去;
2、对象B负责接受经理的输入,把查询资料拼成一个SQL,在数据库上执行,把用户资料查出来显示。

对于这样的代码,要进行单元测试的话,肯定要深入A和B的细节,最终要测试每一种输入输出在数据库里面的数据是不是正确,或者是产生的SQL是不是正确。
这样就会产生楼主所问的问题。

如果换一种内聚性比较好设计,应该有一个Customer对象,由他负责客户资料的存储和读取,并确负责客户能够操作的所有行为。这样就不必测试Customer的执行过程了,只需要关心他的接口。

可以有这样的测试用例:把一个姓名字符串设置到Customer对象上,然后得到这个对象的姓名,他应该是刚才设置的字符串。这就测试了Customer对象的Name属性。至于这个Name是不是真的存储到了数据表的某个字段里,这个不需要测试,即使需要测试也应该通过接口的表现来测试(比如我们可以用同样的ID再得到这个Customer对象,观察他的Name属性),而不应该去深入他的实现细节,看他的SQL拼写的对不对。

即使不测实现过程也没有关系,Customer即使没有正确的保存到数据库里,只要接口是对的,他用起来仍然是一个正确的Customer,他的内部怎么实现是他自己的事情。
0 请登录后投票
论坛首页 综合技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics