Cucumber读书笔记

最近读了一本《Cucumber行为驱动开发指南》,有些收获想总结下来。

极限编程产生了TDD(Test-Driven Development)实践,然后在TDD的基础之上又衍生了BDD(Behavior-Driven Development),标准化了那些TDD实践者的良好习惯。产生的原因背景:领域专家使用他们的行话,技术团队成员则拥有自己的、专门从设计角度讨论领域的语言,由于语言方面的分歧,领域专家描述需求的时候非常模糊,开发人员努力尝试理解一个全新的领域,却只能得到模糊的结果。


通过整个团队、项目涉及的所有人使用同一种大家都能理解的通用语言,就能解决这个问题。Cucumber为存在语言分歧的双方提供了可以会合的场所,从而促进了通用语言的发现和使用。它是一个工具、或者可以说是一套理念,用近似自然语言的方式来描述系统/产品的真实行为,开发人员可以利用这份描述来做验收测试,驱动他们写出行为正确的系统代码,而产品、股东等也可以利用这份描述来确定他们希望系统做哪些正确的事情,再则客户也可以阅读这份描述来理解这是否是他们需要的产品,最后,测试人员可以使用这种描述来作为测试用例,甚至是可以自动运行的自动化场景。总之,所有人看到的都是同一份描述,同一段文字,没有歧义,没有隔阂,不存在误解(当然前提是这段文字写得精确又不冗余,这当然是需要通过磨合锻炼沟通才能达到的效果),先来看一段这样的文字:

Scenario: Successful withdrawal from an account in credit
  Given I have $100 in my account #the context
  When I request $20 #the event
  Then $20 should be dispensed #the outcome
  And I should have $80 in my account now

Cucumber是一个命令行工具,运行的时候它会从普通语言编写的称为Feature的文本文件中读取你的规格说明、解析需要运行的Scenario,然后针对系统执行Scenario里面定义的一系列Step,这段文字必须遵循一些最基本的语法规则,这套规则就叫做Gherkin。


笔者这里就不再介绍更多关于Cucumber基础的内容,由于之前也有过Cucumber的实践,书中提到了一些经验和教训值得每个使用Cucumber的人反思:

  • Cucumber在解析运行Step的时候实际并不关心你使用哪个关键字:Given/Then/When/And/But,他们只不过是为了增加Scenario可读性而存在的。
  • 我们设计的每个Scenario必须是有独立意义的,且能够不依赖其他任何场景独立运行。
  • 注意Scenario的命名,因为除非很有必要,否则没有人会想阅读每个场景的所有步骤,把Scenario当做程序的一个方法名,尽量做到让人不读方法内部的代码就能知道这是干什么的。(建议:不要把Then部分相关的内容引入到Scenario名字中去)
  • 如果你感到Step使用的描述方式出现了一些细微的模糊性,即不同人看了可能会有不同的理解,那么就开始改善你的用词,提高用词的精确性,并跟不同背景、部门的人咨询反馈和建议。毕竟我们最终目的是达到描述所传达的信息是一致的。
  • 每个Feature文件都可以有一个Background元素,它必须出现在Scenario的前面,它可以通过一些通用的步骤降低Scenario里Step的重复性,但应该尽量保持Background小节简短并鲜明生动。这样增加了故事性,更易于读书了解内容和信息。
  • 避免将太多如清除缓存、开启服务器或者打开浏览器之类的技术细节放入背景中。
  • 当你的Scenario时而成功,时而运行失败的时候,我们称之为闪烁的场景,针对这种场景,必须努力搞清楚它发生的原因,通常可能是由共享的环境、渗漏的场景或者竞争条件和打瞌睡的步骤所导致的。必须尽快的修复它,否则宁可讲整个场景删除。
  • 通常真正使用Cucumber来做验收测试和设计场景用例的时候,容易陷入到繁琐的细节步骤中,例如点击了某按钮、填写了某字段、刷新了某页面……这种针对用户界面控件的描述属于泛化且较低层次的领域描述,应该对其进行合理的抽象提高描述的领域层次。否则,相信我,没有人会有兴趣来阅读你大段的界面操作!
  • 在大多数团队中,对于测试的维护都不会出现在靠前优先的位置,这种对待测试的态度是完全错误的。团队需要一丝不苟的关注并维持测试的健康,举个例子:自动化测试对于依赖自动测试的团队来说就相当于心跳。丰田车间里有个著名的故事叫做:停掉生产线。当每次出现问题的时候,车间里的每位工人都有权力停掉整条生产线,问题马上会得到所有经验丰富的员工给予全力关注,只有解决了生产线才会重新启动,之后再有一个小组专门来继续分析根源在哪里。当这个想法第一次提出的时候,只有少数经理赞同,但经过一段时间的实践,停掉生产线的次数越来越少,实施了这一策略的经理们发现车间的生产量在经历了短暂的下滑之后,迅速大大超越了那些沿用旧制度的车间。
  • 许多团队担心Cucumber总是在描述一些最简单的场景,其实任何Bug都可以追溯到我们为那部分系统编写的Cucumber场景的不足上,存在Bug不是任何人的错误,而只是一开始我们没有预料到的一个边界条件而已。
  • 如果你在编写自动化测试,你就是在开发软件,如果你足够重视测试,并且在第一时间编写测试,那么你就会希望将来能回头再修改这些测试。也就是说,我们在编写可维护软件时遵循的那些好习惯在编写测试代码的时候同样适用。
  • 在复杂的系统中,某些Step的执行必须要跟另外的机器或者系统模块通信,此时采用监听事件的方式是让测试与异步系统保持同步最快且可靠的手段。(相对于简单的sleep或者wait固定的时间来说)


