在 敏捷實踐(1) 中,出于介紹目的衔彻,測試用例實現的都相對簡單。
而實際上驗收標準測試用例也并不復雜,百分之八九十的動作無非是:
- 跳轉某個界面
- 在某個輸入框輸入一些內容
- 點擊某個按鈕
- 檢測某些文本
- 判斷是否處于某個界面
- 判斷是否有彈出框徐钠,提示文本
而這些動作(step), cucumber是可以用正則表達式進行標準化處理役首,在然后在各個測試用例中重用。
改進郵箱登錄故事AC需要用到的Step
$cat features/US004_login_by_email.feature
Feature: US_004 郵箱登錄
為了正常使用需要登錄身份的功能
作為一個已經用郵箱注冊過的用戶
我想要用郵箱和密碼登錄系統(tǒng)
@reset_driver
Scenario: AC_US004_02 登錄錯誤: 正確郵箱+錯誤密碼登錄
Given 我已經用郵箱 test_user@mytest.com 與密碼 test123 注冊過賬號
When 我在 "主頁面" 點擊 "登錄/注冊" 進入 "登錄頁面"
And 我在 "郵箱或手機" 輸入 "test_user@mytest.com"
And 我在 "密碼" 輸入 "b123456"
And 我按下按鈕 "登錄"
Then 我應當看到浮動提示 "用戶密碼不匹配"
.....
$ cat features/step_definitions/steps.rb
Given(/^我已經用郵箱 (.*) 與密碼 (.*) 注冊過賬號$/) do |email, password|
# sleep(1)
puts "DEBUG: email: #{email}"
puts "DEBUG: password: #{password}"
end
When(/^我在 "主頁面" 點擊 "登錄\/注冊" 進入 "登錄頁面"$/) do
# 等待主頁面就緒, 主頁面ID 為 home_page
wait { id('home_page') }
# 點擊 主頁面中的 '登錄/注冊' 按鈕显拜,按鈕ID為 btn_to_login
id('btn_to_login').click
# 檢查頁面跳轉到 登錄頁面衡奥, 登錄頁面ID為 page_login_account
wait { id('page_login_account') }
end
When(/^我在 "(.*?)" 輸入 "(.*?)"$/) do |input_field, input_value|
input_id = case input_field
when '郵箱或手機'
'input_username'
when '密碼'
'input_password'
else
'unknown'
end
input_box = id(input_id) # 定位指定的輸入框
input_box.clear # 清除原來的內容
input_box.type "#{input_value}\n" # 輸入新內容并回車
end
And(/^我按下按鈕 "登錄"$/) do
id('btn_login').click
end
Then(/^我應當看到浮動提示 "(.+)"$/) do |msg|
msg.strip!
puts "DEBUG: 期待 #{msg}"
wait { find(msg) }
end
Then(/^我應當到達 "主頁面"$/) do
wait { id('home_page') }
end
And(/^等待 (\d+) 秒后.*/) do |seconds|
sleep(seconds.to_i)
end
.
When 我在 "主頁面" 點擊 "登錄/注冊" 進入 "登錄頁面"
這個step的目的是我們要在某個界面,點擊 某個組件(按鈕或鏈接)远荠,跳轉到另一個頁面矮固。
When(/^我在 "主頁面" 點擊 "登錄\/注冊" 進入 "登錄頁面"$/) do
# 等待主頁面就緒, 主頁面ID 為 home_page
wait { id('home_page') }
# 點擊 主頁面中的 '登錄/注冊' 按鈕,按鈕ID為 btn_to_login
id('btn_to_login').click
# 檢查頁面跳轉到 登錄頁面譬淳, 登錄頁面ID為 page_login_account
wait { id('page_login_account') }
end
這里面有三個小動作:
- 確定當前是否在指定界面档址,是通過查找某個該界面中特有的組件id是否存在來判斷。
- 點擊 某個元素邻梆, 是通過查找到指定id的組件守伸,向它發(fā)送click信號。
- 判斷當前界面是否是指定界面, 同 1 浦妄。
因此每個界面尼摹,我們都設置一個能夠標識該界面的一個唯一的id见芹,便于我們識別當前的界面。
其次每個需要測試交互的組件蠢涝,都分配一個在該頁面唯一的id玄呛。
最后,為上面的硬編碼id改為對照方式. step改為正則匹配和二。
# When(/^我在 "主頁面" 點擊 "登錄\/注冊" 進入 "登錄頁面"$/) do
When(/^我在 "([^"]*)" 點擊 "(.*?)" 進入 "(.*?)"$/) do |location, button, dest|
VIEW_MAPPING = {
'主頁面' => 'home_page',
'密碼登錄頁面' => 'page_login_account'
}
BUTTON_MAPPING = {
'登錄/注冊' => 'btn_to_login'
}
location_id = VIEW_MAPPING[location]
button_id = BUTTON_MAPPING[button]
dest_id = VIEW_MAPPING[dest]
wait do
puts "DEBUG: #{location} => #{location_id}"
id(location_id)
end
wait do
puts "DEBUG: #{button} => #{button_id}"
id(button_id)
end
id(button_id).click
wait do
puts "DEBUG: #{dest} => #{dest_id}"
id(dest_id)
end
end
這個steps基本已經通用了徘铝。
只要feature中按照 我在 "AAA" 點擊 "BBB" 進入 "CCC"
這個格式寫測試步驟,都能匹配處理惯吕。
還有不足的的地方庭砍,每次新加按鈕或頁面id時,都需要進入該step中添加混埠,而且怠缸,其他地方無法重用這些映射定義;另一個是 "=>" 這種映射寫法钳宪,通不過Rubocop的語法檢測揭北。
繼續(xù)優(yōu)化
把 VIEW_MAPPING BUTTON_MAPPING 移到一個新文件,作為全局的常量(其實Ruby中并沒有真正的常量定義)吏颖。 并且搔体,反轉映射寫法以滿足Rubocop。
Cucumber會自動加載 features/step_definitions 中所有的文件半醉,無需自己手動require.
$ cat features/step_definitions/keyword_mapping.rb
##
# 1. 按所在頁面進行分類排序
# 2. 不同頁面存在相同關鍵字(button或input), id應相同
# 3. 在下面注釋中出現 '已被定義' 的前綴疚俱, 是為說明相同的關鍵字,在所處hash中已被定義缩多,不需要重新定義
VIEW_MAPPING = {
home_page: '主頁面',
page_more_races: '更多賽事頁面',
page_login_account: '密碼登錄頁面',
page_login_code: '驗證碼登錄頁面',
....
}.invert
BUTTON_MAPPING = {
# 主頁面
btn_to_login: '登錄/注冊',
btn_races_1: '第一個賽事',
btn_race_detail: '賽事詳情',
btn_order: '訂單',
btn_setup: '設置',
btn_account_security: '賬號安全',
btn_change_password: '修改密碼',
btn_more_races: '更多賽事',
# 密碼登錄頁面
btn_bar_right: '注冊',
btn_bar_left: '左上',
...
}.invert
改進界面輸入的步驟呆奕,模版化
When(/^我在 "(.*?)" 輸入 "(.*?)"$/) do |input_field, input_value|
input_id = case input_field
when '郵箱或手機'
'input_username'
when '密碼'
'input_password'
else
'unknown'
end
input_box = id(input_id) # 定位指定的輸入框
input_box.clear # 清除原來的內容
input_box.type "#{input_value}\n" # 輸入新內容并回車
end
由于一開始我們就使用了正則匹配,僅是在ID這塊做了條件硬編碼衬吆,該為映射處理:
When(/^我在 "(.*?)" 輸入 "(.*?)"$/) do |input, value|
input_id = INPUT_MAPPING[input]
puts "DEBUG: #{input} => #{input_id}"
input_box = nil
wait do
input_box = id(input_id)
end
input_box.clear # 定位指定的輸入框
input_box.type "#{value}\n". # 輸入新內容并回車
sleep 1 # 輸入完等待1秒梁钾,給模擬器留處理時間
end
為 features/step_definitions/keyword_mapping.rb 添加 INPUT_MAPPING
INPUT_MAPPING = {
# 密碼登錄頁面
input_username: '郵箱或手機',
input_password: '密碼',
# 驗證碼登錄頁面
input_phone: '手機號',
input_code: '驗證碼',
# 手機注冊頁面
# 已被定義:手機號,驗證碼
# 郵箱注冊頁面
input_email: '郵箱',
# 實名認證
input_real_name: '真實姓名',
input_id_card: '身份證號',
# 修改密碼
input_old_pwd: '舊密碼',
input_new_pwd: '新密碼',
# 賽事
input_keyword: '賽事關鍵字',
}.invert
繼續(xù)改進剩余step
And(/^我按下按鈕 "登錄"$/) do
id('btn_login').click
end
Then(/^我應當看到浮動提示 "(.+)"$/) do |msg|
msg.strip!
puts "DEBUG: 期待 #{msg}"
wait { find(msg) }
end
Then(/^我應當到達 "主頁面"$/) do
wait { id('home_page') }
end
==>
And(/^我[按下|點擊]+按鈕 "(.*?)"$/) do |button|
button_id = BUTTON_MAPPING[button]
wait do
puts "DEBUG: '#{button}' => #{button_id}"
element = id(button_id)
puts "DEBUG: got button: #{button_id}, #{element}"
end
id(button_id).click
end
Then(/^我應當看到浮動提示 "(.+)"$/) do |msg|
msg.strip!
puts "DEBUG: 期待 #{msg}"
wait { find(msg) }
end
Then(/^我應當到達 "([^"]*)"$/) do |location|
location_id = VIEW_MAPPING[location]
wait do
puts "DEBUG: #{location} => #{location_id}"
id(location_id)
end
end
備注:Cucumber的Step定義中逊抡, And Given Then When 這四個都是等價的語法糖姆泻,Then 定義的步驟,可以直接在其他步驟中使用冒嫡。
And(/^我應當看到浮動提示 "(.+)"$/) do |msg|
When(/^我應當看到浮動提示 "(.+)"$/) do |msg|
Then(/^我應當看到浮動提示 "(.+)"$/) do |msg|
Given(/^我應當看到浮動提示 "(.+)"$/) do |msg|
這四種都是等價的拇勃。
完整示例
當我們把常用的Step整理后, 基本上已經能滿足95%以上的測試用例編寫孝凌,就連產品方咆,設計也能愉快的按著寫AC了
這個step定義,基本可以直接拿去使用胎许,請叫我 紅領巾峻呛。
$ cat features/step_definitions/steps.rb
Given(/^我已經用郵箱 (.*) 與密碼 (.*) 注冊過賬號$/) do |email, password|
# sleep(1)
puts "DEBUG: email: #{email}"
puts "DEBUG: password: #{password}"
end
Given(/^我在 "([^"]*)" 點擊 "(.*?)" 進入 "(.*?)"$/) do |location, button, dest|
location_id = VIEW_MAPPING[location]
button_id = BUTTON_MAPPING[button]
dest_id = VIEW_MAPPING[dest]
wait do
puts "DEBUG: #{location} => #{location_id}"
id(location_id)
end
wait do
puts "DEBUG: #{button} => #{button_id}"
id(button_id)
end
id(button_id).click
wait do
puts "DEBUG: #{dest} => #{dest_id}"
id(dest_id)
end
end
When(/^我點擊 "([^"]*)" [進入|回到]+ "(.*?)"$/) do |button, dest|
button_id = BUTTON_MAPPING[button]
dest_id = VIEW_MAPPING[dest]
wait do
puts "DEBUG: #{button} => #{button_id}"
element = id(button_id)
puts "DEBUG: got button: #{button_id}, #{element}"
end
id(button_id).click
wait do
puts "DEBUG: #{dest} => #{dest_id}"
id(dest_id)
end
end
When(/^我[按下|點擊]+按鈕 "(.*?)"$/) do |button|
button_id = BUTTON_MAPPING[button]
wait do
puts "DEBUG: '#{button}' => #{button_id}"
element = id(button_id)
puts "DEBUG: got button: #{button_id}, #{element}"
end
id(button_id).click
end
When(/^點擊原生button "(.*?)"$/) do |button|
wait do
puts "DEBUG: #{button}"
element = id(button)
puts "DEBUG: got button: #{element}"
end
id(button).click
end
Given(/^我在 "(.*?)" 輸入 "(.*?)"$/) do |input, value|
input_id = INPUT_MAPPING[input]
puts "DEBUG: #{input} => #{input_id}"
input_box = nil
wait do
input_box = id(input_id)
end
input_box.clear
input_box.type "#{value}\n"
sleep 1
end
And(/^等待 (\d+) 秒后.*/) do |seconds|
sleep(seconds.to_i)
end
Then(/^我應當看到浮動提示 "(.+)"$/) do |msg|
msg.strip!
puts "DEBUG: 期待 #{msg}"
wait { find(msg) }
end
Then(/^我應當到達 "([^"]*)"$/) do |location|
location_id = VIEW_MAPPING[location]
wait do
puts "DEBUG: #{location} => #{location_id}"
id(location_id)
end
end
Given(/^我在 "([^"]*)"$/) do |location|
location_id = VIEW_MAPPING[location]
wait do
puts "DEBUG: #{location} => #{location_id}"
id(location_id)
end
end
Given(/.*\(創(chuàng)建數據\)$/) do |table|
params = table.hashes.first
ac = params.delete('ac').downcase
result = RemoteFactory.create(ac, params)
puts result.parsed_body
end
Given(/^我已使用 "([^"]*)" 登錄$/) do |value|
result = RemoteFactory.create('ac_us001', email: value)
puts result.parsed_body
puts '回到主頁'
id(BUTTON_MAPPING['回到主頁']).click if exists { id(BUTTON_MAPPING['回到主頁']) }
to_login = BUTTON_MAPPING['登錄/注冊']
wait do
puts '登錄/注冊'
id(to_login).click
end
email_input = INPUT_MAPPING['郵箱或手機']
password_input = INPUT_MAPPING['密碼']
login = BUTTON_MAPPING['登錄']
wait do
id(email_input)
end
id(email_input).clear
id(email_input).type "#{value}\n"
sleep 1
id(password_input).clear
id(password_input).type 'test123'
sleep 1
id(login).click
end
Given(/^退出登錄$/) do
puts '回到主頁'
id(BUTTON_MAPPING['回到主頁']).click if exists { id(BUTTON_MAPPING['回到主頁']) }
bar_left = BUTTON_MAPPING['左上']
wait do
puts '左上'
id(bar_left).click
end
setup = BUTTON_MAPPING['設置']
wait do
puts '設置'
id(setup).click
end
btn_exit = BUTTON_MAPPING['退出登錄']
wait do
puts '退出登錄'
id(btn_exit).click
end
id('確定').click
end
Given(/^清除數據$/) do
result = RemoteFactory.create('clear')
puts result.parsed_body
end
Then(/^我應當看到 "(.*?)" 顯示 "(.+)"$/) do |location, msg|
msg.strip!
puts "DEBUG: 期待 #{msg}"
location_id = INPUT_MAPPING[location]
wait {
puts "DEBUG: #{location} => #{location_id}"
id(location_id)
}
id(location_id).value.eql?(msg)
end
Then(/^"([^"]*)" 按鈕置灰罗售,無法點擊$/) do |button_text|
pending
end
Then(/^"([^"]*)" 按鈕無法點擊$/) do |button_text|
pending
end
Then(/^看到的 "([^"]*)" 應為 "([^"]*)"$/) do |text_name, value|
text_id = TEXT_MAPPING[text_name]
wait {
puts "DEBUG: #{text_name} => #{text_id}"
id(text_id)
}
target_value = id(text_id).value.strip
puts "DEBUG: #{target_value} eql? #{value}"
raise unless target_value.eql?(value)
end
Then(/^我能看到 "([^"]*)" 這些元素$/) do |elements|
elements.split(',').each do |element|
element
id(TEXT_MAPPING[element])
end
end
And(/^我在Alert中點擊 "(.+)"$/) do |button_text|
wait(1) do
tag('UIAAlert')
button(button_text).click
end
end
And(/^上傳圖片$/) do
id('btn_picker_image').click
wait(10) do
id('圖庫').click
end
wait(10) do
id('好').click
end
wait(10) do
id('Camera Roll').click
end
wait do
tag('XCUIElementTypeCell').click
end
sleep 1
end
Then(/^隱藏鍵盤$/) do
hide_keyboard('Return')
end
And(/^打印調試 (.+)$/) do |debug_name|
debug_name.strip!
if debug_name == 'page'
page
elsif debug_name == 'source'
source
end
end
Then(/^"([^"]*)" 應隱藏/) do |button_text|
pending
end
Then(/^我能看到 "(.+)"$/) do |msg|
msg.strip!
puts "DEBUG: 期待 #{msg}"
find(msg)
end
And(/^上滑$/) do
# swipe start_x: 300, start_y: 300, offest_x: 0, offset_y: -200
swipe direction: 'up'
end
And(/^下拉刷新$/) do
# swipe start_x: 300, start_y: 300, offest_x: 0, offset_y: 200
swipe direction: 'down'
sleep 1
end
Then(/^我應該找不到 "([^"]*)" 這些元素$/) do |elements|
elements.split(',').each do |element|
raise "存在#{element}這個元素" if exists { id(TEXT_MAPPING[element]) }
end
end
Then(/^應不存在 "([^"]*)"$/) do |button|
raise "存在#{button}" if exists { id(BUTTON_MAPPING[button]) }
end
Then(/^在 "([^"]*)" 可匹配到 "([^"]*)"$/) do |button, element|
button_id = BUTTON_MAPPING[button]
label = ''
wait do
puts "DEBUG: #{button} => #{button_id}"
label = id(button_id).label
puts "DEBUG: got label: #{label}"
end
raise "未匹配到#{element}" unless label.match(element)
end
Then(/^pending:.*$/) do
pending
end
完整用戶故事例子
US_001 郵箱注冊
Feature: US_001 郵箱注冊
作為一名非注冊用戶,我需要用郵箱號钩述,使得我可以完成注冊
@reset_driver
Scenario: AC_US001_01 注冊錯誤: 錯誤郵箱注冊
Given 我在 "主頁面"
When 我在 "主頁面" 點擊 "登錄/注冊" 進入 "密碼登錄頁面"
And 我點擊 "注冊" 進入 "手機注冊頁面"
And 我點擊 "使用郵箱注冊" 進入 "郵箱注冊頁面"
And 我在 "郵箱" 輸入 "aa@desh"
And 我在 "密碼" 輸入 "test123"
And 我按下按鈕 "完成"
Then 我應當看到浮動提示 "您的電子郵件格式不正確"
Scenario: AC_US001_02 下一步按鈕是灰色狀態(tài)
Given 我在 "郵箱" 輸入 ""
Then "完成" 按鈕置灰寨躁,無法點擊
Scenario: AC_US001_03 成功跳轉到 手機注冊頁面
Given 我在 "郵箱注冊頁面"
When 我按下按鈕 "使用手機注冊"
Then 我應當到達 "手機注冊頁面"
Scenario: AC_US001_04 成功跳轉到 密碼登錄頁面
Given 我點擊 "使用郵箱注冊" 回到 "郵箱注冊頁面"
When 我按下按鈕 "我已有賬號"
Then 我應當到達 "密碼登錄頁面"
Scenario: AC_US001_05 注冊錯誤 郵箱已注冊
Given 我已經用郵箱 test@gmail.com 注冊過賬號 (創(chuàng)建數據)
| ac | clear | email |
| AC_US001 | true | test@gmail.com |
When 我點擊 "注冊" 進入 "手機注冊頁面"
And 我點擊 "使用郵箱注冊" 回到 "郵箱注冊頁面"
And 我在 "郵箱" 輸入 "test@gmail.com"
And 我在 "密碼" 輸入 "test123"
And 我按下按鈕 "完成"
Then 我應當看到浮動提示 "郵箱已被使用"
# Scenario: AC_US001_06 備注:重復ac,與AC_US001_01重復
Scenario: AC_US001_07 注冊錯誤 密碼格式錯誤
Given 我在 "郵箱" 輸入 "test@gmail.com"
When 我在 "密碼" 輸入 "123456"
And 我按下按鈕 "完成"
Then 我應當看到浮動提示 "密碼格式不正確"
Scenario: AC_US001_08 注冊成功
Given 我在 "郵箱" 輸入 "test1@gmail.com"
When 我在 "密碼" 輸入 "test123456"
And 我按下按鈕 "完成"
Then 我應當到達 "主頁面"
US-006 忘記密碼-郵箱找回
Feature: US-006 忘記密碼-郵箱找回
作為一名忘記密碼的用戶,我需要用已認證的郵箱, 使得我能夠找回密碼
@reset_driver
Scenario: AC-US006-01 沒有任何輸入
Given 我在 "主頁面" 點擊 "登錄/注冊" 進入 "密碼登錄頁面"
And 我點擊 "忘記密碼" 進入 "忘記密碼頁面"
And 我按下按鈕 "使用郵箱找回密碼"
Then "下一步" 按鈕置灰牙勘,無法點擊
Scenario: AC-US006-02 沒有任何輸入 點擊獲取驗證碼
When 我按下按鈕 "獲取驗證碼"
Then 我應當看到浮動提示 "您的電子郵件格式不正確"
Scenario: AC-US006-03 錯誤格式的郵箱 點擊獲取驗證碼
And 我在 "郵箱" 輸入 "test@aa"
And 我按下按鈕 "獲取驗證碼"
Then 我應當看到浮動提示 "您的電子郵件格式不正確"
Scenario: AC-US006-04 錯誤格式的郵箱 點擊獲取驗證碼
And 我在 "郵箱" 輸入 "ricky@aa"
And 我按下按鈕 "獲取驗證碼"
Then 我應當看到浮動提示 "您的電子郵件格式不正確"
# Scenario: AC_US006_05 備注:重復ac,與AC_US006_07重復
Scenario: AC-US006-06 未輸入郵箱 但輸入了驗證碼
When 我在 "郵箱" 輸入 ""
And 我在 "驗證碼" 輸入 "aaa"
And 我按下按鈕 "獲取驗證碼"
Then 我應當看到浮動提示 "您的電子郵件格式不正確"
Scenario: AC-US006-07 輸入正確的郵箱 及正確的驗證碼
Given 我已經用郵箱 test@aa.com 注冊過賬號 (創(chuàng)建數據)
|ac |clear|email|
|AC_US006_07|true |test@aa.com|
And 我在 "郵箱" 輸入 "test@aa.com"
And 我按下按鈕 "獲取驗證碼"
And 我在 "驗證碼" 輸入 "123456"
And 隱藏鍵盤
And 我按下按鈕 "下一步"
Then 我應當到達 "輸入密碼頁面"
Scenario: AC-US006-08 輸入正確的郵箱 及正確的驗證碼
Given 我已經用郵箱 test@aa.com 注冊過賬號 (創(chuàng)建數據)
|ac |clear|email|
|AC_US006_08|true |test@aa.com|
And 我在 "密碼" 輸入 "123456"
And 我按下按鈕 "完成"
Then 我應當看到浮動提示 "密碼格式不正確"
Scenario: AC-US006-09 輸入正確的郵箱 及正確的驗證碼
Given 我按下按鈕 "回到主頁"
When 我在 "主頁面" 點擊 "登錄/注冊" 進入 "密碼登錄頁面"
And 我點擊 "忘記密碼" 進入 "忘記密碼頁面"
And 我按下按鈕 "使用郵箱找回密碼"
And 我在 "郵箱" 輸入 "test@aa.com"
And 我按下按鈕 "獲取驗證碼"
And 我在 "驗證碼" 輸入 "123456"
And 我按下按鈕 "下一步"
And 我在 "密碼" 輸入 "a123456"
And 我按下按鈕 "完成"
Then 我應當到達 "密碼登錄頁面"