敏捷實(shí)踐 (1) - 我們是如何自動(dòng)化App驗(yàn)收標(biāo)準(zhǔn)

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è)信息


appium-doctor-screenshot.png

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

run_appium.png

3.2 運(yùn)行測(cè)試用例

在 test_app 目錄中

$ cucumber features/US004_login_by_email.feature

run_cucumber_feature.png

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ě) (二)、(三)。。。。。。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容