为什么要实施自动化测试?
- ##### 应用系统日趋复杂的客观要求
一个不可避免的问题是,应用系统变得日趋复杂,并且造成了更大的风险。测试消耗的成本越来越高,花费的时间也越来越长。而成本与时间是有限的。为了在有限的成本和时间范围内,控制发布的软件产品质量,测试人员被要求在尽量短的时间内对软件进行足够的测试。 - ##### 避免重复测试的主观要求
迭代式开发过程:目前的软件开发或多或少的使用了迭代式开发过程,这就导致了我们需要进行一轮又一轮的测试。
回归测试:为了检查软件版本是否正确,或者当发现软件中出现一个或多个以前曾经被修复的缺陷时,不可避免的需要进行回归性的测试。
自动化测试实施失败的因素?
- 期望值过高。就像管理人员要求完全测试一样,期望100%的测试自动化,也同样是一个不显示的需求。
- 自动化的收益和成本:
成本 = 用例的开发 + 用例的维护
收益 = 重复运行次数 + 节约的时间
自动化测试的成长过程
Testing often begins as freestyle, expands toward scripted.
测试总是从自由式的探索开始,朝着脚本化方向扩张。
![]()
自动化测试的应用时机
冒烟测试
这一术语源自硬件行业。对一个硬件或硬件组件进行更改或修复后,直接给设备加电。如果没有冒烟,则该组件就通过了测试。在软件中,“冒烟测试”这一术语描述的是在将代码更改嵌入到产品的源树中之前对这些更改进行验证的过程。在检查了代码后,冒烟测试是确定和修复软件缺陷的最经济有效的方法。冒烟测试设计用于确认代码中的更改会按预期运行,且不会破坏整个版本的稳定性。回归测试
回归测试是指修改了旧代码后,重新进行测试以确认修改没有引入新的错误或导致其他代码产生错误。自动回归测试将大幅降低系统测试、维护升级等阶段的成本。回归测试作为软件生命周期的一个组成部分,在整个软件测试过程中占有很大的工作量比重,软件开发的各个阶段都会进行多次回归测试。在渐进和快速迭代开发中,新版本的连续发布使回归测试进行的更加频繁,而在极端编程方法中,更是要求每天都进行若干次回归测试。因此,通过选择正确的回归测试策略来改进回归测试的效率和有效性是很有意义的。
重头戏:测试方案设计
- 自动化测试的功能方案设计的流程:
操作对象→对象属性→功能实现→结果验证 - 自动化测试的功能方案设计操作用例图:
Cucumber
开源项目地址:github/cucumber-ruby
Installation
you can run:
gem install cucumber-rails -v 0.3.2
// gem install cucumber
If you are using Bundler, just add it to your Gemfile:
gem "capybara", "1.1.1"
gem "cucumber", "1.1.0"
gem "cucumber-rails", "0.3.2"
Then initialize a features directory:
cucumber --init
Running
To see the full list of options:
cucumber --help
Otherwise, to run all features:
cucumber
If working on a fresh Rails project, first set up the (empty) database:
rake db:migrate
(Otherwise Cucumber fails with the error no such file to load – YourProjectName/db/schema.rb.)
Then run the features:
rake cucumber
Watir WebDriver
开源项目地址:github/watir
API:api
浏览器支持:
Firefox, Chrome, IE,Safari
I could make uo lots of quotes about how good it is, but I don’t need to, it’s that good.
Getting Started
// gem install watir-webdriver
gem 'watir', '~> 6.7'
gem install watir
Getting Going
require 'watir-webdriver'
b = Watir::Browser.new
b.goto 'bit.ly/watir-webdriver-demo'
b.text_field(:id => 'entry_1000000').set 'your name'
b.select_list(:id => 'entry_1000001').select 'Ruby'
b.select_list(:id => 'entry_1000001').selected? 'Ruby'
b.button(:name => 'submit').click
b.text.include? 'Thank you'
自动化测试
Cucumber和Watir的关系
Cucumber是一个BDD框架;Watir是一个Web Driver。两者都是由Ruby编写的。
单独使用Cucumber
对于Cucumber的执行过程可以参考文章:行为驱动开发:Cucumber的目录结构和执行过程。
由于Cucumber和Watir没有必然联系,因此两者均可单独使用,下面就让我们用Cucumber写一个简单的单元测试。定义一个需要测试的Calculator类如下:
class Calculator
def add num1, num2
num1 + num2
end
end
class Calculator
def add num1, num2
num1 + num2
end
end
用于测试的Calculator类的add方法放入feature文件如下:
Feature: Unit testforCalculator
Scenario: Add two numbers
Given I have a calculator created
When I add '3' and '5'
Then I should get the result of '8'
对应的step文件为:
require File.join(File.dirname(__FILE__), "../calculator")
require 'rspec'
Given /^I have a calculator created$/ do
@calculator = Calculator.new
end
When /^I add '([^"]*)' and '([^"]*)'$/ do |num1, num2|
@result = @calculator.add(num1.to_i, num2.to_i)
end
Then /^I should get the result of '([^"]*)'$/ do |expected_result|
@result.should == expected_result.to_i
end
在以上的step文件中,第1,2行分别require了自定义的Calculator类和rspec(用于assertion,参考第13行的‘should’),第5行新建了一个@calculator实例变量,第9行完成两个数相加(3 + 5),第13行为测试断言。运行cucumber命令,输出结果如下:
Feature: Unit testforCalculator
Scenario: Add two numbers # features/Calculator.feature:4
Given I have a calculator created # features/step_definitions/calculator_step.rb:4
WhenI add '3' and '5' # features/step_definitions/calculator_step.rb:8
ThenI should get the result of '8' # features/step_definitions/calculator_step.rb:12
1 scenario (1 passed)
3 steps (3 passed)
100m 0.002s
单独使用Watir
接下来用Watir完成Google搜索功能,新建watir_google.rb文件并加入以下内容:
require 'watir_webdriver'
browser = Watir::Browser.new :chrome
browser.goto "www.google.com/"
browser.text_filed(:name => "q").set "Why I am so handsome ?"
browser.button(:name => "btnG").click
当执行到第2行时,一个浏览器窗口会自动打开,之后访问google主页(第三行),设置搜索关键字“why I am so handsome ?”,最后点击搜索按钮,单独运行Watir成功。
用Cucumber + Watir写自动化测试
由上文可知,Cucumber只能用接近自然语言的方式描述业务行为,而Watir则只能对人为操作页面动作进行模拟。当使用Cucumber + Watir实现自动化测试时,通过正则表达式匹配将Cucumber的行为描述与Watir的网页操作步骤耦合起来即可。同样以Google搜索为例,搜索关键字后,我们希望获得搜索结果,先用Cucumber完成搜索行为描述:
Feature:GoogleSearch
Scenario: search for keyword
Given I am on google home page
When I search for 'why I am so handsome ?'
Then I should be able to view the search result of 'why I am so handsome ?'
对应的Watir代码如下:
require "rubygems"
require "watir-webdriver"
require "rspec"
Given /^I am on google home page$/ do
@browser = Watir::Browser.new :chrome
@browser.goto("www.google.com")
end
When/^I search for'([^"]*)'$/ do |search_text|
@browser.text_field(:name => "q").set(search_text)
@browser.button(:name => "btnK").click
end
Then /^I should be able to view the search result of '([^"]*)'$/ do |result_text|
@browser.text.should include(result_text)
end
运行cucumber,一个新的浏览器被打开,显示结果测试通过
自动化测试的设计模式 :Page对象
在上面的例子中,所有的step均直接对@browser对象进行操作,对于这样简单的例子并无不妥,但是对于多页面的网站来说显得过于混乱,既然是面向对象,我们就希望将不同的页面也用对象封装,于是引入Page对象,即可完成对页面的逻辑封装,又实现了分层复用。此时位于high-level的Cucumber文件无需改动,我们只需要定义一个Page对象来封装Google页面:
require "rubygems"
require "watir-webdriver"
require "rspec"
class GooglePage
def initialize
@browser = Watir::Browser.new :chrome
@browser.goto("www.google.com")
end
def search str
@browser.text_filed(:name => "q").set(str)
@browser.button(:name => "btn").click
end
def has_text text
@browser.text.should include(text)
end
end
相应的step文件需要做相应的修改:
require File.join(File.dirname(_FILE_), "google-page")
Given /^I am on google home page$/ do
@page.GooglePage.new
end
When /^I search for '([^"]*)'$/ do |search_text|
@page.search search_text
end
Then /^I should be able to view the search result of '([^"]*)'$/ do |reault_text|
@page.has_text result_text
end
运行cucumber,一个新的浏览器被打开,显示结果与上次结果相同。
加入用户角色
既然是行为驱动,既然是模拟用户实际操作,name直接对Page对象进行操作也显得不够,于是我们引入用户角色User,对于拥有多种用户角色的网站来说特别实用。加入User对象之后,step文件中不再出现对Page对象的直接引用,而是在User对象的行为方法中进行引用,定义User对象如下:
require File.join(File,dirname(_FILE_), "google-page")
class User
def initialize
@browser = Watir::Browser.new :chrome
end
def visit_google
@page = GooglePage.new(@browser)
end
def search_text text
@page.search text
end
def assert_text_exist text
@page.has_text text
end
end
feature文件保持不变,在step文件用User代替Page:
require File.join(File.dirname(_FILE_), "user")
Given /^I am on google home page$/ do
@user = User.new
@user.visit_google
end
When /^I search for '([^"])'$/ do |search_text|
@user.search_text search_text
end
Then /^I should be able to view the search result of '([^"])'$/ do |result_text|
@user.assert_text_exist result_text
end
运行cucumber,一个新的浏览器被打开,显示结果与上次结果相同。
对于拥有多个用户角色的网站,比如有admin,user等,可分别对这些角色定义相应的对象,再在step文件中应用这些对象即可。
用ruby的Module来封装不同的行为功能
对于单个用户来说,比如网上购物网站的customer,既要购物操作,又能修改自己的profile,此时为了对这些不同的逻辑功能进行组织,可引用ruby中的Module来进行分装,即将customer的不同行为功能模块封装在不同的Module中,然后customer对象中include这些Module。为了简单起见,仍然用Google搜索进行演示,此时可将搜索功能加入到Module中,定义搜索module如下:
module SearchBehavior
def visit_google
@page = GooglePage.new(@browser)
end
def search_text text
@page.search text
end
def assert_text_exist text
@page.has_text text
end
end
在User对象中include这个Module:
require File.join(File.dirname(__FILE__), "search-behavior")
class User
require 'search_behavior'
def initialize
@browser = Watir::Browser.new :chrome
end
end
对step文件和feature文件均不用修改,运行cucumber,一个新的浏览器被打开,显示结果与上次结果相同。
总结
我们可以在Cucumber对应的step文件中直接访问Watir的API,这样的确也能达到测试目的,但这样的缺点在于缺少设计,于是我们引入Page对象来封装不同的页面,引入用户角色管理不同的用户行为,再引入Module来组织不同的功能模块,最后重构成了一个简单实用的自动化测试框架。
Web对象的识别-Watir-WebDriver
input-输入框
button-按钮
select-下拉框
link-链接
click-点击
radio-单选
checkbox-多选
send_key :enter-模拟键盘
页面元素的捕获
页面元素
attribute_value
获取当前空间的属性Value = ie.link(:id=>'xxx').attribute_value("href")
rand_select
随机选择select list中的某一项ie.select_list(:name=>’’).rand_select
popupwin
端架弹窗上的‘确定’按钮ie.popupwin.button(:name=>"确定").click
sikuli_image
点击图片控件ie.sikuli_image(:image=>"1.png").click ie.sikuli_image(:image=>"1.png;2.png").click#可以指定多张图片来识别
double_click
双击事件ie .sikuli_image(:image=>"1.png").double_click
right_click
右击事件exist?
判断用户元素是否存在edit = ie.text_field(:name,”username”)
if edit.exist?() #The highlighted
edit.flash
ie.text_field(:name, “password”).set(pwd)
ie.button(:class, “x-login-submit”).click
end
endText Fields
require 'watir-webdriver' b = Watir::Browser.start 'bit.ly/watir-webdriver-demo' t = b.text_field :id => 'entry_0' t.exists? t.set 'your name' t.value
Select LIsts - Combos
require 'watir-webdriver' b = Watir::Browser.start 'bit.ly/watir-webdriver-demo' s = b.select_list :id => 'entry_1' s.select 'Ruby' s.selected_options # 返回选中的选项 puts b.select(:name, 'sex').selected_options
Radios
require 'watir-webdriver' b = Watir::Browser.start 'bit.ly/watir-webdriver-demo' r = b.label(:text => 'What is ruby?').parent.radio :value => 'A gem' r.exists? r.set r.set?
Checkboxes
require 'watir-webdriver' b = Watir::Browser.start 'bit.ly/watir-webdriver-demo' c = b.label(:text => 'What versions of ruby?').parent.checkbox :value => '1.9.2' c.exists? c.set c.set? # 选择checkbox b.checkbox(:name, 'check_me').set true # 清除选择 b.checkbox(:name, 'check_me').set false
Buttons
require 'watir-webdriver' b = Watir::Browser.start 'bit.ly/watir-webdriver-demo' btn = b.button :value, 'Submit' btn.exists? btn.click
Links
require 'watir-webdriver' b = Watir::Browser.start 'bit.ly/watir-webdriver-demo' l = b.link :text => 'Google Docs' l.exists? l.click
Div & Spans
require 'watir-webdriver' b = Watir::Browser.start 'bit.ly/watir-webdriver-demo' d = b.div :class => 'ss-form-desc ss-no-ignore-whitespace' d.exists? d.text s = b.span :class => 'ss-powered-by' s.exists? s.text
实例
按钮
ie.button(:name=>"",:id=>"",:index=>n,:type=>"").click
ie.button(:name=>"",:id=>"",:index=>n,:type=>"").doclick
输入框
ie.text_field(:name=>"").set "变量"
ie.text_field(:name=>"").value 取text_field值不是用text而是value!
下拉框
ie.select_list(:name=>"").select "下拉框值"
ie.select_list(:name=>"").select "#1" #表示第一项内容
ie.select_list(:name=>"").rand_select
ie.select_list(:name=>"").getSelectedItems|getAllContents->返回Array
单选框
ie.radio(:id=>"",:name=>"",:index=>n).set(选中当前radio)
ie.radio(:id=>"",:name=>"",:index=>n).clear(取消选中当前radio)
ie.div(:class=>"iradio_minimal-blue checked").radios[1]
复选框
ie.check_box(:id=>"",:name=>"",:index=>n).set(true|false)(true表示选中,false表示不选中)
ie.check_box(:id=>"",:name=>"",:index=>n).clear(取消选中当前checkbox)
链接
ie.link(:text=>"").click/doclick
ie.link(:text=>"").href(返回当前link指向的链接)
cell (TD标签,用时一般需要先找到上层控件如table、div等)
ie.table(:class=>"",:index=>n).cell(:class=>"",:index=>n).text
ie.table(:index=>n).rows 行 列 .text (行、列从1开始)
ie.div(:class=>"",:index=>n).cell(:class=>"",:index=>n).text
span
ie.table(:id=>"").span(:class=>"").text
弹出框
ie.popupwin.get_static_text (返回当前提示框的文本)
ie.popupwin.button(:name=>"确定").click/doclick (前一个点击按钮必须用doclick)
ie.file_dialog(:index=>1/2).set_file(file_path_download,true) (保存文件的弹出窗口)
alert
if b.alert.exists?
puts "ok"
else
puts "no"
end
puts b.alert.text
#b.alert.ok
b.alert.close
b.close
图片
ie.image(:src=>/word3a_nor.gif/).click/doclick
back
后退
ie.back
forward
前进
ie.forward
refresh
刷新页面
ie.refresh
在Watir-WebDriver中处理frame是非常简单的,就跟处理其他页面元素一样:
b.frame(:id => "content_ifr").send_keys "hello world"