行为驱动开发之四,为自动化测试(运行Cucumber)提速

  六个月前,开始推广BDD。时至今日,已经有了1200个情景(Scenario)。如果把每个情景,当做一个自动化测试用例,那么短短半年,我们已经从无到有,开发了1200个测试用例。在没有BDD的过去五年中,我们一共才开发了2000个自动化测试用例。粗略地计算下:

  • 过去2000/5=400个/年
  • 现在1200/0.5=2400个/年

  即BDD使我们的自动化开发效率,提高了五倍,是原来的六倍。(等我先我自豪一下,hiahia。)

越多越慢

  慢着!还不能高兴地太早。高效的自动化开发,带来了新的问题。突然一下子多起来的测试用例,让自动化执行时间变长了。每次的nightly build/test的时间,竟然已经接近12小时。如果加上双平台,那么跑上一轮自动化测试,要整整等上一天才能知道结果。在推广持续集成的现在(推广CI的博客,我会陆续登出),只要有代码提交,便运行的自动化测试,竟要耗上一整天,才能对提交的代码有一个反馈?!这不管对测试人员,还是研发人员,都是不可以接受的。

  自然,最简单的解决办法,少跑。从每个Feature(功能)中,精心挑选出最基础的Scenario(场景),每次只运行这些基础的场景。这样子,便无法叫做回归测试,顶多算是验收测试,或者冒烟测试。这个做法,大家说的烂大街了,挑呗,没啥难的。

  可我不偏不要烂大街!自动化测试开发的意义,就是要多跑!多测!不间断地验证提交代码的正确!回归正确!要真是写了个脚本,一个礼拜跑一次,那也太金贵了!因此,一场轰轰烈烈的为自动化测试提速的运动展开鸟。

  以下按照提速有效性,降序排列,越往后,提速越小。

提速法1:降低Setup/Cleanup的层次。

  举一个在Eclipse的workspace中创建Java文件的例子。

Scenario: create java file in ws 
  Given I have an Eclipse workspace 
  When I create a new java file with name "Student" 
  Then eclipse should create a file with name "Student.java" as:
   """ 
   public class Student { 
    public Student() {
    } 
   } 
   """

  修改前,此处的step:

Given /^I have an Eclipse workspace$/ do 
  eclipse.new_workspace() 
end

  但是,当测试用例变得越来越多时,每个用例都需要"Given an Eclipse workspace",这时候,就要思考一下,有没有什么底层的,快速的办法,生成一个空的"workspace"呢?答案是,直接写文件。因此,我们大可以将上述step,改为:

Given /^I have an Eclipse workspace$/ do 
  system("cp -r #{$empty_ws} .")
end

  以此类推,需要“delete_user"的地方,大可以直接delete database,甚至可以使用数据库镜像与回滚(db snapshot/rotate)的工具。理由:我要测试的步骤是"create a new java file",而前后的步骤,只是我所需要的环境。搭箭环境的步骤,以快,准,稳为原则,尽量避免免调用系统接口浪费资源与时间

提速法2:提高Setup/Cleanup的层次。

  在降低了部分Setup/Cleanup的层次后,我们会发现,有些Setup/Cleanup,是不需要每一个Scenario都要运行的。如远程Login的链接,如登录Web页面的链接等等。hook.rb中,可能表现为:

Before do 
  @cli = Cli.login(host, user, password) 
  @web = Web.login(url, user, password) 
end 
After do 
  @cli.logout 
  @web.logout 
end

  这些链接是属于无状态的,并不会因为这次提交了某个cmd,或者http request而影响链接本身。为了避免重复登录、退出,可以在一开始(所有Scenario开始前)就登录,而在结束后(所有Scenario结束后)退出。改写后的hook.rb为:

@cli = Cli.login 
@web = Web.login 

at_exit do 
  @cli.logout 
  @web.logout 
end

  这样,就避免了每次登录、退出所需要的时间。

提速法3:干掉sleep。

  这个本来不想说,不过我搜了一把,sleep还真是多啊。去掉每个sleep的方法都有不同,这里要根据具体情况分析,就不赘述了。实在不行,sleep这一类的Scenario,可以打一个标签,叫@long,或者@sleep,在运行时,去掉这类标签。将其由每小时运行一次,改为深夜运行,每天一次吧。

提速法4:减少模块依赖。

  控制集成测试与系统测试的用例数,对于提高自动化运行速度,很有帮助。如下面的例子:

Scenario: user action should be logged 
  Given user login bank website 
  When user transfer "20" bucx to stock 
  Then bank should log as: 
  | userid | mount | date |
  | $user | 20      | today|

  开始时,我们的step为:

Then /^bank should log as:$/ do |ast| 
  ast.hashes.each do |hash| 
    sleep 5 
    logdb.search(hash).should == true 
  end 
end

  这里的问题是:引入log系统的数据库。数据从web系统,到log系统,最后被存入数据库,耗时较长,且不定时常。只能靠长时间的sleep来解决。如果将所产生的日志写在文件里,只检测文件,那么速度就快多了。修改后,step为:

Then /^bank should log as:$/ do |ast| 
  ast.hashes.each do 
    open("log", r) do |f| 
      f.read.should.include ("userid"+hash["user"]+"mount"+hash["mount"]+"date"+hash["date"]) 
    end 
  end 
end

  当将log系统剥离之后,这个Scenario的执行时间变短了,更提高了模块化的程度。即,如果log系统当前版本不稳定时,依然不会影响web系统的测试。

提速法5:精简数据

  这可能是大部分测试人员会发生的问题,用例的过分复杂,导致step(步骤)太多,或是data(数据)太复杂。如:

Scenario: regiestered user login 
  Given registered users as: 
    |user | email                | password |
    | a     |a@cnblogs.com | 123           |
    | b-B_|b-B_@cn.com    |3445         |
    | c      |c@haha.com     |1                | 
  When users login with password:
    |user | password| 
    | a     | 123   |
    | b-B_| 3445  |
    |c      |2       |
    |d      | 1233 | 
  Then users should see: 
    |user    | msg       |
    |a        | welcome |
    |b-B_   |    welcome |
     | c      | incorrect password|
     |d | invalid user |

  这个用例的问题是,测试重点不突出,导致了冗余数据。从Scenario书写的意图看,即想测试各种用户名,又想测试合法与非法用户登录。为了突出测试意图,我们可以将它分解:

Scenario Outline: different user name login 
  Given regiestered users as <user> and <email> and "123" 
  When user login with password "123" 
  Then user should see "welcome" 

  Scenarios: 
  |user | email |
   |a |a@cnblogs.com|
   |b- |b-@cnblogs.com|
   |c_ |c_@cnblogs.com|
   |B |B@cnblogs.com | 

Scenario: unregiestered user login fail 
  Given unregiestered user "a" 
  When user "a" login with password "123" 
  Then user should see "invalid user"

  之所以把它排在最后,是因为精简了数据,很可能导致一个超级复杂的Scenario,变成若干个小的简洁的,测试意图明确的小的Scenario,却不保证,运行时间一定会缩短。看着运行Cucumber的时间一天比一天少,心情也越来越好。傍晚路过咖啡店时,买了个很甜的东西奖励自己。可直到吃完,都不知道那东西叫什么名字。

posted @ 2011-09-02 01:00  jarodzz  阅读(3416)  评论(5编辑  收藏  举报