AppOSS中的研发测试

——李福@淘宝

fswordlee@twitter

http://weibo.com/fsword

AppOSS中的研发测试

我们都很重视测试

然而测试确实很麻烦

测试遇到的问题

AppOSS的测试情况

 coverage of core model  ->  53%             
 code to test ratio      ->  1:0.7           

但是,AppOSS并不是用java...

我们希望成长!

白盒的优势

数据准备

# roles.rb 使用代码制造数据
Role.create(:name => Role::Admin)
Role.create(:name => Role::PE)
Role.create(:name => Role::APPOPS)
# inventory.rb 直接装载sql,提高测试速度
conn = ActiveRecord::Base.connection
data_file = "#{Rails.root}/fixtures/load_all.sql"

File.readlines(data_file).each do |line| 
  conn.execute line
end

数据准备

# coffees.rb 数据间的依赖
S.define :hangzhou do
  S[:dalian]=City.create(name: "HangZhou")
end

S.define :cafes, :req => :hangzhou do
  S[:starbucks]=Store.create(
    name: "Starbucks", city: S[:dalian] 
  )
  S[:metoo]=Store.create( name: "Metoo", city: S[:dalian] )
end
    

使用数据

自动化测试 - 从TDD到BDD

# language: zh-CN
功能: 基本运维
  系统运维的基本用例

场景: 用户通过系统进行一次运维操作的完整流程
  假如创建了一个应用,名字是center
  而且应用center包含机器localhost
  而且基础数据已经导入
  当lifu被任命为center的PE
  而且lifu通过浏览器登录系统
  那么系统显示center应用
  而且机器列表中包含localhost

这是一个测试用例

这是一个可执行的测试用例

演示一下

一个真实的例子

很简单......

# 用例描述
  当lifu开始管理自己的指令
# 驱动代码
When /(.+)开始(.+)/ do |user, behaviour|
  user = find_or_create_user(user)
  case behaviour
  when '管理自己的指令'
    @browser.link(:text, '管理的指令').click
  end
end
!!!发现可消除的重复

改进

# 用例描述
  当lifu开始管理自己的指令
  当lifu开始管理的指令
When /(.+)开始(.+)/ do |user, behaviour|
  user = find_or_create_user(user)
  case behaviour
  when '管理自己的指令'
    @browser.link(:text, '管理的指令').click
  end
  @browser.link(:text, behaviour).click
end
小结:一致性通常能提高复用水平

接着...

# 用例描述
  ......
  当lifu开始管理我的指令
  那么系统显示我的指令列表
# 驱动代码
Then /^系统显示(.+)列表$/ do |title|
  @browser.html.should include(title)
end
验证能力不足!!!

改进

# 修改视图文件view
...
    我的指令列表
    <span class="title">我的指令列表</span>
...
# 用例描述
Then /^系统显示(.+)列表$/ do |title|
  @browser.html.should include(title)
end
Then /^系统显示(.+)列表$/ do |title|
  @browser.span(:text, title).class_name.should == 'title'
end
小结:测试推进html文档结构化

更完整的代码...

# 驱动代码
Then /^系统显示(.+)列表$/ do |title|
  span = nil
  @browser.wait_until do
    span = @browser.span(:text, "#{title}列表")
    span.exist?
  end
  @zone = span.           tap{|e| e.should_not be_nil}.
            parent.       tap{|e| e.tag_name.should == "h2" }.
              parent.     tap{|e| e.tag_name.should == 'div'}
当用户在其中执行创建...

然后...

# 用例描述
  ......
  当用户填写指令date并提交
  那么列表中出现date指令

  当用户在列表中删除date指令
# 驱动代码
When /用户在列表中删除(.+)指令/ do |command|
  @zone.span(:text, command).parent.link(:text, "删除").click
end
失败!!!
Element is no longer attached to the DOM

改进:重新获取dom

# 驱动代码
When /用户在列表中删除(.+)指令/ do |command|
  span = nil
  @browser.wait_until do
    span = @browser.span(:text, "#{title}列表")
    span.exist?
  end
  @zone = span.           tap{|e| e.should_not be_nil}.
            parent.       tap{|e| e.tag_name.should == "h2" }.
              parent.     tap{|e| e.tag_name.should == 'div'}
  @zone.span(:text, command).parent.link(:text, "删除").click
end
缺少变量!

改进:修改用例描述

# 用例描述
  ......
  当用户填写指令date并提交
  那么列表中出现date指令

  当用户在我的指令列表中删除date指令
When /用户在(.+)列表中删除(.+)指令/ do |title, command|
...
    
    
依然失败——
Modal dialog present (Selenium::WebDriver::Error::UnhandledAlertError)

改进:绕开confirm

# 驱动代码
When /用户在(.+)列表中删除(.+)指令/ do |title, command|
  @browser.execute_script(
    "window.confirm = function() {return true}"
  )
  span = nil
  @browser.wait_until do
    span = @browser.span(:text, "#{title}列表")
    span.exist?
  end
  @zone = span.           tap{|e| e.should_not be_nil}.
              parent.     tap{|e| e.tag_name.should == "h2" }.
                parent.   tap{|e| e.tag_name.should == 'div'}
  @zone.span(:text, command).parent.link(:text, "删除").click
end
考虑复用

改造以后

def get_list_zone_by_title title
  span = nil
  @browser.wait_until do
    span = @browser.span(:text, "#{title}列表")
    span.exist?
  end
  @zone = span.           tap{|e| e.should_not be_nil}.
            parent.       tap{|e| e.tag_name.should == "h2" }.
              parent.     tap{|e| e.tag_name.should == 'div'}
end

改造以后

When /用户在(.+)列表中删除(.+)指令/ do |title, command|
  @browser.execute_script(
    "window.confirm = function() {return true}"
  )
  get_list_zone_by_title(title).
    span(:text, command).parent.link(:text, "删除").click
end
Then /^系统显示(.+)列表$/ do |title|
  get_list_zone_by_title(title)
end

最后的用例

# language: zh-CN
功能: 基本运维
  系统运维的基本用例

场景: 用户通过系统进行一次运维操作的完整流程
  假如创建了一个应用,名字是center
  而且应用center包含机器localhost
  而且基础数据已经导入

  当lifu被任命为center的PE
  而且lifu通过浏览器登录系统
  那么首页包含center应用
  而且机器列表中包含localhost

  当用户开始管理我的指令
  那么系统显示我的指令列表

  当用户在其中执行创建
  那么页面出现新增指令模板

  当用户填写指令date并提交
  那么列表中出现date指令

  当用户在我的指令列表中删除date指令
  那么列表中不出现date指令

自动化测试 - 降低测试成本

测试与快速开发

测试与快速开发

总结:研发测试的要点

广告时间:AppOSS中的其它技术话题

Q & A

附录

agent技术选择

erlang

center技术选择

ruby/rails

开始

/

#