Skip to content

第20讲:没有ATDD,就没有用户故事的可测试性

从这一讲开始,我们就进入了第 4 部分的学习:测试左移更体现敏捷测试的价值 。因为敏捷更提倡团队对质量负责、预防缺陷胜于发现缺陷,这两点就意味着我们要构建出高质量的产品,把质量构建推向源头------需求,把测试活动左移到需求阶段,持续地对需求和设计进行评审、及时发现需求和设计的问题。


测试左移的目的是及时发现研发前期的错误,避免将错误带到代码阶段、测试阶段,TDD/ATDD(测试驱动开发/验收测试驱动开发)是更为彻底的测试左移,一次把事情做对,即零缺陷质量管理思想在软件研发中的实践,从而帮助企业节省研发成本并缩短开发时间。测试左移还包括测试计划和设计尽早开始,所有这些就构成了第 4 部分要讨论的内容。


测试左移还包括让开发人员做更多的测试、加强单元测试、持续集成等,在敏捷开发里这些都是核心实践,持续集成已经在第 12 讲讲过了,单元测试的内容将在第 42 讲中介绍。


在第 7 讲介绍敏捷团队中的专职测试人员时,提到过测试人员的责任之一是对软件的可测试性进行把关,可测试性是测试的基础或前提,没有可测试性,谈何测试?这一讲,我就来讲解什么是可测试性、可测试性的不同层次,以及如何提高用户故事的可测试性。

可测试性概念

软件的可测试性,从字面上的意思来说是指一个系统能不能进行测试。从理论上讲,可测试性基本上是由可观察性和可控制性构成的,还可以包括可预见性。

  • 可观察性(Observability)是指在有限的时间内使用输出描述系统当前状态的能力。系统具有可观察性意味着一定要有输出,没有输出就不能了解系统当前处于什么状态,也就不能确定系统行为表现是否正确。

  • 可控制性(Controllability)是指在特定的合理操作情况下,整个配置空间操作或改变系统的能力,包括状态控制和输出控制。系统具有可控制性表明一定要有输入,只有通过输入才能控制系统。


理解了前面两个,那可预见性(Predictability)也就好理解了,就是在一定的输入条件下,输出的结果是可以预测的,这样我们就可以给出系统的预期行为或预期结果,常常用于测试用例设计中。


图1 可控制性和可观察性的示意图


在此基础上,你可以重新理解一下可测试性:通过被测系统提供的接口对系统进行操作,然后,能够通过输出的结果了解到系统的状态是否符合预期。


如果一个系统缺乏可控制性和可观察性,如图 1 所示,可以通过增加接口的方式,使之可以控制它或产生输出。在敏捷测试中,没有自动化测试万万不行,而自动化测试对可测试性提出更高的要求,因为自动化测试更需要明确的输入和输出,所以要求系统能够提供可以调用的接口,通过接口调用来驱动程序运行并验证返回的结果。接口可以是 UI 界面或 UI 下面那一层面向业务的 API, 也可以是微服务模块暴露给其他模块的 API。


AI 软件的可测试性就是一个挑战,因为缺乏可预见性,不太容易事先定义好 Test Oracle 来验证输出结果是否符合预期。比如,Google 在 2018 年公布了用于 NLP(Natural Language Processing,自然语言处理)技术的 BERT(Bidirectional Encoder Representations from Transformers)模型,这个模型通过超过 3 亿个参数提供了强大的语句预测能力。但是,模型处理的结果需要人类去综合判断分析是否正确,并且需要理解参数微调带来的变化。不过,目前人们也在探索如何解决这些问题,第 44 讲会讨论 AI 软件的测试设计与执行。

需求、设计和代码等不同层次的可测试性

对可测试性的需求不仅仅是能够进行自动化测试和手工测试,既然我们要测试左移,那可测试性应该包括三个方面:需求的可测试性、设计的可测试性和代码的可测试性

  • 需求的可测试性,包括所定义的功能正确性是否能够被判断?输入、输出的数据是否有清楚的定义从而能验证其精准性?系统的性能、可靠性等是否有验证的标准和方法?

  • 设计的可测试性,是指通过设计来确保系统的特性具有可控制性、可观察性。另外,需要通过设计确保系统的简单性。从测试角度来说,设计越复杂,系统各组件之间的耦合度越高,对单个组件的测试难度就越大。

  • 代码的可测试性,一方面要保证需求和设计的可测试性得到实现,比如各项功能需求得到了满足,并且按照设计实现了系统的松散耦合、模块之间的接口定义。

用户故事的可测试性和 ATDD

敏捷开发提倡"可工作的软件胜于完备的文档",强调开发团队成员之间通过沟通来澄清需求,这就造成文档质量不高、需求描述不清楚。敏捷采用用户故事的形式对需求进行描述,一个用户故事(User Story)通常按照下列的格式(模板)来表述:

As a who(谁?),I want to what(什么行为?)so that why(为何?)。

(作为一个用户角色,我需要做某事,这样就能达到某个目的)


用户故事示例:

作为这家购物网站的买家,我要通过商品名称查询历史订单,这样我就能查看某个订单的详细信息了。


用户故事是以业务语言来描述的,而不是使用技术语言,比如,我要实现微服务 A 的一个 API 供微服务 B 调用,这就不是一个好的用户故事。同时,用户故事必须是可测试的,要不然怎么验证用户故事的实现是不是符合预期?


