在我的职业生涯刚开始的时候,我曾经是一个编码人员,虽然我被叫做开发人员。我通常参与开发中小型项目和产品。在刚开始的几年,我把大部份时间都花在写代码和实现功能上面。虽然我尽了自己一切努力,但是我依然经常在项目上线或者交付给QA测试后,要经历一段十分艰苦的修BUG时期。结果,我开始和其他团队成员一起延长工作时间,然后挣扎着尝试把这些似乎永无止境的BUG修完。我们没日没夜地工作,甚至周末也需要加班,但是产出却总不尽如人意,可以说是非常糟糕。在每个版本发布后,开发团队总是会有很大的压力。
那时候我认为最大的问题在于做计划和估计,于是我把我的想法提了出来,然后在接下来的一个项目中,我得到了我估计中所需要的时间。但令我意外的是,我几乎看不到任何改进。最后,我和很多人一样,被工作占据了大量的私人时间。
在这里,我想说明的不是计划和估算对一个项目的成败并不重要。但是在一个项目中,就算有充足的计划和估算,如果只有编码人员而没有开发人员的话,仍然是无法成功的。
正向测试
在最开始的时候,我一般会在完成编码后进行一些正向测试,就是说我只会测试一些符合功能要求的某些场景。我通常会在输入框中填入一些有合理的值,然后看看系统是否能够返回正确的结果。当我回想当时的情形的时候,觉得这样其实挺好笑的。
那时候,我无法理解为什么有人会输入一些无效的数据,或者进行一些系统根本不支持的操作。结果,我花了更多的时间在培训用户和编写详细的培训资料上面。
但是,我很快就意识到了这样的做法有问题,因为我发现有很多因素可能破坏在说明书上的规则。比如说,使用客户端的用户有可能会改变;不一定每个用户都会跟着说明书一步步操作;用户实际的操作流程经常和说明书上推荐的步骤不一致,因为用户通常会根据自己更熟悉的老系统的操作流程进行操作;最后,人类总是会犯错的。
探索性测试
后来我开始做一些探索性测试作为正向测试的简单补充。如果我觉得某个功能比较复杂的话,我会尝试围绕着这个功能进行一些反向或者额外的测试。这样做就比我之前只做正向测试进步了一点,但是我依然在模块和组件集成,准备交付给QA测试或者项目上线的时候非常痛苦。
猴子测试
再后来我开始将我的测试覆盖面增大。我开始尝试在各个页面中随意跳转,然后随意输入一些无效的或者格式不对的数据,通过这样我找出了更多的BUG。基本上,我就是漫无目的的随意测试整个系统,看看会不会导致系统异常。实际上,这种测试就是凭着感觉对整个系统进行一个随意的测试。
后来我才知道这叫做“猴子测试”。尽管我可能做得不怎么样,毕竟还是比以前有所进步。
伪单元/集成测试
为了遵守公司的规范和最佳实践,我准备了一些单元测试和集成测试的文档。在这些文档中,我定义了测试用例和用来检测这些用例是否成功的条件。这是一种很好的实践,因为它确保了一个功能已经被开发人员很好地测试过了。
下面是一些我在这方面的经验:
1. 尽管在做计划的时候给编写单元测试和集成测试文档分配了充足的时间,但是很多编码人员仍然没有对其给予足够的重视。
2. 通常,编码人员会在完成代码后才准备这些文档。
3. 编码人员通常会把大多数分配给单元测试的时间用于编码。
4. 直到在产品发布之前,编码人员才会开始编写单元测试文档,然后把所有测试用例的结果的统统写上“通过”,然而他们根本没有执行过这些用例。
5. 编码人员编写的测试用例通常没有覆盖到全部场景。
6. 编码人员有时候会根据情况跳过正向测试、探索性测试、猴子测试。
从编码人员转变成开发人员
一直以来,我都想方设法改进产出和移除障碍。结果,我发现问题在于我使用的方法不对。因为我太过集中在编码上面,而只花很少时间在测试上,要改变现状,必须先找到这两者之间的平衡,这个要从改变自己开始。无论我的代码有多么的出色,如果没有办法处理所有可能的场景,整个程序就等于没用。
于是我开始更重视测试,而且把测试当作成开发的一个重要环节。这样使我开始从一个编码人员转变为一个开发人员。为此,我尝试了各种方法来改进我的实践。非常幸运地,一位刚刚加入公司的同事推荐我学习TDD—测试驱动开发。这对我来说是个全新的概念,我搜集了这方面的资料然后和我的团队分享。
我的TDD第一步
我被同事说服了使用TDD,但是我并不知道如何开始。而且更不幸的是,由于缺乏时间和培训,我没有办法使用xUnit家族。尽管如此,我仍然热衷于按自己的方法进行TDD,于是我和团队进行讨论,然后制定了一些规则:
1. 在对任何功能开始编码之前,必须先编写单元测试文档。
2. 为了能够保证测试用例在编码完成,必须在文档中使用track changes功能。
3. 在实现功能之前,把所有对应的测试用例的结果设定为“失败”。
4. 写足够的代码来实现功能。
5. 执行之前编写的单元测试,根据结果更新测试用例文档。
尽管我极力要求整个团队都要遵守上述规则,但是正如我预料中一样,我发现每个人都对这些规则有很大意见。一个被提及的问题就是:连功能都没有实现,我怎么写测试用例呢?在刚开始的时候连我自己都有这样的疑问,但是我很快发现这种疑问是毫无根据的,因为测试人员也是只根据需求文档就完成了测试用例的编写。最后,所有人都同意遵守上面的规则,然后在几个版本之后重新评估它们,以找出哪些是真正合理和实用的。
在几个版本之后,我发现了下面这些现象:
1. 开发人员开始对功能有更好的认识,而且能够更好的构思产品的行为。由于他们必须在开发代码之前先编写测试用例,因此他们更能够从用户的角度来思考问题。
2. 开发人员能够想到更多可能的场景,包括正向和反向,然后再编写相应的代码。
3. 由于开发人员已经把功能测试过了,他们也就对自己交付的代码更加有信心了。
4. 在一两个版本之后,整个团队能够发现自己不足,然后在下一个版本加以改善。例如,在一个用例中没有考虑到的因素,最后发现是由于团队对业务的不熟悉所造成的。
到这里为止,剩下最大的麻烦就是回归测试和每次有新的改动就要把之前所有的用例都执行一次。毫无疑问这是不可能通过手工来完成的,因为其需要大量的时间。尽管如此,TDD还是帮助我们在某个程度上使我们的版本更加稳定了。
使用nUnit,向自动化单元测试迈进
直到这之前,我们还一直在项目使用传统的开发方法。后来,我有机会参与到一个使用敏捷开发方法的项目中。在这个项目中,我学会了使用nUnit来自动化单元测试。当然,刚开始使用nUnit的时候我遇到了一些困难,但是我已经跨过了最大的障碍:把我的思维方式从一个编码人员转变成一个开发人员。另外,我们觉定只为新的功能编写自动化的单元测试,因为要给老代码补上自动化测试的话需要花费大量的时间。
于是我们只给新功能和需要改变的代码编写nUnit测试,然后渐渐地自动化的用例开始越来越多。编写自动化单元测试的一项好处是,这更像是在做开发而不是在做测试,然后最终会令测试和代码回顾更加容易和快速。然而,在一些UI测试和自动化测试比较难实施的情况下,我发现我在上文提到的5个步骤会更加合适和有效。
要点
我的开发生涯一直持续到今天。我发现做开发有一件很吸引的事就是,它既需要编码也需要测试,这正是TDD所强调的。在所有的项目中都需要把编码人员转变成开发人员。
作者:Vinay Krishna
原文地址:http://www.scrumalliance.org/articles/357-my-experiments-with-tdd