锁定老帖子 主题:吹弹得破是重回一人犯错,全家光荣的老路
该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2006-04-23
winterwolf 写道 框架本身就不支持松藕合 系统本身就没有按模块来设计 在测试上如何能划分清楚 ?
我觉得这个不用列出一堆代码来解释. 闪那么快干嘛 有错误可以说. 你和gigx的意思无非说人家的单元测试水平有问题 我感觉单元测试水平再高也没用 根本就不是这个问题. 不是水平,而是过程。如果你一开始就TDD,你就不会写出“不松耦合”、“不模块设计”的东西,因为你一开始就没法给这些东西写测试。 |
|
返回顶楼 | |
发表时间:2006-04-23
gigix 写道 charon 写道 我有一个疑问,难道我为了测试一个业务方法是不是正确实现,还要给它提供一堆dao,还要关注这些dao是不是被以合适的顺序合适的方式调用?
TDD是关于设计,而不是关于测试。业务逻辑需要取出x和y的数据,然后用z的方式组合这些数据,这是你的设计,所以你应该用测试来描述它。 举个例子,比如要获取每个客户类别的总人数,此时,作为测试,我只需要测试是不是准确的得到了这个数据,而并不需要知道提供这个逻辑的业务方法是不是用sql的方式,一条或者多条sql语句来搞定。 在编写测试用例时,并没有理由去限制它的实现方式。 从安全的角度来说,对内部实现方式的限制并没有比只检测外部输入输出的方式有更多的安全性,而从重构的角度来说,反而有更多的麻烦. 因为我的测试代码和实现代码耦合太紧密了,任何有点实际意义的重构都必须先调整测试代码. 从重用的角度来看,这两类测试的稳定性也是不一样的.针对接口方法的测试当接口契约稳定的时候是稳定的,而针对内部实现方式的测试,是相当不稳定的.把这些东西放在一起,显然是一股臭味. 从被测试对象的使用方式示例的角度来看,针对内部实现方式的测试,也是啰嗦的. 实际上,TDD中也不应当去规范内部实现. 借用另一个术语, 应当规范的是对象的"可观测"行为. 在这里就是暴露在接口上的那些方法. 能够做某件事情并不是说是一定要那么去做.实际上用测试来固定内部设计/逻辑的方式是double check了,但因为设计和实现思路的一致性,其效果仍然不如异种算法验证.而且,此时测试已经比所谓的安全网要更进一步,而混入了钢筋水泥之中了. |
|
返回顶楼 | |
发表时间:2006-04-23
charon 写道 gigix 写道 charon 写道 我有一个疑问,难道我为了测试一个业务方法是不是正确实现,还要给它提供一堆dao,还要关注这些dao是不是被以合适的顺序合适的方式调用?
TDD是关于设计,而不是关于测试。业务逻辑需要取出x和y的数据,然后用z的方式组合这些数据,这是你的设计,所以你应该用测试来描述它。 举个例子,比如要获取每个客户类别的总人数,此时,作为测试,我只需要测试是不是准确的得到了这个数据,而并不需要知道提供这个逻辑的业务方法是不是用sql的方式,一条或者多条sql语句来搞定。 在编写测试用例时,并没有理由去限制它的实现方式。 从安全的角度来说,对内部实现方式的限制并没有比只检测外部输入输出的方式有更多的安全性,而从重构的角度来说,反而有更多的麻烦. 因为我的测试代码和实现代码耦合太紧密了,任何有点实际意义的重构都必须先调整测试代码. 从重用的角度来看,这两类测试的稳定性也是不一样的.针对接口方法的测试当接口契约稳定的时候是稳定的,而针对内部实现方式的测试,是相当不稳定的.把这些东西放在一起,显然是一股臭味. 从被测试对象的使用方式示例的角度来看,针对内部实现方式的测试,也是啰嗦的. 实际上,TDD中也不应当去规范内部实现. 借用另一个术语, 应当规范的是对象的"可观测"行为. 在这里就是暴露在接口上的那些方法. 能够做某件事情并不是说是一定要那么去做.实际上用测试来固定内部设计/逻辑的方式是double check了,但因为设计和实现思路的一致性,其效果仍然不如异种算法验证.而且,此时测试已经比所谓的安全网要更进一步,而混入了钢筋水泥之中了. mockUserDao.findByCategory(category);; 这不是很顺利成章的么?你测试调用了dao的这个方法,传入了正确的参数。至于dao用不用sql、用什么sql去实现,不在这个测试里面。 |
|
返回顶楼 | |
发表时间:2006-04-23
简单点说,现在要测试bookController的list()方法
如果是黑盒测试,你只需关心list()方法的输入输出。 但如果要mock Controller里面使用的BookService, 对list()方法的测试就成了白盒测试。 因为ut里需要录制list()所调用service对象的输入与输出,暴露了list内部的实现。list()方法内部实现代码的修改,会导致ut代码的修改。 白盒测试当然好,当然酷,在资源足够的前提下。 |
|
返回顶楼 | |
发表时间:2006-04-23
gigix 写道 mockUserDao.findByCategory(category);; 这不是很顺利成章的么?你测试调用了dao的这个方法,传入了正确的参数。至于dao用不用sql、用什么sql去实现,不在这个测试里面。 针对要获取每个客户类别的总人数(我前面没有表述清楚,让gigix以为是获取某个类别的总人数了) 这里有两个方式: 一个方法是mockUserDao.findCustomerCountOfCategories(); 这个是在有一个dao方法可以搞定的情形下 另一个方法是遍历所有的category并逐个调用categorymockUserDao.findCustomerCountByCategory(category),把每次调用的结果汇总,得到每类别的总人数,此时,隐含对应的是多条sql语句(类似情况在非关系数据库如sleepycat作为后台存储时更可能出现, 其实对于hibernate和ibatis做持久端时,也存在类似的粒度差异). 这个时候,如果首先利用模板模式做一个针对接口的测试用例,然后根据具体的dao实现做特定于持久层的数据初始化和扫尾处理,此时,当需要切换接口的实现时,测试的主体部分可以重用. 从TDD的角度来说,接口不是事先定义的,而是浮现出来的,也就是说,只有在出现两个类实现同样的一簇功能时,才会抽象出一个接口来. 这个时候,针对这两个类的这个接口的测试,有一部分,或者是黑盒部分是可以重用也必须重用的. 当然,针对那种接口至上的极端方法(也就是有类必有接口的方式),模板方式的测试用例只是自寻烦恼,因为很多时候根本就不会去做一个接口的第二个实现. |
|
返回顶楼 | |
发表时间:2006-04-24
charon 写道 gigix 写道 mockUserDao.findByCategory(category);; 这不是很顺利成章的么?你测试调用了dao的这个方法,传入了正确的参数。至于dao用不用sql、用什么sql去实现,不在这个测试里面。 针对要获取每个客户类别的总人数(我前面没有表述清楚,让gigix以为是获取某个类别的总人数了) 这里有两个方式: 一个方法是mockUserDao.findCustomerCountOfCategories(); 这个是在有一个dao方法可以搞定的情形下 另一个方法是遍历所有的category并逐个调用categorymockUserDao.findCustomerCountByCategory(category),把每次调用的结果汇总,得到每类别的总人数,此时,隐含对应的是多条sql语句(类似情况在非关系数据库如sleepycat作为后台存储时更可能出现, 其实对于hibernate和ibatis做持久端时,也存在类似的粒度差异). 这个时候,如果首先利用模板模式做一个针对接口的测试用例,然后根据具体的dao实现做特定于持久层的数据初始化和扫尾处理,此时,当需要切换接口的实现时,测试的主体部分可以重用. 从TDD的角度来说,接口不是事先定义的,而是浮现出来的,也就是说,只有在出现两个类实现同样的一簇功能时,才会抽象出一个接口来. 这个时候,针对这两个类的这个接口的测试,有一部分,或者是黑盒部分是可以重用也必须重用的. 当然,针对那种接口至上的极端方法(也就是有类必有接口的方式),模板方式的测试用例只是自寻烦恼,因为很多时候根本就不会去做一个接口的第二个实现. 这个逻辑是DAO的还是外围业务组件的? 从你的描述来看是DAO,那么就跟数据库集成测DAO吧。测试的目标是验证返回结果的正确性以及合理的SQL性能优化。 如果把它当作一个业务逻辑,分步调用各个DAO接口,然后对返回的结果进行特定业务逻辑的处理,ok,这是业务组件的逻辑 ,那么mock各个被依赖的DAO,然后进行充分的单元测试吧。测试的目标仅仅是业务处理的逻辑。 |
|
返回顶楼 | |
发表时间:2006-04-24
firebody 写道 如果把它当作一个业务逻辑,分步调用各个DAO接口,然后对返回的结果进行特定业务逻辑的处理,ok,这是业务组件的逻辑 ,那么mock各个被依赖的DAO,然后进行充分的单元测试吧。测试的目标仅仅是业务处理的逻辑。 可是,这样就测不到service层的事务处理是否正确了。 毕竟数据是否处于预期的状态才是判断业务逻辑正确与否的唯一标准! |
|
返回顶楼 | |
发表时间:2006-04-24
daquan198163 写道 firebody 写道 如果把它当作一个业务逻辑,分步调用各个DAO接口,然后对返回的结果进行特定业务逻辑的处理,ok,这是业务组件的逻辑 ,那么mock各个被依赖的DAO,然后进行充分的单元测试吧。测试的目标仅仅是业务处理的逻辑。 可是,这样就测不到service层的事务处理是否正确了。 毕竟数据是否处于预期的状态才是判断业务逻辑正确与否的唯一标准! 如果你使用异常来回滚事务的话,那么就测试是否抛出给定的异常吧,异常抛出==事务回滚,这是单元测试的一个绿色标志。 从那个业务逻辑类来看,它只复杂这些逻辑,测试这些逻辑就是针对它的单元测试。 至于数据回滚和正常恢复与否,那不是这个业务类的逻辑,而是事务管理的逻辑。 单元测试需要知道该测试什么,什么测试了就表示这个单元测试完善了呢? 是一个需要理解和实践的问题。 |
|
返回顶楼 | |
发表时间:2006-04-24
更进一步的,我想测DAO在事务中是否仍能正确工作
mock测试能确定service组件的事务逻辑正确、service组件对DAO的调用也正确,可是: “service组件S的事务逻辑正确”+“DAO组件D在Testcase的事务环境中工作正确”+“S对D的调用正确” != “D在S的事务中工作正确” 解决这种问题,我看不出有比一个JUnit的集成测试更简单的方法。 |
|
返回顶楼 | |
发表时间:2006-04-24
难道用mock对一个Service组件录制一大堆东西,只为了验证它调用了某某DAO、它能抛出某某异常?
这是测试该关心的事吗? 发生这种错误的机会有多大? 单元测试一定要这么“白盒”才能获得安全感? 用mock测试符合TDD的直觉吗?(我在为一个组件写测试时,想得更多的是它从外面看起来是什么样子,而不会去想也还想不清楚它里面会有哪些机关) |
|
返回顶楼 | |