1. 問(wèn)題背景
Scrum敏捷過(guò)程對(duì)交付質(zhì)量有著嚴(yán)格的要求:零缺陷的增量發(fā)布。
要做到這一點(diǎn)意味著每個(gè)迭代發(fā)布都必須對(duì) AC (Acceptance Criteria - 驗(yàn)收標(biāo)準(zhǔn)) 進(jìn)行全回歸,隨著迭代中增量交付的增加,全回歸的工作量與所需的時(shí)間也隨之增加。
我們?cè)谧鯝PP開(kāi)發(fā)過(guò)程中,app開(kāi)發(fā)人員 告訴我這app自動(dòng)化測(cè)試搞不了,測(cè)試人員 也說(shuō),不好搞,很麻煩,而且還得靠錄屏,blablabla.......
結(jié)果測(cè)試工作往往只能依賴人手進(jìn)行測(cè)試,AC要靠人手來(lái)全回歸測(cè)試,就會(huì)發(fā)生這樣一種情景:
通常一個(gè)迭代的周期為兩周10個(gè)工作日。
- 迭代1, 計(jì)劃完成5個(gè)功能,相應(yīng)的AC也就不多,如果1天就能全部回歸,那么開(kāi)發(fā)有9天時(shí)間來(lái)實(shí)現(xiàn)功能。
- 迭代2, 計(jì)劃完成4個(gè)功能,相應(yīng)的AC加上迭代1的AC,需要2天才能全部回歸,那么開(kāi)發(fā)有8天的時(shí)間來(lái)實(shí)現(xiàn)功能。
- 迭代3,計(jì)劃完成3個(gè)功能,相應(yīng)的AC加上兩個(gè)迭代的AC,可能需要3天才能全部回歸,那么開(kāi)發(fā)有7天的時(shí)間來(lái)實(shí)現(xiàn)功能。
- ...
每次迭代AC全回歸的所需的時(shí)間在不斷增加,而能用于開(kāi)發(fā)的時(shí)間不斷在受到擠壓而減少,能交付的功能也就不斷在減少。
- 要質(zhì)量就沒(méi)速度,如此還能敏捷的起來(lái)嗎?
- 不少團(tuán)隊(duì)(包括我們)采用了犧牲交付質(zhì)量(僅部分回歸甚至無(wú)回歸)來(lái)確保開(kāi)發(fā)進(jìn)度,這樣做既不符合敏捷過(guò)程的價(jià)值觀,實(shí)際上也沒(méi)有達(dá)到交付目標(biāo)。
- 還有一個(gè)土豪的做法,就是增配測(cè)試。
我們一直在探索這個(gè)問(wèn)題的解決方案,既想確保增量發(fā)布的速度,又想擁有交付質(zhì)量。只能想辦法把AC自動(dòng)化起來(lái),全回歸就不需要占用太多人手,而且還能隨時(shí)進(jìn)行,所需要的時(shí)間也大大縮短。
2. 解決方案
在2017年1月份的ScrumMaster培訓(xùn)課中,聊起App自動(dòng)化測(cè)試有什么工具,便有小伙伴提了一下Appium?;貋?lái)后便查資料研究,發(fā)現(xiàn)有兩個(gè)非常不錯(cuò)的自動(dòng)化工具,一個(gè)就是Appium,另一個(gè)是阿里系的Macaca。這里不得不說(shuō),Macaca做的很不錯(cuò),javascript寫(xiě)測(cè)試也相對(duì)容易上手。
然而,做了對(duì)比后,我決定還是選用Appium。原因很簡(jiǎn)單,Appium支持Ruby和Cucumber,我們的后端正好是用RubyOnRails寫(xiě)的,后端開(kāi)發(fā)人員已經(jīng)在寫(xiě)后端過(guò)程中積累了許多編寫(xiě)自動(dòng)化測(cè)試用例的經(jīng)驗(yàn)。正好也可以來(lái)寫(xiě)app的自動(dòng)化測(cè)試。
2.1 環(huán)境準(zhǔn)備
以下內(nèi)容的運(yùn)行環(huán)境為 MacOS 10.12 + Ruby 2.3.3 + NodeJS 7.x。
至于如何安裝 Ruby 和 NodeJS,這個(gè)應(yīng)當(dāng)為基本技能,請(qǐng)自行搞定。
請(qǐng)不要問(wèn)我如何在Windows上去搭建環(huán)境的有關(guān)問(wèn)題,我只會(huì)建議要么白蘋(píng)果,要么黑蘋(píng)果,要么Ubuntu (不支持iOS)。要么走您。
由于npmjs.org rubygems.org都在國(guó)外,訪問(wèn)速度慢且不穩(wěn)定,建議先搭好梯子,或者改用國(guó)內(nèi)的源。
2.1.1 Appium 1.6.3 安裝
npm install -g appium
npm install -g appium-doctor
# ios還需要安裝兩個(gè)工具 ios-deploy 用于安裝app到真機(jī);Carthage 用于安裝app到模擬器
brew install carthage
brew install ios-deploy
# brew 是個(gè)什么鬼,如何安裝的?沒(méi)什么技術(shù)含量,請(qǐng)自行baidu
# 完成后可以運(yùn)行appium-doctor診斷一下環(huán)境是否正常
# 如果沒(méi)有android需求的,可以不用管 JAVA_HOME 與 android adb 等錯(cuò)誤
appium-doctor
一切正常會(huì)得到類似這個(gè)信息

