测试驱动开发(TDD)是一种开发方式,它改变了传统软件开发的流程,即首先设计程序,再进行编码与测试工作。TDD 采取了很小的增量式开发方式:首先编写一个测试,再编写实际程序代码以通过测试,最后对代码进行改进。这种方式的结果是大量的(通常可达到几千个)自动化测试,能够在几秒钟之内执行完毕。
测试人员需要注意到一点,这些高效的自动化单元测试剔除了大多数手工测试的执行。这样一来,我们就需要重新反思是否有必要在 TDD 团队中继续保留测试人员的角色。
从表面上看,无论是否采用 TDD,“测试人员”都是团队中必不可少的角色,但实际情况要复杂得多,现在让我们来看看这些复杂性体现在何处:
- 如果你打算开始尝试 TDD,那么建议你不要试图在团队中揉合老派的 QA 与功能测试人员。
- 如果你已经成功地实施了 TDD,那么在团队中安排一位专攻测试的成员仍然是有意义的。
- 在 TDD 中团队中能够取得成功的测试人员与传统的功能测试人员的区别在于,前者具有更扎实的技术背景。
QA 的兴衰
在对“ TDD 已死?”这一主题所展开的一次对话中,Kent Beck(KB)、Martin Fowler(MF)与 David Heinemeier Hansson(DHH)围绕着 QA 与测试展开了激烈的讨论。他们指出了专职测试人员历史的 3 个发展阶段:
- 堆积 QA:通常指机能失调的 QA 部门,其中充斥着大量的功能测试人员。
- 摒弃 QA:对于让程序员负责测试的做法过于自信,在开发过程中摒弃测试人员。
- 当前现状:在项目中引入适当的 QA(甚至是功能性的)仍是有必要的。
流行于上世纪 90 年代的堆积 QA 的做法现在看来似乎已经过时了,许多 IT 组织已经解散了他们的 QA 部门,并将测试人员分派到各个敏捷团队中。不过,在许多敏捷团队中,这些测试人员仍在继续着早期的手工测试任务。众多组织仍然受困于延续自 20 年前的机能失调的测试方法。
老派的 QA 方式之所以出现机能失调的情况,是因为这种方式依赖于大量的功能测试人员。这些测试人员是手工测试方面的专家,但对于技术方面的技能知之甚少。测试人员的专业性决定了他们擅长于对功能的“测试”。但是,老派的 QA 部门更倾向于(同时也出于商业利益的考虑)让这些测试人员对功能进行“检查”。
“检查”的主要特点在于:这种测试完全可以实现 _ 自动化 _(Bach 与 Bolton 2013)。这就意味着“检查”功能可以由程序员完成。至于是应该让测试人员还是程序员进行功能“检查”,这种选择貌似随意,其实不然:无论是发现 bug、进行隔离、汇报、跟踪或是提出修复意见,测试人员都要花费更多的时间 (Kaner 2001)。
通过手工测试人员对功能进行“检查”的方式让老派的 QA 变得非常低效。一旦团队培养出“不要测试自己的代码,把它丢给 QA 去做”这种观念,测试工作就变得机能失调了 (KB 与 DHH 在这次对话中的观点)。这种方式发展到一定程度,就会造成效率的不断下降,随着投入的测试人员越多,反而会造成 bug 数量的不断升高。(‘Better Testing - Worse Quality’,Hendrickson 2001)
摒弃 QA 是对于手工测试这种机能失调的实践的一种自然反应。之所以本文的标题没有取名为“敏捷团队中的测试人员”,是因为摒弃 QA 的做法在某些情况下并不可行,比如你的敏捷团队虽然实施了 Scrum 框架,却没有进行任何自动化单元测试,又或是团队正在进行某些商业现成品或技术(COTS)的软件开发。如果团队中没有设立功能测试人员,则必须实施 TDD 实践,或是其他任何一种能够生成自动化单元测试的方法。
在大多数情形下,选择了 TDD 就意味着你必须改变程序员的技能、习惯,并且往往还需要改变他们的态度与自我意识。而实现以上这几点并不容易,同时 TDD 本身也并非可以一促而就的:“要很好地掌握遗留代码、对单元测试进行适当的隔离、以及集成测试是非常困难的”(Shore 2007)。根据评估,当程序员转为采用 TDD 实践后,前几个月的生产力会急剧下降。不仅如此,对实践 TDD 的新手往往要进行几周乃至几个月时间进行手把手的培训(Larman,Vodde 2008)。
依我的经验看来,老派的程序员与测试人员之间往往存在着一种共生现象。老派的程序员不喜欢进行单元测试,只要项目中有测试人员,他们就企图蒙混过关。而老派的测试人员也不愿意学习技术知识,只要为程序员找到了足够的 bug,他们也同样选择应付了事。老派的程序员与测试人员都希望避免进行任何改变。因此,在我看来,如果程序员已经开始实施 TDD 实践,再往团队中安置一个功能测试人员就是一个坏主意。
我在多年的经验中观察到了这种反模式:如果你打算采用 TDD 或其他某种由开发者进行测试的实践,那么仅仅是因为在团队中出现了一位功能测试人员,就会让你的努力付诸东流。因此,如果你确实计划实施 TDD,我的建议是从团队中取消功能测试人员的角色!
但事实上,在实施 TDD 的过程中,在团队中保留一定的 QA 仍然是必要的,这是因为某些变化或许会出乎你的意料。在上述关于 TDD 与 QA 的对话中,David Heinemeier Hansson 说道:“或许你已经通过了所有测试,但或许它并没有发现真正的问题。一旦到了实际应用过程中,用户会以你始料未及的方式使用你的应用。”
Martin Fowler 十分赞同 David 的观点,但在同一番对话中,Kent Beck 的措词显得更为谨慎。但他也同意,在 QA 这方面,“事情的发展已经趋向于另一个极端”。如果你无法预见到所有的可能性,那么从外部获取某些反馈的做法“非常有意义”。
TDD 团队中的测试与团队组成
在以上对话的最后,我们已意识到在 TDD 的实施中,除了在编程过程中所创建的测试外,进行一定其他形式的测试工作仍是有意义的。敏捷测试的概念在“敏捷测试”(Crispin,Gregory 2009)等书籍中进行了详尽的描述。但实施敏捷测试是否仍然需要“测试人员”,即专业从事测试的员工,人们对于这一点似乎还有争论。Google 仍然有数百名测试人员,而 Facebook 几乎完全没有设立测试人员的职位。
而普通的公司则有着不同的考虑,他们必须保证员工已掌握了工具与概念方面的知识以开发并维护各种应用,并确保高效的分工。让我们实际分析一下在 Java 环境中引入测试人员意味着什么。
支持 Java 的 TDD 工具包括 JUnit 与某种模拟测试框架,一般的开发者都能够掌握这些工具。不过,JUnit 框架不仅支持在 Java 环境中应用 TDD,它还表现出了测试工作的第二次革新:它不仅支持自动化单元测试,还支持其他各种测试的自动化。
JUnit 目前还支持运行:通过 JAX-RS 实现的集成测试、自动验收测试、基于 Selenium Webdriver 的 UI 测试、以及支持各种数据集的参数化测试等等。并且这些测试都能够与持续集成(CI)方案进行整合。
除了这些测试工具之外,其他各种工具与框架也大量涌现。可以说,一般的开发者很难掌握在一个普通的现代化项目中所用到的全部工具。
概念性的知识是创建高质量应用的基本。要实现高可维护性,开发者需要了解代码整洁之道,而要掌握这方面知识需要多年的经验积累。如果我们想要精通这一领域的知识,接下来还可以学习设计模式、线程以及性能的原理。
准确的、可维护的以及高性能的代码虽然十分重要,但他们并不能保证某个应用是可信赖的。为了弥补这方面的缺失,开发者还需要学习安全方面的知识。而为了创建一个能够吸引用户的应用,开发者还要了解 UX 方面的知识。最后,为了设计一种高效的方式以保证以上所说的特性,开发者还需要熟悉测试的知识。
在组建 IT 部门时,工作的分工是又一项要考虑的重点。在团队的专业构成中,我们可以选择由各领域的专家,例如由一位安全方面的专家、一位 UX 设计师和一位测试人员组成一个团队,但这样一来就没有编码者的位置了,结果就是团队无法产出任何实际的东西。
反过来,我们也可以由多面手构成整个团队,但这意味着整个团队必须将最好的光阴花费在学习上,除非他们都是天才。这样的团队同样不会有很高的产出。
因此,我们的结论是在开发团队中有必要引入部分专利性。我们不能指望每个开发者不仅能够掌握全部的工具,并且还是整洁代码、UX 以及安全和测试方面的专家。另一方面,在团队中引入的专家数量也应有所限制。
既然我们必须引入一定的专业性,那么设置一位测试专家是比较有意义的:对于开发者来说,如果让他们来选择,那么大多数人不会去探索单元测试之外的内容,甚至有很多人根本不愿意承担任何测试工作。这也是为什么许多开发者不喜欢、甚至是厌恶测试的原因。如果要在这种环境中尝试转变为敏捷测试实践,那么就需要设立一位对于测试工作有热情并乐于实现它的专家。
与 TDD 的实施类似,以上过程同样需要他人的指导,并且向团队展示其工作结果。如果某位测试专家创建了对某个服务的测试集,并且能够从 IDE 中执行,那么程序员就很可能会去使用。不仅如此,如果开发者感受到了测试的实用性,那么他们就会开始扩展其功能,并以可维护的方式实现。一旦为测试所触动之后,程序员就会愿意继续进行测试。但以我的经验来看,仅靠程序员自己是无法感受到测试的好处的。
TDD:具有扎实技术背景的测试人员
在 QA 的兴衰这一节的总结部分,我曾表示:在实现了对手工检查工作进行自动化的 TDD 环境中,对于缺乏技术知识的传统测试人员的需求已经大大降低了。随后在对 JUnit 与 TDD 的介绍中,我们又看到开发者创建了大量的测试工具,而缺乏技术知识的测试人员将无法使用这些工具。
我们现在可以负责任的说,在 TDD 环境中,我们需要一种新型的测试人员,他们需要具备更扎实的技术背景。至于他的日常活动包括哪些内容,要考虑到 TDD 所实施的环境。对于敏捷测试来说,TDD 实现了自动化金字塔(Cohn 2009)的底层,以及测试象限(testing quadrants)的第 1 象限(Marick 2003 及 Crispin 2009)。
为了更清楚地了解其效果,让我们来考虑这个测试场景:某个表单的一个输入框可以接受一个整数,该数字必须在规定的边界之内,并且要进行后端的校验。我们在此处可以建立 16 种功能性的测试用例:{ x | boundary,boundary-1,boundary+1,decimal, locale,Z,0,null,“”,“ “,abc,UTF-8,2^31-1,2^31, -2^31,-2^31-1},但这些基本的单元测试只属于测试象限中的第 1 象限(通过面向技术的测试指导开发)。
而在 TDD 实践中,以上测试用例将实现自动化,测试人员不应(参照上文)执行这些测试用例。一般来说,他应当对于该输入字段是否存在以及一个正面用例进行校验(测试象限 2,通过面向业务的测试指导开发)。虽然可以通过某种录制与播放工具完成该任务,但这种方案缺乏可维护性。更有效的技术方案是(通过整洁的代码)编写 Selenium Webdriver 代码,并且让它能够在整个团队共用的 IDE 中执行。
象限 2 中的其他测试技术包括用户故事的测试,而这些测试同样可以实现自动化。“作为 InfoQ 的用户,我希望能够登录系统,以下载某些特别的内容”这样的行为可以暴露为 REST 调用等方式,并通过自动化测试执行。对于在 GUI 层进行的这种简单测试,有人可能会选择使用外部工具(例如 SoapUi)。但更高效的做法是让这个测试能够在 JUnit 中作为集成测试(“LogInIT.java”)而运行。而其他(没有许可证的)团队成员可以直接运行与维护该测试,并且无需学习该工具的使用。
当基本功能都实现了自动化检查后,我们就达到了第 3 象限(通过面向业务的测试来评价产品):团队已具备了开始进行探索性测试的先决条件。David Heinemeier Hansson 在上述对话表示,用户会以你始料未及的方式使用你的应用。这一点对于其他系统也成立,此时这种方式叫做突现行为(emergent behavior)。由于你不知道应该期望怎样的行为,因此此处可引入探索性测试(Hendrickson 2013)。
探索性测试(ET)依赖于小型的迭代:执行测试、对应用进行学习并为此设计新的测试。这种测试方式最初是受到 Test Heuristics Cheat sheet((Hendrickson 2006))这份非常容易获取的资料而启发的,但并不是说只需简单地执行其中的内容就代表你已经实现了探索性测试。探索性测试的真正价值在于它的迭代式特征以及对于知识的运用。
举例来说:在 Heuristics Cheat Sheet 中提到,在 web 测试中可以“对 url 进行各种操作,(例如变更或删除某些参数)”。如果在没有准备的情况下直接尝试编写相关的脚本或直接执行是没有实用性的。如果要改善这方面的行为,我们可以首先用几个迭代的时间去学习该应用使用这些参数的方式,随后想出(设计)一个相关的测试,最后才开始测试(执行)。毫无疑问,如果能够正确地运用 http 协议方面的知识,对于该测试的设计将带来极大的便利。
我在探索性测试中的常用做法是:在 IDE 中运行应用程序、对应用程序服务器的日志进行监控、打开数据库并对网络请求进行监控。这种方式显然能够看到一些在 GUI 中不会显示出来的错误。通过这种方式,我通常能够发现这些内容:大量的网络错误与请求、日志污染、非预期的持久行为、大量的 / 低效的数据库查询、安全性隐患以及使用性的错误等等。
这并不是说一旦应用了 TDD,所有的测试工作就会变得充满技术性,或是由工具所驱动。依然有一些非常重要的测试与人相关(Ambler 2003-2014),或是与 UX 的测试相关。这些测试所包含的技术性较少,但并不意味着就不需要了解深入的知识。
以上内容表示,TDD 让测试人员的角色发生了变化,而不再需要进行手工功能性测试(例如检查)。虽然他仍有大量的工作需要完成,但他所负责的功能性测试应该已经实现了自动化。而如果他能够掌握更多的技术、工具或其他方面的知识,那么他的手工(探索性)测试工作很可能会变得更为高效,只是这些知识往往并不容易掌握。
那么,TDD 团队中的测试人员究竟应当掌握哪些技术方面的知识呢?以下陈述基本是没什么疑问的:敏捷测试人员需要掌握良好的技术知识,了解如何与他人合作进行自动化测试,而成为经验丰富的探索性测试人员(Crispin, Gregory 2009)对于 TDD 团队来说同样有意义。
但我却相信,对于已开始实践 TDD 的敏捷团队与尚未开始实践 TDD 的敏捷团队来说,他们对于职务的需求也是不同的。对于尚未开始 TDD 的团队来说,敏捷测试人员也许将被迫使用某些不为开发人员所用的测试工作,或是进行大量的手工测试。而在 TDD 团队中,测试人员更有可能在 IDE 中进行工作,这时,该角色的技术需求就变为:
- 掌握至少一门编程语言(从而能够阅读及编写测试)。
- 了解命令行与脚本编写的知识(包括服务器与本地机器)。
- 具备数据库方面的经验(用于在没有 GUI 的情况下检查持久化的情况)。
结语
本文引用了 Kent Beck、Martin Fowler 和 David Heinemeier Hansson 的对话,这也是激励我撰写本文的动力。如果你对于测试有兴趣,应该听一听他们对于“将代码扔给 QA”以及“老派的 QA 做法还不如不要 QA”等观点坦率而直接的表述。
为了对此问题进行透彻的分析,我首先描述了老派的功能性测试方法,它所造成的结果不经过思考的功能检查,这种方式带来的伤害更大于它的价值。这并非我的臆想,而是有强烈的迹象表明仍有许多组织以这种方式进行测试,无论他们是否采用了“敏捷”实践。
接下来,我指出了为什么将 TDD 开发者与“老派的功能测试人员”结合在一起是一种不推荐的方式。在团队组成那一部分,我对于在 TDD 团队中设置测试人员的角色持保留态度,并将其修正为在团队中应当设立一些对于测试充满热情的成员。
至于测试人员所需的技能,我认为在 TDD 过程中已不需要进行老派的功能性检查。在 TDD 团队中仍然有测试人员的一席之地,但他们的测试工作需要更专业的技术知识。
收获
如果你是一位仍在进行手工检查的测试人员,那么应当考虑 TDD 或其他能够将手工检查自动化的解决方案。如果你还不具备上文所提到的技术知识,那么是时候将你的知识水平提升至这一程度,从测试工作中获得更大的乐趣!《More Agile Testing》(Crispin Gregory 2015)一书对于应当具备的知识进行了详尽的介绍,我极力推荐这本书给那些希望继续从事测试工作的读者们。为了掌握这些知识,我建议大家进行正规的学习,它会让你更好地了解某个主题,并且加快学习的速度,同时也使你有机会证明自己已具备了这些知识。
如果你是一位团队主管或经理,并且对于测试方面的问题感到受挫,那么你或许应当考虑一下如何实现更高级的测试方案。你需要的是在团队中找到能够实现方案,同时又对测试充满热情的人。在“程序员即测试人员?”(Programmers as Testers?)这篇文章(Gregory 2011)中,Janet Gregory 表示她倾向于测试人员应当具备技术背景的观点,但如果他们将测试人员的角色仅仅当作成为程序员的一块垫脚石,那么就不要以测试人员的身份招聘他们。这一点无可厚非,如果测试人员对于测试工作没有热情,他们就无法很好地实现测试象限或探索性测试。反过来说,如果某个测试人员不具备必需的技能,他就无法实现测试自动化,甚至在探索性测试中也做不到完全高效。换句话说,技能与热情是实施敏捷测试的必要条件。
参考
- Ambler (2003-2014), Personas an Agile Introduction
- Bach, Bolton (2013), Testing and Checking Refined
- Cohn (2009), The forgotten layer of the Test Automation Pyramid
- Crispin (2009), Agile Test Planning with the Agile Testing Quadrants
- Crispin, Gregory (2009), Agile Testing A Practical guide for Testers and Agile Teams
- Crispin, Gregory (2015), More Agile Testing Learning Journeys for the Whole Team
- Gregory (2011), Programmers as Testers ?
- Hendrickson (2001), Better Testing – Worse Quality ?
- Hendrickson (2006), Test Heuristics Cheat sheet
- Hendrickson (2013), Explore it!
- Kaner, Hendrickson and Brock (2001), MANAGING THE PROPORTION OF TESTERS TO (OTHER) DEVELOPERS
- Larman, Vodde 2008, Scaling Lean & Agile Development
- Marick (2003), Agile testing directions, tests and examples
- Shore (2007), The Art of Agile
关于作者
Maarten Folkers是一位测试方面的专家顾问,他对于(管理)传统的软件测试方法与现代化的测试技术的应用有着丰富的经验。现代化的测试技术包括 TDD 风格的编程方式、构建与部署自动化、在构建管道中集成协议层以及 GUI 层的测试、以及探索性测试(的宣传)。Maarten 具有法律专业的硕士学位,并且正在攻读计算机科学方面的本科学位。他目前居住在位于荷兰南部的 Den Bosch,热衷于历史、烹饪与跑步。
查看英文原文: Testers in TDD teams
评论