背景 Ui 自动化测试框架
目标
确保质量
减少人工成本
统一标准
考虑方面
平台支持。
稳定性。
维护成本。
可扩展性。
All: Appium, kobiton, calabash, MonkeyTalk Andoird: robotinum, uiautomator, espresso iOS: XCTest, UIAutomation, Frank, KIF, Kiwi Appium + cucumber .使用 cucumber BDD 框架,底层还是使用 appium 。为封装更多 step,可以使用 selenium-cucumber(待查看)。
支付宝开源了 SoloPi android 自动化测试工具。是依靠录制回放、性能测试、一机多控 来实现的。测试是通过录制用户操作,记录下来再在各个设备上回放。
行为驱动开发(Behavior-driven development,缩写BDD)是一种敏捷软件开发的技术。它通过用自然语言书写非程序员可读的测试用例扩展了测试驱动开发方法。 满足 BDD 和 跨平台是我们需要的。
Calabash 满足 BDD 和 跨平台。 相对小众,且 ios 接入 calsbash 成本太高。Calabash 对元素的定位主要依赖 ID,ios 一般不具备这个条件。
Appium 是一个C/S架构,核心是一个Web服务器,它提供了一套REST的接口。当收到客户端的连接后,就会监听到命令,然后在移动设备上执行这些命令,最后将执行结果放在HTTP响应中返还给客户端。 可以和 python 很好结合,但是在业务快速发展的过程中,维护成本会越来越难以接受
结合 Calabash 和 Appium,通过 appium + cucumber + selenium 来实现上层使用 cucumber 来满足 BDD,底层使用 appium 来确保跨平台和稳定性
搭建初始化 现在跑通的是 appium + cucumber + selenium + ruby
ruby 安装 mac 自带的 2.3.0 可能不够用,需要安装 2.4.0 以上的。 可以直接 brew install ruby 安装,注意要设置环境变量取代默认版本.
Cucumber.rb gem install cucumber
s
开始 新建文件夹 mkdir TestDemo
初始化 cucumber。 cucumber –init 执行上面命令,会生成如下目录结构:
1 2 3 4 features # 存放feature的目录 ├── step_definitions # 存放steps的目录 └── support # 环境配置 └── env.rb
创建 Gemfile 文件 创建 Gemfile 文件 touch Gemfile
打开Gemfile,导入Ruby库
1 2 3 4 5 6 7 8 9 10 11 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' gem 'selenium-cucumber', '~> 3.1.5'
安装依赖库 1 2 3 4 5 # 需要先安装bundle gem install bundle # 安装ruby依赖 bundle install
配置运行基本信息
进入features/support目录,新建appium.txt文件
编辑appium.txt文件,这里只配置了iOS的模拟器和真正代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 [caps] # 模拟器 #platformName = "ios" #deviceName = "iPhone X" #platformVersion = "11.2" #app = "./apps/AutoUITestDemo.app" #automationName = "XCUITest" #noReset="true" # 真机 # platformName = "ios" # deviceName = "xxx" # platformVersion = "10.3.3" # app = "./apps/AutoUITestDemo.app" # automationName = "XCUITest" # udid = "xxxx" # xcodeOrgId = "QT6N53BFV6" # xcodeSigningId = "ZHH59G3WE3" # autoAcceptAlerts = "true" # waitForAppScript = "$.delay(5000); $.acceptAlert();" # 处理系统弹窗 # android platformName = "android" deviceName = "ce10171a72ba8938057e" platformVersion = "8" app = "./app/app-flavors_process_test-debug.apk"//app 路径,这是以 TestDemo 文件夹为相对路径 automationName = "UIAutomator2" noSign="true" [appium_lib] sauce_username = false sauce_access_key = false
打开env.rb文件,配置启动入口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 require 'rspec/expectations' require 'appium_lib' require 'selenium-cucumber' # require 'cucumber/ast' # Create a custom World class so we don't pollute `Object` with Appium methods class AppiumWorld end if ENV['IDEVICENAME']=='android' caps = Appium.load_appium_txt file: File.expand_path("./../android/appium.txt", __FILE__), verbose: true elsif ENV['IDEVICENAME']=='ios' caps = Appium.load_appium_txt file: File.expand_path("./../ios/appium.txt", __FILE__), verbose: true else caps = Appium.load_appium_txt file: File.expand_path('./', __FILE__), verbose: true end # end Appium::Driver.new(caps, true) Appium.promote_appium_methods AppiumWorld World do AppiumWorld.new end Before { $driver.start_driver } After { $driver.driver_quit }
features目录下,新建guide.feature文件,用来描述测试用例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @guidepage Feature: 引导页 1.首次安装应用,判断是否展示引导页; 滑到最后一张,判断是否展示“登录/注册”和“进入首页”两个按钮; 点击“登录/注册”按钮,判断是否展示登录界面。 2.滑动到最后一张引导页,点击“进入首页”按钮,判断引导页是否还存在。 @guide_01 Scenario: 首次安装应用,展示引导页;滑动到最后一张引导页,展示“登录/注册”和“进入首页”两个按钮 When 展示引导页 Then 滑动到最后一页 Then 展示“登录/注册”和“进入首页”两个按钮 When I press "“登录/注册" Then I see "登陆" @guide_02 Scenario: 点击最后一张引导页“进入首页”按钮,判断引导页是否还存在 When 滑动到最后一张引导页,点击“进入首页”按钮 Then 退出引导页
在step_definitions目录下,新建 common_steps.rb 和 guide.rb 文件,用来存放脚本代码
编写rb脚本之前,先打开 appium,再执行 cucumber 命令。
将 cucumber 提示我们实现的复制下来放到 rb 文件
common_steps.rb 可以放公共的可复用的 step,guide.rb 放自己的step
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 common_steps.rb When("I press {string}") do |string| guideIsExist = exists { button(string) } puts guideIsExist ? "存在该按钮" : "不存在该按钮" expect(guideIsExist).to be true sleep 1 end guide.rb # @guide_01 # 首次安装应用,判断是否展示引导页; # 滑到最后一张,判断是否展示“登录/注册”和“进入首页”两个按钮; # 点击“登录/注册”按钮,判断是否展示登录界面。 When("展示引导页") do guideIsExist = exists { button("现在就下载") } puts guideIsExist ? "存在引导页面" : "不存在引导页面" expect(guideIsExist).to be true end Then(/^滑动到最后一页$/) do swipe_to_last_guide_view sleep(1) end Then(/^展示“登录\/注册”和“进入首页”两个按钮$/) do $loginBtnIsExist = exists { button("现在就下载") } puts $loginBtnIsExist ? "存在“登录/注册”按钮" : "不存在“登录/注册”按钮" expect($loginBtnIsExist).to be true startBtnIsExist = exists { id("setting_items") } puts startBtnIsExist ? "存在“进入首页”按钮" : "不存在“进入首页”按钮" expect(startBtnIsExist).to be true end
运行 cucumber –dry-run 来查看 rb 文件编写测试实例的情况(是否有遗漏)
运行 cucumber。cucumber –tags @guidepage 可按 tags 运行。
补充 元素定位 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 # 1、使用button查找按钮 first_button // 查找第一个button button(value) // 查找第一个包含value的button,返回[UIAButton|XCUIElementTypeButton]对象 buttons(value) // 查找所有包含value的所有buttons,返回[Array<UIAButton|XCUIElementTypeButton>]对象 eg: button("登录") // 查找登录按钮 # 2、使用textfield查找输入框 first_textfield // 查找第一个textfield textfield(value) // 查找第一个包含value的textfield,返回[TextField] eg: textfield("用户名") // 查找 # 3、使用accessibility_id查找 id(value) // 返回id等于value的元素 eg: id("登录") // 返回登录按钮 id("登录页面") // 返回登录页面 # 4、通过find查找 find(value) // 返回包含value的元素 find_elements(:class, 'XCUIElementTypeCell') // 通过类名查找 eg: find("登录页面") # 5、通过xpath查找 xpath(xpath_str) # web元素定位: # 测试web页面首先需要切换driver的上下文 web = driver.available_contexts[1] driver.set_context(web) # 定位web页面的元素 driver.find_elements(:css, ".re-bb") # 通过类选择器.re-bb定位css的元素
更多详情
常用事件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // 通过坐标点击 tap(x: 68, y: 171) // 通过按钮元素点击 button("登录").click // 滑动手势 swipe(direction:, element: nil) // direction - Either 'up', 'down', 'left' or 'right'. eg: 上滑手势 swipe(direction: "up", element: nil) // wait wait { find("登录页面") } // 等待登录页面加载完成 // sleep sleep(2) // 延时2秒
更多详情
断言 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 # 1. 相等 expect(actual).to eq(expected) # passes if actual == expected expect(actual).to eql(expected) # passes if actual.eql?(expected) expect(actual).not_to eql(not_expected) # passes if not(actual.eql?(expected)) # 2、比较 expect(actual).to be > expected expect(actual).to be >= expected expect(actual).to be <= expected expect(actual).to be < expected expect(actual).to be_within(delta).of(expected) # 3、类型判断 expect(actual).to be > expected expect(actual).to be >= expected expect(actual).to be <= expected expect(actual).to be < expected expect(actual).to be_within(delta).of(expected) # 4、Bool值比较 expect(actual).to be_truthy # passes if actual is truthy (not nil or false) expect(actual).to be true # passes if actual == true expect(actual).to be_falsy # passes if actual is falsy (nil or false) expect(actual).to be false # passes if actual == false expect(actual).to be_nil # passes if actual is nil expect(actual).to_not be_nil # passes if actual is not nil # 5、错误 expect { ... }.to raise_error expect { ... }.to raise_error(ErrorClass) expect { ... }.to raise_error("message") expect { ... }.to raise_error(ErrorClass, "message") # 6、异常 expect { ... }.to throw_symbol expect { ... }.to throw_symbol(:symbol) expect { ... }.to throw_symbol(:symbol, 'value')
更多断言详情