测试 敏捷实践 (1) - 我们是如何自动化 App 验收标准

edwardzhou · 2017年08月07日 · 最后由 edwardzhou 回复于 2017年08月07日 · 8097 次阅读

简书:http://www.jianshu.com/p/fd17c1bb6806

1. 问题背景

Scrum 敏捷过程对交付质量有着严格的要求:零缺陷的增量发布

要做到这一点意味着每个迭代发布都必须对 AC (Acceptance Criteria - 验收标准) 进行全回归,随着迭代中增量交付的增加,全回归的工作量与所需的时间也随之增加。

我们在做 APP 开发过程中,app 开发人员 告诉我这 app 自动化测试搞不了,测试人员 也说,不好搞,很麻烦,而且还得靠录屏,blablabla.......

结果测试工作往往只能依赖人手进行测试,AC 要靠人手来全回归测试,就会发生这样一种情景:

通常一个迭代的周期为两周 10 个工作日。

  • 迭代 1,计划完成 5 个功能,相应的 AC 也就不多,如果 1 天就能全部回归,那么开发有 9 天时间来实现功能。
  • 迭代 2,计划完成 4 个功能,相应的 AC 加上迭代 1 的 AC,需要 2 天才能全部回归,那么开发有 8 天的时间来实现功能。
  • 迭代 3,计划完成 3 个功能,相应的 AC 加上两个迭代的 AC,可能需要 3 天才能全部回归,那么开发有 7 天的时间来实现功能。
  • ...

每次迭代 AC 全回归的所需的时间在不断增加,而能用于开发的时间不断在受到挤压而减少,能交付的功能也就不断在减少。

  • 要质量就没速度,如此还能敏捷的起来吗?
  • 不少团队 (包括我们) 采用了牺牲交付质量 (仅部分回归甚至无回归) 来确保开发进度,这样做既不符合敏捷过程的价值观,实际上也没有达到交付目标。
  • 还有一个土豪的做法,就是增配测试。

我们一直在探索这个问题的解决方案,既想确保增量发布的速度,又想拥有交付质量。只能想办法把 AC 自动化起来,全回归就不需要占用太多人手,而且还能随时进行,所需要的时间也大大缩短。


2. 解决方案

在 2017 年 1 月份的 ScrumMaster 培训课中,聊起 App 自动化测试有什么工具,便有小伙伴提了一下 Appium。回来后便查资料研究,发现有两个非常不错的自动化工具,一个就是 Appium,另一个是阿里系的 Macaca。这里不得不说,Macaca 做的很不错,javascript 写测试也相对容易上手。

然而,做了对比后,我决定还是选用 Appium。原因很简单,Appium 支持 Ruby 和 Cucumber,我们的后端正好是用 RubyOnRails 写的,后端开发人员已经在写后端过程中积累了许多编写自动化测试用例的经验。正好也可以来写 app 的自动化测试。


2.1 环境准备

以下内容的运行环境为 MacOS 10.12 + Ruby 2.3.3 + NodeJS 7.x。 至于如何安装 Ruby 和 NodeJS,这个应当为基本技能,请自行搞定。

请不要问我如何在 Windows 上去搭建环境的有关问题,我只会建议要么白苹果,要么黑苹果,要么 Ubuntu (不支持 iOS)。要么走您。

由于 npmjs.org rubygems.org 都在国外,访问速度慢且不稳定,建议先搭好梯子,或者改用国内的源。


2.1.1 Appium 1.6.3 安装

npm -g appium
npm -g appium-doctor
# ios还需要安装两个工具 ios-deploy 用于安装app到真机;Carthage 用于安装app到模拟器
brew install carthage
brew install ios-deploy
# brew 是个什么鬼,如何安装的?没什么技术含量,请自行baidu

# 完成后可以运行appium-doctor诊断一下环境是否正常
# 如果没有android需求的,可以不用管 JAVA_HOME 与 android adb 等错误
appium-doctor

一切正常会得到类似这个信息

appium-doctor-screenshot.png


2.1.2 安装 Appium 的 Ruby 驱动,和 appium ruby console

gem install appium_lib
gem install appium_console

至此,环境准备工作已经完成。


2.2 编写自动化测试用例

测试用例采用 Cucumber 方式编写,Feature 对应 用户故事,Scenario 对应 AC。 一个 Feature 包含多个 Scenarios。 一个用户故事有多个 AC 验收标准。

