谈谈测试
说起测试,相信每个软件团队都同意其非常重要,但在实际的软件研发过程中,我们却发现真正理解测试、做好测试的团队却不多。深究其原因,不是不想做,而是对于测试的认知出现了偏差,导致实际执行往往花了很大的精力,却吃力不讨好。测试成了大家口中的“我知道很重要,但是我做不到”的又一个心头之痛。
一件事情做不成的原因,大体可以分为认知不到位和执行不到位。在这里想结合自己多年的经验做一个分享,希望能给大家的认知带来一点提升。
一、测试的目的
测试的目的是为了保障软件交付的质量。对于质量的度量又有以下几个维度:
功能符合需求。对应功能测试。
性能符合需求。对应性能测试。
安全达标。对应安全测试。
功能测试通过模拟各种各样的用户使用场景(Use Case),验证软件功能实现正确性。性能测试模拟大量用户同时使用软件的场景,通过增大并发访问量,验证软件功能、性能符合要求。安全测试通过模拟恶意访问请求,验证软件的安全性是否符合要求。
二、单元测试
单元测试,顾名思义,是对一个代码单元的测试。在 Java 之类的面向对象的语言里,最小的测试单元就是方法。一个软件包含许多的功能,单元测试背后的逻辑是通过确保每一个局部功能的正确,来减少全局错误的发生概率。
并不是所有的方法都需要单元测试。判断是否需要编写单元测试的依据是一个方法是否包含自包含业务逻辑的实现。例如一个修改用户密码的方法,输入老密码和新密码,输出修改后的密码,这个业务逻辑是自包含的,应该编写单元测试。而一个纯粹的访问数据库的方法,就不需要编写单元测试,因为不包含具体的业务逻辑。我们不需要验证一个 DAO 框架(例如 mybatis)或者一个数据库(例如 mysql)的正确性,他们一般被认为值得信赖的。
单元测试应该是非常轻量的。它可以在本地开发环境直接执行,不需要依赖于任何的外部模块或者设施。如果一个方法有依赖的对象参数、或者内部实现依赖其它方法的调用结果,那么通过 Mock 技术来模拟这个方法需要的输入参数和依赖方法的返回结果。
单元测试应该是无状态的、自包含的。每一个单元测试都可以独立重复的执行,不需要依赖其它的测试先执行。
开发人员在本地开发时,应该频繁的运行单元测试。在提交代码时出发自动构建也应该运行单元测试。注意一个单元测试不通过不意味着错误,有可能是需求改动了,单元测试没有做相应的更改。但起码有了单元测试我们能第一时间发现冲突。
三、集成测试
单元测试都通过了并不能确保功能组合之后是正确的,为了进一步保障软件质量,还需要进行集成测试。
集成测试就是将每一个功能单元组合在一起进行的测试。集成测试的驱动是业务流程。例如:一个用户修改了他的密码,那么他应该被强制注销当前登录会话。为了验证这个业务场景,我们应该编写一个集成测试,把修改密码和用户注销两个单元连接在一起来测试,验证修改密码后用户的会话确实被注销了。
功能的单元分很多层次:方法 --> 模块 --> 子系统 --> 系统 --> 外部系统,因此我们的集成测试也有不同的层次。有的集成测试是在系统内部的,有的还要和外部系统联合进行。有的集成测试可以直接在内存中运行,有的需要搭建复杂的设施,提前做好各种准备才可以运行。
但无论多复杂,原则上一个集成测试也是无状态的,可以重复运行,无需其它前置的集成测试,也不会对其它集成测试产生副作用。因此一个集成测试往往需要有一个准备阶段,一个清理阶段。
尽可能的使用内存实现可以简化集成测试的过程。但有些情况无法避免,例如我们要测试一个系统对于 Oracle 和 Mysql 的兼容性,就不得不搭建外部的 Oracle 和 Mysql 数据库。
集成测试可以由开发人员编写,也可以由测试人员编写。
四、手工测试 V.S.自动化测试
在国内大部分的软件研发团队都依赖于专职的测试人员进行手工测试。测试人员根据对需求的理解,编写测试用例从各个角度模拟真实用户的操作,测试软件是否正确的实现了需求。
自动化测试的出发点是效率。随着系统越来越复杂,每一个功能的变更都可能对已有的功能造成影响,完全依靠人力去回归已有的功能是不现实的,因此我们需要编写自动化测试来帮助我们提升回归测试的效率。单元测试、集成测试都是一种自动化测试。
有一些追求极致工程化的人喜欢提消灭手工测试,一切都自动化。这对于一些底层的软件是可以做到的,例如一个驱动程序,但对于面向人直接使用的软件是不现实的,例如一个有复杂人机交互的 web 应用。一个优秀的测试人员能凭直觉去找出可能的 bug,而自动化测试只能机械的执行一个预先设定好的场景。
五、研发团队 V.S. 测试团队
先提一个问题:可以不需要专职的测试人员吗?
在国外的大型互联网公司,例如谷歌是没有专职的测试人员的。开发人员负责编码、测试、上线发布和运维。而在国内,几乎所有公司都设置有专门的测试团队。到底哪种模式更好?
其实测试只是软件生命周期里的一个步骤,具体由谁做,并没有强制规定。如果开发人员有足够的工程化意识和能力,例如谷歌的工程师都是经过严格面试百里挑一的精英,那么测试就可以由开发人员兼做。而其它大部分公司的开发人员不具备这种能力,只能把测试的工作分出去,通过专业分工来实现。把测试与开发的工作分开还有一个考量,就是开发人员往往对自己的代码很自信,认为不会有问题,潜意识里容易忽视可能的错误。
另外把开发和测试分开,也是通过 A/B 岗交叉验证一个需求是否被正确实现的管理手段。这个 A/B 岗是一种合作的关系,是共同对需求负责。但在国内的一些公司,往往把这种 AB 岗的划分变成对立的。测试团队如果泄露了 bug 到生产环境,就要扣绩效,因此测试团队反过来对研发施压,要求研发自测达到一个标准后才能提测。但如果研发花了很多时间做测试,还需要专门的测试团队吗?当然如果开发完全不自测也不行,测试也不是为开发找 bug 的。
因此如何平衡好两个团队的工作,是一个有点复杂的管理问题。一个推荐的做法是:开发站在实现的角度,编写单元测试、主线业务场景的集成测试,并确保通过,这样软件达到可测的要求。测试团队则站在用户使用的角度,做更细致的业务场景测试。开发团队和测试团队可以共享主要的业务场景,并对可测试的标准达成一致。
六、什么时候编写测试
如果你去问一个开发人员:为什么不写单元测试?回答基本上都是项目太紧,没有时间写测试,或者需求变化太快,刚写完测试后需求又变化了,编写测试代码是浪费时间。
对于持上述答案的开发人员,讽刺的是他们往往有时间不断的手工测试自己写的接口,有时间加班修改线上的 bug。其实,当测试成为一种下意识,编写测试代码在并不会花费多少额外的时间。执行不下去,本质的原因是源于对测试的认知不到位,以及对于如何构建模块化的软件所需的技能认知不到位,不知道如何下手。良好的代码一定是可测试的。反过来,大部分人都没有掌握编写好代码的技能,因此难以编写测试代码。
以下是对什么时候编写测试代码的建议:
开发人员应该为每一个必要的方法编写单元测试。如果遵循 TDD,那么应该先编写测试代码,再实现具体的业务逻辑。这里说的必要的方法,就是包含一定业务处理逻辑的方法。对于一些直接访问数据库的方法,请求接收方法(例如 Spring MVC 的 Controller)则不必进行单元测试。
如果不遵循 TDD,开发人员也应在需求实现比较稳定后,编写集成测试代码。
测试人员为功能已经稳定的版本编写自动化测试用例。一般把手工执行的测试用例自动化。
七、总结
要做成一件事,首先要认知到位,然后才能执行到位。希望上述我对测试的理解,能为大家打开思路,树立落地测试的信心。
版权声明: 本文为 InfoQ 作者【得大自在】的原创文章。
原文链接:【http://xie.infoq.cn/article/27d23f31ba2cbbf0992fed695】。文章转载请联系作者。
评论