DevOps工具链之自动化UI测试-IOS(中)

谈谈IOS开发中的自动化UI测试—Jenkins加上Appium、Macaca等

Posted by Marco Liu on June 21, 2018

“Hope for the best. ”

使用ruby+cucumber编写测试用例

开始编写测试用例前,请先了解Cucumber语法,参考这里https://github.com/cucumber/cucumber/wiki

1、新建文件夹

在合适的位置创建测试目录,例子:

cd Desktop
mkdir 测试用例

2、创建Gemfile文件,导入ruby依赖库

source 'https://www.rubygems.org'
gem 'appium_lib',         '~> 9.7.4'
gem 'rest-client',        '~> 2.0.2'
gem 'rspec',              '~> 3.6.0'
gem 'cucumber',           '~> 2.4.0'
gem 'rspec-expectations', '~> 3.6.0'
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 是 Ruby 模块 (叫做 Gems) 的包管理器。其包含包信息,以及用于安装的文件。

Gem通常是依照”.gemspec”文件构建的,包含了有关Gem信息的YAML文件。Ruby代码也可以直接建立Gem,这种情况下通常利用Rake来进行。

3、安装ruby依赖库

在命令行输入下面命令

需要先安装bundle

gem install bundle

安装ruby依赖

bundle install

4、初始化cucumber

cucumber --init

执行上面命令,会生成如下目录结构

features # 存放feature的目录
├── step_definitions # 存放steps的目录
└── support # 环境配置
    └── env.rb

5、新建apps目录

mkdir apps

将目标.app文件拖到apps目录下,注意:这里如果使用模拟器测试,需要app文件是以模拟器架构(eg: iPhone 8)编译的,如果使用真机测试,那么app文件需要使用真机架构编译(eg: Generic iOS Device)

补充:打开工程,command+U,build完成之后,可在工程的Products目录下看到app文件

6、设置Appium服务器初始化参数

新建文件appium.txt

使用xcrun simctl list devices查看可用的设备

设置Desired Capabilities,例如

[caps]
# 模拟器
platformName = "ios"
deviceName = "iPhone 8"
platformVersion = "11.1"
app = "/Users/yangfangming/Desktop/TuanFund-Appium/apps/TuanFundApp/build/TuandaiFund.app"
automationName = "XCUITest"

注意:windows上路径要使用转义,如:app = “//Users//yangfangming//Desktop//TuanFund-Appium//apps//TuanFundApp//build//TuandaiFund.app”

参数描述

7、根据appium.txt配置的参数,初始化全局的driver对象

打开env.rb文件,输入如下代码

require 'rspec/expectations'
require 'appium_lib'
require 'cucumber/ast'
 
# 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('../appium.txt', __FILE__), verbose: true
Appium::Driver.new(caps)
Appium.promote_appium_methods AppiumWorld
 
World do
  AppiumWorld.new
end
 
Before { $driver.start_driver }
After { $driver.driver_quit }

8、新建feature文件,这里以登录为例,新建login.feature文件,输入如下代码

#language: zh-CN
 
功能: 账户测试
 
  # 作为一个开发者
  # 我已经注册了用户名为18576771524,密码为123456a的账号
  # 我希望能使用这个账号登录
 
  场景: 验证登录成功
    假如 广告或者引导页面存在跳过广告和引导页面
       点击 "我的"
    并且 点击 "登录/注册"
    并且 在用户名输入框输入 18576771524
    并且 在密码输入框输入 123456 "a"
    并且 点击 "登录"
    并且 等待 3 
    那么 登录页面应该消失

创建login.feature文件后,有个小技巧,执行bundle exec cucumber运行测试用例(确保Appium服务端端已经启动),由于这里还没有创建steps,会提示如下信息

9、创建steps,根据上面的提示创建loginSteps.rb文件

最终目录结构如下

├── Gemfile # ruby依赖库
├── Gemfile.lock # 依赖库版本管理
├── apps # 目标app
   └── TuandaiFund.app