至于 Cucumber, 有空我会再写一个文章。目前请先自行 baidu 之。


2.2.1 创建目录

mkdir app_test
cd app_test
# 创建测试用例目录
mkdir features 
# 创建测试用例步骤目录
mkdir features/steps
# 创建支持目录
mkdir features/support

2.2.2 准备 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

# 结束时,推出驱动
at_exit { $driver.driver_quit }

# 在运行标有@reset_driver的Scenario前重启驱动
Before("@reset_driver") do
  $driver.restart
end

2.2.4 appium 配置文件 appium.txt

关于 appium.txt 配置说明可以参考文档 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 编写测试用例

每一用户故事的测试用例编写为一个 feature。


2.3.1 编写邮箱登陆用户故事的 AC

邮箱登陆用户故事只是登陆故事中的其中一个,故事 ID US004

$cat features/US004_login_by_email.feature

Feature: US_004 邮箱登录
  为了正常使用需要登录身份的功能
  作为一个已经用邮箱注册过的用户
  我想要用邮箱和密码登录系统

  @reset_driver
  Scenario: AC_US004_02 登录错误: 正确邮箱+错误密码登录
    Given 我已经用邮箱 test_user@mytest.com 与密码 test123 注册过账号
    When 我在 "主页面" 点击 "登录/注册" 进入 "登录页面"
    And 我在 "邮箱或手机" 输入 "[email protected]"
    And 我在 "密码" 输入 "b123456"
    And 我按下按钮 "登录"
    Then 我应当看到浮动提示 "用户密码不匹配"

  Scenario: AC_US004_03 登录错误: 没有输入用户名和密码
    Given 我已经用邮箱 test_user@mytest.com 与密码 test123 注册过账号
    And 我在 "邮箱或手机" 输入 ""
    When 我在 "密码" 输入 ""
    And 我按下按钮 "登录"
    Then 我应当看到浮动提示 "请填写完整"

  Scenario: AC_US004_04 登录错误: 输入用户名没有输入密码
    Given 我已经用邮箱 test_user@mytest.com 与密码 test123 注册过账号
    And 我在 "邮箱或手机" 输入 "[email protected]"
    And 我在 "密码" 输入 ""
    When 我按下按钮 "登录"
    Then 我应当看到浮动提示 "请填写完整"

  Scenario: AC_US004_01 正常邮箱+密码登录
    Given 我已经用邮箱 test_user@mytest.com 与密码 test123 注册过账号
    When 我在 "邮箱或手机" 输入 "[email protected]"
    And 我在 "密码" 输入 "test123"
    And 我按下按钮 "登录"
    Then 我应当到达 "主页面"
    And 等待 2 秒后退出


2.3.2 为 feature 编写相应的 steps

Scenario 中的每个 Given When Then And 都在 steps.rb 中有对应的定义。

$ 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(/^我已经用邮箱 (.*) 与密码 (.*) 注册过账号$/) 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


3. 运行测试用例

3.1 运行 Appium

运行测试用例前必须先启动 appium, 新打开一个命令行窗口 $ appium

run_appium.png


3.2 运行测试用例

在 test_app 目录中

$ cucumber features/US004_login_by_email.feature

run_cucumber_feature.png


3.3 观看测试用例的运行录屏


3.4 示例代码参考

https://github.com/appium/sample-code/


4. 总结

抛开复杂的 appium、cucumber steps,发现没,测试用例是不是很容易理解?

Scenario: AC_US004_01 正常邮箱+密码登录
  Given 我已经用邮箱 test_user@mytest.com 与密码 test123 注册过账号
  When 我在 "邮箱或手机" 输入 "[email protected]"
  And 我在 "密码" 输入 "test123"
  And 我按下按钮 "登录"
  Then 我应当到达 "主页面"
  And 等待 2 秒后退出

像上面的 AC,就算是非技术人员也能分分钟写出来,我们的产品经理、UI 设计师都在参与编写 AC,这就是我们选择 cucumber ruby 的根本原因。