在实际工作中,也许你经常会碰到这些情况:某个用户故事的描述太简单,每个看到用户故事的人,其理解都不一样,但产品负责人(PO)可能和开发人员有过面对面的沟通,开发人员基本知道做什么,但测试人员还是不清楚,测试就追着开发问这个用户故事究竟要实现到什么程度?开发会说:别急,等过两天,我做好了给你和 PO demo 一下,就清楚了,如果你们觉得不行,我再改。看似态度很好,但测试人员能做什么吗?根本无法设计测试用例或写自动化脚本。


功能性的用户故事还好,非功能性的用户故事不可测问题更大。 例如,用户故事描述是这样的:作为一个注册用户,我希望能够快速地登录到系统内。那么,你会问,究竟多快算快?1 秒钟还是 3 秒钟?


为了保证用户故事是可测试的,需要对用户的实际需求有一个明确的说明,这就是用户故事的验收标准(Acceptance Criteria,AC)。比如,你阅读上面那个用户故事示例,会有很多疑问:

  • 查询历史订单,能够查询多长时间的历史订单?最近一年的,还是没有时间限制?

  • 支持不支持模糊查询?

  • 如果查到多个,如何显示?

  • 如果没有查到,给出什么提示?

  • 查看订单的详细信息,究竟包含哪些项?


有这么多问题不明白,验收标准就是要让这些问题明白,因此为这个用户故事示例增加相关的AC:

  • 缺省是查询过去一年的历史订单;

  • 如果没查到,问用户是否选择一个查询的时段;

  • 支持模糊查询;

  • 按匹配度来排序,而不是按时间排序;

  • 每页最多显示十个记录;

  • 查到后只显示订单号、商品名称、价格和日期;

  • 如果想继续看,再点击订单查看;

  • ......


这样,所要实现的用户故事就清楚了,也可以验证了。如果没有明确的验收标准,就没有用户故事的可测试性。用户故事的验收标准不仅让测试有据可依,而且由于列出了各种条件,开发人员在实现用户故事时就不容易犯错误,代码质量也会高得多。也正因为用户故事的验收标准澄清了需求,避免了开发和测试的争吵,有利于开发和测试的协作,而且减少了沟通成本,可以提高开发效率。


验收测试驱动开发(Acceptance Test Driven Development,ATDD)就是在开发设计、写代码之前,先明确(定义)每个用户故事的验收标准,然后再基于用户故事的验收标准进行开发。这样有利于我们之前强调的"质量是构建出来的"、"预防缺陷比发现缺陷更有价值"。

ATDD 与 TDD(UTDD)的关系

测试驱动开发(Test Driven Development,TDD)是指测试先行的理念,即测试在前、开发在后,提倡在编程之前,先写测试脚本或设计测试用例。TDD 在敏捷开发模式中被称为"测试优先的编程(Test-First Programming)"。


TDD 中测试先行的理念是为了给开发人员对编写的代码有足够的信心,代码的错误可以通过测试来验证。敏捷开发往往是快速迭代,程序设计不足,所以经常需要不断重构代码,而重构的前提是测试就绪,这样重构的质量就可以通过运行已有的测试得到快速的反馈。所以,有了 TDD,程序员就有了勇气进行设计或代码的快速重构,有利于快速迭代和持续交付。


重构是指在不改变代码外在行为的前提下,对代码做出修改。------《重构:改善既有代码的设计》


ATDD 和 UTDD(Unit Test Driven Development,单元测试驱动开发)都属于 TDD 思想指导下的优秀实践,可以看作是 TDD 具体实施过程的两个层次:

  • ATDD 发生在业务层次,在设计、写代码前就明确需求(用户故事)的验收标准。

  • UTDD 发生在代码层次,在编码之前写单元测试脚本,然后编写代码直到单元测试通过,这里的 UTDD 相当于传统概念(如极限编程)的 TDD。



在开发实践中,UTDD 在备受推崇的同时,也受到了广泛且持久的争议。 David H.Hansson 是著名的 Web 开发框架 Ruby On Rails 的开发者,他在 2014 年写了一篇文章对 TDD 提出了公开的质疑和否定,该文章是"TDD is dead. Long live testing"(TDD 已死,测试永生)。这篇文章一出来就引起了广泛的讨论,赞成者认为说出了自己的心声,当然也趁机表达了自己的意见,比如,工期紧、时间短根本来不及写单元测试;TDD 对开发人员的要求过高,推行的最大问题在于很多开发人员不会写测试用例,也不会重构代码。


反对者认为 David 对 TDD 的理解是片面的、不正确的,认为 TDD 就是 UTDD,而忽略了 TDD 还包括 ATDD。这里暂时不对 UTDD 进行详细讨论,但就目前的情况来看,UTDD 虽然没有死,但推行的也不好。我在今年年初公布的 2019 年软件测试调查结果中显示,只有 21% 的团队在做静态代码分析、单元测试,更别提更加高大上的 UTDD 了。


而相比 UTDD,面向业务层面的 ATDD 推行起来就比较容易,而且是必须的。因为需求模糊、需求不具有可测试性,你能接受吗?模糊的需求往往意味着返工和浪费,没有可测试性也就意味着无法开展测试。 所以,团队按照验收标准来实现用户故事,是不是理所当然?


到这里,这一讲的内容就讲完了,我简单总结一下:

  • 系统的可测试性包括可观察性、可控制性和可预见性;

  • 明确用户故事的验收标准是让用户故事具备可测试性的关键,也是必要的;

  • 把 TDD 看成是"测试先行"的理念,这样 ATDD 和 UTDD 则是其两个不同层次上的落地实践;

  • 相比 UTDD 引起的争议和推行的困难,ATDD 更容易在实践中落地。


最后出一个思考题:你所在的公司里是在推行 UTDD、还是 ATDD 呢?有哪些挑战和优秀的经验可以分享?欢迎留言讨论。