└── features # 存放featuresteps的目录
    ├── login.feature
    ├── step_definitions # 存放steps的目录
       └── loginSteps.rb
    └── support # 环境配置
        ├── appium.txt
        └── env.rb

编写steps

假如(/^广告或者引导页面存在跳过广告和引导页面$/) do
  pending # Write code here that turns the phrase above into concrete actions
end
 
(/^点击 "([^"]*)"$/) do |arg1|
  pending # Write code here that turns the phrase above into concrete actions
end
 
当(/^在用户名输入框输入 (\d+)$/) do |arg1|
  pending # Write code here that turns the phrase above into concrete actions
end
 
当(/^在密码输入框输入 (\d+) "([^"]*)"$/) do |arg1, arg2|
  pending # Write code here that turns the phrase above into concrete actions
end
 
(/^等待 (\d+) $/) do |arg1|
  pending # Write code here that turns the phrase above into concrete actions
end
 
那么(/^登录页面应该消失$/) do
  pending # Write code here that turns the phrase above into concrete actions
end

编写steps可借助Appium的元素检查器appium inspector,如何启动inspector,如何定位元素和操作,前面环境配置和Appium_lib已经说过了,这里不再赘述

值得一提的是Gherkin中的关键字,假如、当、并且、那么等是等价的语法糖

假如(/^点击 "([^"]*)"$/) do
end
 
当(/^点击 "([^"]*)"$/) do
end
 
并且(/^点击 "([^"]*)"$/) do
end

上面3个steps是等价的,如果有多个这样的feature,只能保留其中一个,否则运行测试用例的时候会报冲突的错误

编写完成之后的loginSteps.rb文件如下:

# 跳过广告页面
def dismiss_ad_View
  adExist = exists { id("广告页面") }
  if adExist
    sleep(5)
  end
end
 
# 跳过引导页面
def dismiss_guide_page
  guideExist = exists { id("引导页面") }
  # guideExist ? puts("引导页面存在") : puts("引导页面不存在")
  if guideExist
    swipe(direction: "left", element: nil)
    sleep(1)
    swipe(direction: "left", element: nil)
    sleep(1)
    button("进入首页").click
    sleep(1)
  end
end
 
# 跳过手势密码页面
def dismiss_gestrure_password_page
  gestureExist = exists { id("手势密码页面") }
  # guideExist ? puts("引导页面存在") : puts("引导页面不存在")
  if gestureExist
    swipe(direction: "right", element: nil)
    sleep(1)
    swipe(direction: "right", element: nil)
    sleep(1)
  end
end
 
假如(/^广告或者引导页面存在跳过广告和引导页面$/) do
  dismiss_ad_View
  dismiss_guide_page
  dismiss_gestrure_password_page
end
 
# 按钮点击
(/^点击 "([^"]*)"$/) do |arg1|
  e = exists { button(arg1) }
  if e
    btn = button(arg1)
  else
    btn = driver.find_element(:id, arg1)
  end
  wait { btn.click }
end
 
当(/^在用户名输入框输入 (\d+)$/) do |arg1|
  textfield("请输入手机号").type arg1
end
 
当(/^在密码输入框输入 (\d+) "([^"]*)"$/) do |arg1, arg2|
  textfield("请输入密码").type arg1+arg2
end
 
# 延时执行
(/^等待 (\d+) $/) do |arg1|
  sleep(arg1.to_i)
end
 
那么(/^登录页面应该消失$/) do
  # 期望:登录页面dismiss
  actual = exists { id("登录页面") }
  expect(actual).to eq(false)
end

现在进入测试用例目录运行bundle exec cucumber查看测试效果吧

使用python3+behave写测试用例

10、可能碰到的问题:

执行bundle exec cucumber命令的时候可能碰到Could not find nokogiri-1.8.2 in any of the sources的错误

解决方法:

brew install libxml2
bundle config build.nokogiri "--use-system-libraries --with-xml2-include=/usr/local/opt/libxml2/include/libxml2"
bundle install

—— Marco 后记于 2018.06