从业务的角度Cucumber提供了一种方式来让整个系统的所有相关人员看同一段文字达到无障碍的理解系统最正确的行为。从技术的角度,Cucumber只是一个命令行工具,把场景中的每一条Step通过正则表达式匹配的方式映射到底层代码的实现中去,从而完成自动化测试。至于底层代码的实现,Cucumber支持多种语言:Ruby,Java,Javascript等等,都可以在开源社区找到相应的库支持Cucumber的整套理念。例如下面这段Java代码,就对应了几条Step实现的操作:

@Given("^a shopping list:$")
    public void a_shopping_list(List<ShoppingItem> items) throws Throwable {
        for (ShoppingItem item : items) {
            shoppingList.addItem(item.name, item.count);
        }
    }

    @When("^I print that list$")
    public void I_print_that_list() throws Throwable {
        printedList = new StringBuilder();
        shoppingList.print(printedList);
    }

    @Then("^it should look like:$")
    public void it_should_look_like(String expected) throws Throwable {
        assertEquals(expected, printedList.toString());
    }

对应的场景(Scenario)如下:

 Scenario: Print my shopping list
  The list should be printed in alphabetical order of the item names

    Given a shopping list:
      | name  | count |
      | Milk  | 2     |
      | Cocoa | 1     |
      | Soap  | 5     |
    When I print that list
    Then it should look like:
      """
      1 Cocoa
      2 Milk
      5 Soap

      """

可以看到Cucumber运行的时候不关心关键字用的Then/Given/When,当Step的语句能唯一被正则表达式识别的时候,就会投射到相应的Java方法来自动化的做实际的操作。关键字的作用永远只是为了增加场景描述的可读性而已。