本来打算一篇写完的,写着写着,发现要写的内容太多了,决定还是写成一个系列:

  • 首先、用 Appium 来测试,也存在一些坑要填。
  • 上面的 feature 和 steps,为了演示好理解,做了相当的简化。实际上,我们的 steps 经过一些重构,变得很通用灵活,后续放出来。
  • Appium 的配置与基本指令
  • 辅助神器 Appium Ruby Console (ARC) 的使用
  • 如何用 Cucumber 编写自动化测试
  • React-Native 类型的 APP 自动化有哪些坑需要绕过

有空的时候再接着写(二)、(三)。。。。。。

可以发个行数最多的 Scenario 看看吗?

我们的产品经理、UI 设计师都在参与编写 AC

提交代码库、调试、数据准备这些事是他们做吗?
这种情况有经历过一次较大的 UI 变动吗?或者持续半年以上?

sanlengjingvv 回复

Scenario 的行数越多,说明步骤越多,场景越复杂 (或繁琐),应当避免这种情况。

行数比较多的 Scenario 我找了几个

Feature: US063 个人中心-旧密码修改密码方式
  作为一名已登录用户,我需要有一个修改密码的接口
  使得我能够重新更换密码

  @reset_driver
  Scenario: AC_US063_01 修改密码
    Given 清除数据
    And 我已使用 "[email protected]" 登录
    And 我在 "主页面"
    When 我点击按钮 "左上"
    And 我点击按钮 "设置"
    And 我点击按钮 "账号安全"
    And 我点击按钮 "修改密码"
    Then 我应当到达 "通过密码修改密码界面"
    And 我在 "旧密码" 输入 "old123"
    And 我在 "新密码" 输入 "new123"
    And 我点击按钮 "确定"
    Then 我应当看到浮动提示 "用户密码不匹配"
Feature: US071 更多赛事-类型筛选
  可通过主办方筛选赛事

  @reset_driver
  Scenario: AC_US071_01 更多赛事-类型筛选
    Given 创建主办方为wpt的赛事 (创建数据)
      |ac      |clear|host_name|race_name|
      |AC_US071|true |wpt|2017-WPT启航站  |
    When 我点击按钮 "更多赛事"
    And 我应当到达 "更多赛事界面"
    And 我点击按钮 "筛选主办方"
    And 我点击按钮 "第一个主办方"
    And 我点击按钮 "完成筛选主办方"
    Then  "第一个赛事" 可匹配到 "2017-WPT启航站"


  Scenario: AC_US071_02 更多赛事-类型筛选 + 日期筛选
    Given 创建主办方为wpt的赛事 (创建数据)
      |ac      |clear|host_name|race_name|begin_date|end_date|
      |AC_US071|true |wpt|2017-WPT启航站  |2017-01-01|2111-02-02|
    And 我按下按钮 "回到主页"
    When 我点击按钮 "更多赛事"
    And 我应当到达 "更多赛事界面"
    And 我点击按钮 "筛选主办方"
    And 我点击按钮 "第一个主办方"
    And 我点击按钮 "完成筛选主办方"
    Then  "第一个赛事" 可匹配到 "2017-WPT启航站"
    And 我点击 "日历按钮" 进入 "日历界面"
    And 点击原生button "18"
    And 我点击按钮 "日历按钮"
    Then  "第一个赛事" 可匹配到 "2017.01.01-2111.02.02"
    Then  "第一个赛事" 可匹配到 "2017-WPT启航站"

提交代码库、调试、数据准备这些事是他们做吗?

产品或 UI 通常并不具备“提交代码库、调试、数据准备”的能力,也不要苛求他们能做到这个。说服他们试着写一些简单用例,是为了让他们更好的体验自己的产品需求设计。并且,实际上,极少产品或 UI 愿意做这个事情。如果能碰到愿意做的产品或 UI,请好好珍惜 😀

“提交代码库、调试、数据准备”都有开发团队完成。

这种情况有经历过一次较大的 UI 变动吗?或者持续半年以上?

一旦 UI 发生变动,不是简单的位置调整,样式调整,而是,交互发生变化的 (如输入空间,按钮,流程等),测试脚本都需要同步更新。交互方式发生变化的原因来自于需求发生变化,实现也就需要跟着变化,那么,测试脚本也就需要跟着调整。

测试脚本也是需要同步更新维护的,不是一写完,就万事大吉,一成不变。测试脚本的维护成本需要计入需求变更中,产品经理需要背这个锅,这样 Ta 才能意识到变更的成本,就不会乱变。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号