在XP Agile Universe上,两个人-或许更多-告诉我说,我在敏捷测试的发展方面贡献不够。我在过去5年里花了太多的时间说我不知道敏捷测试会怎样,没有足够的指示和指导。“但是让我们看看,也许我们可以找到。”他们可能是对的。因此我让本文作为这方面的一个起点。
我先重申一些普遍的概念区别,以作为起点。
如果你听到别人在谈论敏捷项目中的测试,问一下那些测试是面向业务的还是面向技术的,会对你有很大的帮助。面向业务的测试是你可以用一个业务专家感兴趣的术语来向他描述测试。如果你通过电话描述测试回答了什么问题,你可以使用业务领域的术语:“如果你支取超过你的账户余额的现金,系统是否会自动给予你一笔与超出部分等额的贷款?”
面向技术的测试是你使用程序员的领域的术语来描述测试:“不同的浏览器会通过不同的方式实现Javascript,所以我们测试产品是否能在最主要的浏览器上工作。”或者:“如果用户记录不存在,PersistentUser#delete不应该执行。”
(这些分类有着很多模糊的界线,例如,选择测试哪个浏览器配置,部分是业务决定。)
问一下正在讨论测试的人,他们希望测试支援编程还是批判产品。对于“支援编程”,我的意思是程序员把测试作为编程的主要组成部分。例如,一些程序员编写测试用例来告诉他们下一步应该写什么代码。通过编写那些代码,他们改变一些程序的行为。这些更改之后通过运行这部分的测试来保证他们修改的是他们需要的。运行其它的测试来确保更改的行为不会影响其它不需要更改的部分。
批判产品的测试则不是专注于编程方面。而是在已完成的产品上查找发现产品的不足之处。
如果把这两类区别放到一起,就得到下面的矩阵图:
接下来,我会谈谈这个矩阵的每个区域,我关于它们的发展的预测。
'It all depends on what
you mean by home.'
[...]
'Home is the place where, when you have to
go there,
They have to take you in.'
'I should have called it
Something you somehow haven't to deserve.'
-- Robert Frost, "The Death of the Hired Man" |
“这在于你如何理解家的概念。”
…
“家是你随时随地可以去的地方,是他们会让你进去的地方”
“我应该把它称为你不知何故,不值得拥有的东西”
-- Robert Frost,“The Death of the Hired Man”
上次,我画了这样一张矩阵图:
左边是偏向“支援编程”的测试,右边是偏向“批判产品”的测试。但是两种测试的意义和内涵存在很大的不同。
对于支援编程,测试主要作为准备和保证。你通过写测试代码来阐明关于问题的思考。你把它作为说明性的例子来描述代码应该怎样做。幸运地是,它同时是活跃地检查代码的说明性例子,即重新保证。这些测试也会找bug,但是那是第二目的。
在另一方面,测试是关于暴露主要错误和遗漏。这里,测试的原义就是关于bug。有其它的意义,但是首要的意义是最主要的。(很多测试员,尤其是最好的测试员,在他们的身上已经融入了那些词语的内涵。)
我想做个尝试。如果我们在矩阵的左边不使用“testing”和“test”这些词语会怎样?如果我们把它叫做“checked
examples”(检查例子)怎样?
设想两个XP程序员坐在代码前面。他们开始构建一个例子来说明下一步要做什么。他们会检查,在代码还没写之前。(如果写了,那是很特殊个别的情况。)他们编写代码。检查例子是否运行正确,其他例子也保持正确。然后继续下一个例子用于展示下一步应该做的事情。
替换词语有意义吗?是否只是文字上的替换而已?你做一些尝试,然后回答这些问题吧。尝试经常使用“example(例子)”,经常使用让它听起来不会感觉很奇怪。现在,当你坐在代码前面时,是否根本改变了你的观点?是否有了一些不同:在你向客户要求一个例子,而不是一个测试时。加上一些形容词:example(例子)是否看起来更具激发性、更生动、更有深刻内涵?与强大的测试存在怎样的区别?(“强大”作为附加给测试的典型的形容词。)测试人员在XP项目中,每个人都在制作例子,没有人做测试,这样是否看起来更轻松些?
为了帮助讨论和理解,我把“敏捷项目中的测试”这一主题分解成4个区分的主题。今天,我讲一下我们怎样使用面向技术的例子来帮助和支援程序开发。
这里适用的一个是测试驱动开发,在Kent Beck的书中,David Astel最近的书,Phlip,J.B.Rainsberger接下来的书中都讨论了这种开发方式。我认为测试驱动开发(我现在会叫它例子驱动开发,example-driven
development)是可靠的。它不是主流的开发方式,但是看起来要成为主流了。套用Geoffrey Moore的话,我认为它正在跨越鸿沟的路途中。
也可以这样说,例子驱动开发已经从Thomas Kuhn所说的“革命性的科学”发展到“正规科学”。在正规科学中,人们扩展了特定方法的适用范围。因此我们现在很多人在GUI应用EDD(sic),设法让它与遗留代码(legacy
code)一起工作,讨论mock objects的好的使用方法,讨论处理私有方法的技术,等等。但是这些都不是重大的发展。
我希望不久的将来会看到更多同时拥有测试和编程的技巧的人被吸引到敏捷项目中来。这些人既不会成为像纯测试人员那样的好测试员,也不会成为像纯编程人员那样好的编程人员。但是那还是不错的,如果你跟我一样相信敏捷项目应该重视通才多于重视专才的话。
我不是这样的混合人物。我没有与纯编程人员一起做很多的结对编程。但是我注意到,在维持编程的进度和目标的希望与确保很多好的测试的主意被考虑到的希望之间存在着紧张的关系。我发现我自己在进入程序员模式和退出并考虑全局的之间振荡。从经验看来,我们需要更好的思路来管理这个过程,以及关于什么类型的“测试思考”在编码过程中是合适的。
也可能有一些测试员在敏捷项目中不担当程序员的工作。不过,他们中的一些人还是跟程序员结对讨论单元测试(关于程序员如何检查代码)。程序员学习如何避免哪些类型的bug,测试员学习他们正在测试的是什么。不知何故,加拿大的卡尔加里成为了这些活动的温床,我指望Jonathan
Kohl,Janet Gregory和其他人告诉我怎样才能做好。
我需要强调这些都是关于人的。传统上,测试员与编程人员会有很紧密(或者很广阔)的关系。对于矩阵的支援程序员这部分,我相信传统的关系是不合适的。
我使用术语“checked examples(检查例子)”作为支援程序员的测试。我们可以把这个概念一分为二。一部分作为指引下一步编码的决定。另外一部分是自动化的例子,作为“改变探测器”(change
detectors),看刚才的修改是否是你期待的。
通常的习惯是,改变探测器仅仅是保留的代码的保护性例子。(你让你的单元测试套件作为救助,一个对应一个地,在编码的同时测试。)那不是逻辑上的要求。我喜欢先建立一些关于什么时候做其他事情的知识。
例如,考虑这样的维护场景:你首先开发了一些代码例子。一个月后,别人加了一个新的例子并改变了代码以便匹配。很多之前为那部分代码开发的例子都成了“bad
examples”坏例子了。(测试失败,但是因为他们现在是错误的,不是因为代码错误。)修正那些例子以便他们一致。我的意思是在下表的左边的事件序列本来是期望与右边的测试一样的。(先看左边栏,然后看右边。)
Example foo written |
Example bar written |
Code written to match foo |
Code written to match bar |
Example bar written (foo invalidated) |
Example better-foo written (bar is still a
good example) |
Code changed to match bar - oops, now foo
doesn't check out |
Code changed to match better-foo (and bar
continues to check out) |
Update foo to be better-foo |
|
最近坏掉的例子被重写以便匹配一个理想的开发顺序,在这个过程中不需要重写任何例子。但是为什么?在上表的左列,新的例子foo没有用于驱动开发,只是作为检查。对于驱动开发来说是最佳的可能对于检查来说不是最佳的。
假设软件系统开发切面层(shearing layers),切面层的接口通常是不会改变很大的。为了可维护性,在修改时移植坏掉的例子到界切面层是有意义的。取代一个例子对应一个类的一个方法的做法,我们现在使用一个例子对应整个子系统。那可能会很糟糕
- 想想调试 – 但是它减少了维护的负担,甚至能提供关于子系统的行为的完整文档的好处。
我希望人们分清楚两个角色 – 指引不远的将来和重新检查过去 – 会发现有建设性的知识。例如,什么时候编写面向技术的“改变探测器”(而这些与指引编程没有任何关系)可能会有用?
我在上面说的测试驱动开发是“期中一个适合”今天的主题的东西。那么其他适合的是什么?我不知道。EDD是否是最合适的?(是否最近可能发生革命性的改变?)我也不知道
– 我要依赖那些善于打破旧习的人来帮我找出来。对此我将非常感兴趣。
|