鉴于之前经历过的Cucumber实践,笔者看到的问题是:

  • 我们经常把界面的每一个操作步骤给Step化,让整个Scenario充满了"I click xxx", "I fill form with xxx", "I login with account xxx"之类的Step,可能一个场景写下来就有100行之多,相对复杂的系统这样的场景可能有几百甚至上千个,没有人能提起兴趣去阅读成篇的GUI操作步骤而仍然不知道系统在做什么,只知道当你完成这些点击和表单填写之后,GUI上会发生什么事情。所以我们犯的错误是描述的领域层次太低,方向就走错了!
  • 我们经常会看到大量重复的步骤,例如“login xxx”和"fill xxx form"等等,当你发现冗余的代码非常多的时候,重构和抽象肯定是做得不好的。实际上Cucumber有许多方手段支持Scenario步骤的抽象,例如Data Table,Transform,Background, After/Before等Hook,还有Env、World这样的全局对象可以用来预定义一些全局数据和状态,善用它们可以使得你的Scenario Step言简意赅,更像一个生动的故事,可以让读的人不觉得厌烦而且能快速理解里面的信息。
  • 我们经常看到Scenario之间存在依赖性,当一个场景不通过,可能导致数十个场景必然失败,这说明对于场景的设计是非常脆弱的。这里必须提到设计场景的人可能是资深的QA,他们并没有代码重构和可维护性的经验,实现场景的可能是测试开发人员或者开发,它们也可能缺乏对产品功能的足够深入的了解,去避免场景之间的依赖性。只有协作描述的方式才能解决这类问题,甚至在Step的用词方面,应该早期就把产品人员、运营人员、开发一起拉进来,大家约定通过一致的用词和句型,把误解的可能性降到最低。(这也说明了为什么测试在需求阶段就介入的意义何在,实际上那个时候是没有实际的东西可测的,但早期就从测试基础建设上做投入,持续的迭代改进必然是最科学有效的方式)
  • 我们的测试人员在写Step的时候,通常会由于大家的语法、句型习惯不一样而写出不同风格的描述Step,有的Step会唯一的映射到一个方法的正则表达式上,有的却会同时匹配两三个方法的正则表达式,此时破坏了Step的唯一性和全局性,Cucumber是不会知道该用哪个正则表达式来匹配你的Step,无法继续执行下去。由此可见,对正则表达式的设计也是非常讲究的,首先它的实现肯定是测试开发人员,很可能不是设计场景的测试人员,但是只有资深有经验的测试人员,非常了解系统的行为,才知道用哪一套句型可以精准完整的描述出系统所有的行为。这个时候,我们本末倒置的是所有的Step正则表达式都有测试开发人员直接设计并实现,然后测试人员直接拿来使用,结果就感觉到越用越不好用。
  • 由于Cucumber是顺序执行的,在解决了场景之间的依赖性之后,复杂的系统可能有几百甚至上千个场景,从头跑到尾需要数小时才能得到测试结果的报告。这对于需要快速响应和反馈的敏捷流程来说,往往是不可接受的。开发可能不愿意等结果就继续提交代码,从而触发新一轮的测试。这种时候,我觉得应该从场景的划分和管理、运行上,就开始重构,把场景按模块、按优先级、按时间量级进行分类管理(实际上Cucumber也已经提供了许多手段帮助你做到这一点),然后在运行上,可以同时在几台不同的虚拟机上并行测试,这一切在运行之前,就应该评估出完成测试的时间和范围这些数据,然后由人的经验来判断哪些场景需要,哪些这轮不需要,对于代码模块划分较好、敏捷迭代比较成熟的团队,没有代码修改的模块通常需要极少的测试,这些通过配置就可以轻易解决,让整个自动化测试流程可以更快的响应得到结果,从而指导开发及时的发现问题改善代码质量、修复Bug。可惜,在当时,整个团队做得并不好!


最后,笔者想强调的是,Cucumber的目的是好的,但是在如今的行业现状内,并不是每个公司,每个项目都适用的。不要永远用理想化的理念去映射现实,哪怕这些理念永远是对的。比如,在有的团队,即使你的场景设计得再好,故事再生动,可能企业文化风格就决定了测试以外的其他部门根本不会来阅读它;再比如在有些项目中,产品的行为要牵连到许多硬件、或者难于用Step实现代码来自动化控制的东西上,此时Scenario的唯一意义也就沦为了测试用例而已,它无法或者很难被自动化;还有的产品本身存在许多遗留代码,乱而且不规范,在高度耦合的情况下,要设计出独立运行不依赖的场景本身就相当困难,这种时候你甚至要反思Testing或者Bahavior到底能不能驱动开发。

Anyway,我们不能否则Cucumber提出了一种DevOps和Agile里也提倡的方式,如果用得好,是能够大大改善产品的质量和整个研发效率的!


如果对Cucumber感兴趣,可以去它的GitHub站点找到各种语言版本的支持和源码、文档:https://github.com/cucumber


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值