2.1.2 安裝 Appium 的 Ruby 驅(qū)動(dòng),和appium ruby console
gem install appium_lib
gem install appium_console
至此,環(huán)境準(zhǔn)備工作已經(jīng)完成。
2.2 編寫(xiě)自動(dòng)化測(cè)試用例
測(cè)試用例采用Cucumber方式編寫(xiě),F(xiàn)eature 對(duì)應(yīng) 用戶故事,Scenario 對(duì)應(yīng) AC。
一個(gè)Feature包含多個(gè)Scenarios。
一個(gè)用戶故事有多個(gè) AC驗(yàn)收標(biāo)準(zhǔn)。
至于Cucumber, 有空我會(huì)再寫(xiě)一個(gè)文章。目前請(qǐng)先自行baidu之。
2.2.1 創(chuàng)建目錄
mkdir app_test
cd app_test
# 創(chuàng)建測(cè)試用例目錄
mkdir features
# 創(chuàng)建測(cè)試用例步驟目錄
mkdir features/steps
# 創(chuàng)建支持目錄
mkdir features/support
2.2.2 準(zhǔn)備Ruby Cucumber的Gemfile
$ cat Gemfile
source 'https://gems.ruby-china.org'
gem 'appium_lib', '~> 9.3.0'
gem 'rest-client', '~> 1.6.7'
gem 'rspec', '~> 2.14.1'
gem 'cucumber', '~> 1.3.15'
gem 'rspec-expectations', '~> 2.14.5'
gem 'spec', '~> 5.3.4'
gem 'sauce_whisk', '~> 0.0.13'
gem 'test-unit', '~> 2.5.5' # required for bundle exec ruby xunit_android.rb
安裝gem包
bundle install
2.2.3 cucumber env
$ cat features/support/env.rb
# This file provides setup and common functionality across all features. It's
# included first before every test run, and the methods provided here can be
# used in any of the step definitions used in a test. This is a great place to
# put shared data like the location of your app, the capabilities you want to
# test with, and the setup of selenium.
require 'rspec/expectations'
require 'appium_lib'
require 'cucumber/ast'
require 'sauce_whisk'
# Create a custom World class so we don't pollute `Object` with Appium methods
class AppiumWorld
end
# Load the desired configuration from appium.txt, create a driver then
# Add the methods to the world
caps = Appium.load_appium_txt file: File.expand_path('./', __FILE__), verbose: true
Appium::Driver.new(caps)
Appium.promote_appium_methods AppiumWorld
World do
AppiumWorld.new
end
# 結(jié)束時(shí),推出驅(qū)動(dòng)
at_exit { $driver.driver_quit }
# 在運(yùn)行標(biāo)有@reset_driver的Scenario前重啟驅(qū)動(dòng)
Before("@reset_driver") do
$driver.restart
end
2.2.4 appium配置文件 appium.txt
關(guān)于appium.txt配置說(shuō)明可以參考文檔 appium-server-capabilities
$ cat features/support/appium.txt
[caps]
platformName = "ios"
deviceName = "iPhone 6s"
platformVersion = "10.2"
app = '../ios/build/Build/Products/Debug-iphonesimulator/PuKe.app'
automationName = 'XCUITest'
language = 'zh'
locale = 'zh_CN'
[appium_lib]
sauce_username = false
sauce_access_key = false
2.3 編寫(xiě)測(cè)試用例
每一用戶故事的測(cè)試用例編寫(xiě)為一個(gè)feature。
2.3.1 編寫(xiě)郵箱登陸用戶故事的AC
郵箱登陸用戶故事只是登陸故事中的其中一個(gè), 故事ID US004
$cat features/US004_login_by_email.feature
Feature: US_004 郵箱登錄
為了正常使用需要登錄身份的功能
作為一個(gè)已經(jīng)用郵箱注冊(cè)過(guò)的用戶
我想要用郵箱和密碼登錄系統(tǒng)
@reset_driver
Scenario: AC_US004_02 登錄錯(cuò)誤: 正確郵箱+錯(cuò)誤密碼登錄
Given 我已經(jīng)用郵箱 test_user@mytest.com 與密碼 test123 注冊(cè)過(guò)賬號(hào)
When 我在 "主頁(yè)面" 點(diǎn)擊 "登錄/注冊(cè)" 進(jìn)入 "登錄頁(yè)面"
And 我在 "郵箱或手機(jī)" 輸入 "test_user@mytest.com"
And 我在 "密碼" 輸入 "b123456"
And 我按下按鈕 "登錄"
Then 我應(yīng)當(dāng)看到浮動(dòng)提示 "用戶密碼不匹配"
Scenario: AC_US004_03 登錄錯(cuò)誤: 沒(méi)有輸入用戶名和密碼
Given 我已經(jīng)用郵箱 test_user@mytest.com 與密碼 test123 注冊(cè)過(guò)賬號(hào)
And 我在 "郵箱或手機(jī)" 輸入 ""
When 我在 "密碼" 輸入 ""
And 我按下按鈕 "登錄"
Then 我應(yīng)當(dāng)看到浮動(dòng)提示 "請(qǐng)?zhí)顚?xiě)完整"
Scenario: AC_US004_04 登錄錯(cuò)誤: 輸入用戶名沒(méi)有輸入密碼
Given 我已經(jīng)用郵箱 test_user@mytest.com 與密碼 test123 注冊(cè)過(guò)賬號(hào)
And 我在 "郵箱或手機(jī)" 輸入 "test_user@mytest.com"
And 我在 "密碼" 輸入 ""
When 我按下按鈕 "登錄"
Then 我應(yīng)當(dāng)看到浮動(dòng)提示 "請(qǐng)?zhí)顚?xiě)完整"
Scenario: AC_US004_01 正常郵箱+密碼登錄
Given 我已經(jīng)用郵箱 test_user@mytest.com 與密碼 test123 注冊(cè)過(guò)賬號(hào)
When 我在 "郵箱或手機(jī)" 輸入 "test_user@mytest.com"
And 我在 "密碼" 輸入 "test123"
And 我按下按鈕 "登錄"
Then 我應(yīng)當(dāng)?shù)竭_(dá) "主頁(yè)面"
And 等待 2 秒后退出
2.3.2 為feature編寫(xiě)相應(yīng)的steps
Scenario 中的每個(gè) Given When Then And 都在steps.rb 中有對(duì)應(yīng)的定義。
$ cat features/step_definitions/steps.rb
# These are the 'step definitions' which Cucumber uses to implement features.
#
# Each step starts with a regular expression matching the step you write in
# your feature description. Any variables are parsed out and passed to the
# step block.
#
# The instructions in the step are then executed with those variables.
#
# In this example, we're using rspec's assertions to test that things are happening,
# but you can use any ruby code you want in the steps.
#
# The '$driver' object is the appium_lib driver, set up in the cucumber/support/env.rb
# file, which is a convenient place to put it as we're likely to use it often.
# This is a different use to most of the examples; Cucumber steps are instances
# of `Object`, and extending Object with Appium methods (through
# `promote_appium_methods`) is a bad idea.
#
# For more on step definitions, check out the documentation at
# https://github.com/cucumber/cucumber/wiki/Step-Definitions
#
# For more on rspec assertions, check out
# https://www.relishapp.com/rspec/rspec-expectations/docs
Given(/^我已經(jīng)用郵箱 (.*) 與密碼 (.*) 注冊(cè)過(guò)賬號(hào)$/) do |email, password|
# sleep(1)
puts "DEBUG: email: #{email}"
puts "DEBUG: password: #{password}"
end
When(/^我在 "主頁(yè)面" 點(diǎn)擊 "登錄\/注冊(cè)" 進(jìn)入 "登錄頁(yè)面"$/) do
# 等待主頁(yè)面就緒, 主頁(yè)面ID 為 home_page
wait { id('home_page') }
# 點(diǎn)擊 主頁(yè)面中的 '登錄/注冊(cè)' 按鈕,按鈕ID為 btn_to_login
id('btn_to_login').click
# 檢查頁(yè)面跳轉(zhuǎn)到 登錄頁(yè)面, 登錄頁(yè)面ID為 page_login_account
wait { id('page_login_account') }
end
When(/^我在 "(.*?)" 輸入 "(.*?)"$/) do |input_field, input_value|
input_id = case input_field
when '郵箱或手機(jī)'
'input_username'
when '密碼'
'input_password'
else
'unknown'
end
input_box = id(input_id) # 定位指定的輸入框
input_box.clear # 清除原來(lái)的內(nèi)容
input_box.type "#{input_value}\n" # 輸入新內(nèi)容并回車
end
And(/^我按下按鈕 "登錄"$/) do
id('btn_login').click
end
Then(/^我應(yīng)當(dāng)看到浮動(dòng)提示 "(.+)"$/) do |msg|
msg.strip!
puts "DEBUG: 期待 #{msg}"
wait { find(msg) }
end
Then(/^我應(yīng)當(dāng)?shù)竭_(dá) "主頁(yè)面"$/) do
wait { id('home_page') }
end
And(/^等待 (\d+) 秒后.*/) do |seconds|
sleep(seconds.to_i)
end
3. 運(yùn)行測(cè)試用例
3.1 運(yùn)行 Appium
運(yùn)行測(cè)試用例前必須先啟動(dòng)appium,
新打開(kāi)一個(gè)命令行窗口
$ appium

3.2 運(yùn)行測(cè)試用例
在 test_app 目錄中
$ cucumber features/US004_login_by_email.feature

3.3 觀看測(cè)試用例的運(yùn)行錄屏
http://v.youku.com/v_show/id_XMjUyMTkwODUzMg==.html
3.4 示例代碼參考
https://github.com/appium/sample-code/
4. 總結(jié)
拋開(kāi)復(fù)雜的appium、cucumber steps,發(fā)現(xiàn)沒(méi),測(cè)試用例是不是很容易理解?
Scenario: AC_US004_01 正常郵箱+密碼登錄
Given 我已經(jīng)用郵箱 test_user@mytest.com 與密碼 test123 注冊(cè)過(guò)賬號(hào)
When 我在 "郵箱或手機(jī)" 輸入 "test_user@mytest.com"
And 我在 "密碼" 輸入 "test123"
And 我按下按鈕 "登錄"
Then 我應(yīng)當(dāng)?shù)竭_(dá) "主頁(yè)面"
And 等待 2 秒后退出
像上面的AC,就算是非技術(shù)人員也能分分鐘寫(xiě)出來(lái),我們的產(chǎn)品經(jīng)理、UI設(shè)計(jì)師都在參與編寫(xiě)AC,這就是我們選擇cucumber ruby的根本原因。
本來(lái)打算一篇寫(xiě)完的,寫(xiě)著寫(xiě)著,發(fā)現(xiàn)要寫(xiě)的內(nèi)容太多了,決定還是寫(xiě)成一個(gè)系列:
- 首先、用Appium來(lái)測(cè)試,也存在一些坑要填。
- 上面的feature和steps,為了演示好理解,做了相當(dāng)?shù)暮?jiǎn)化。實(shí)際上,我們的steps經(jīng)過(guò)一些重構(gòu),變得很通用靈活,后續(xù)放出來(lái)。
- Appium的配置與基本指令
- 輔助神器 Appium Ruby Console (ARC) 的使用
- 如何用Cucumber編寫(xiě)自動(dòng)化測(cè)試
- React-Native類型的APP自動(dòng)化有哪些坑需要繞過(guò)
有空的時(shí)候再接著寫(xiě) (二)、(三)。。。。。。