本文介绍了如何用行业流行的行为驱动BDD框架Cucumber作为测试框架,使用Node.js 编程语言结合Appium开发iOS原生应用的自动化测试。本文使用了BDD的可视化开发工具CukeTest (cuketest.com)
主要内容
-
准备被测应用app
-
编写用例的场景描述
-
安装自动化库
-
生成、完善测试代码
-
运行
-
生成测试报告
前提条件
-
准备一台Mac电脑
-
配置appium 具体可参考 appium.io/docs/cn/app…
-
安装CukeTest,自动化脚本开发工具,下载地址: cuketest.com/download
下载时请选择Mac版
1. 准备被测试的应用app
本次教程使用https://developer.apple.com官网提供的教学的样例作为被测app,iOS app制作过程可以参考官网教程 developer.apple.com/library/arc…
应用源码 developer.apple.com/sample-code…
源码下载后解压,使用xcode打开源码。执行编译,app UI界面如下
2. 编写用例的场景描述
行为驱动(BDD)的优势就是可以用自然语言描述测试场景,并与代码相关联,提高了可读性和可维护性。它从被测应用的外部描述应用的行为,再转换为自动化脚本,让自动化测试的开发变得非常有条理。
我们这里就按照行为驱动的最佳实践,先编写中文的场景描述。步骤如下:
a. 首先打开CukeTest,新建项目,项目模板选择--基本模版,点击创建。
b. 项目创建成功后默认项目结构为:
c. 在feature文件中编辑测试用例:可以手动在可视化界面编写用例,也可以在文本界面编写用例。
# language: zh-CN
功能: IosDemo 操作
常见的增删改查操作
场景: 添加一个新的记录
假如点击添加按钮
当meal name 中输入"abcdefg"
同时点击图片从相册中选择一张图片
并且选择4颗星
当点击保存按钮
那么首页应该会有4个列表
场景: 修改一条记录
假如选择点击最后一个列表
当文本输入框中的更改值为"HelloWorld"
同时点击保存按钮
那么首页最后一个列表文本值应该为"HelloWorld"
场景: 删除一条记录
假如点击Edit按钮转换为可编辑状态
当选择最后一个列表
同时点击删除
那么此列表应该被删掉
复制代码
对应的可视化界面为:
有了场景描述,下面就是实现这个场景的自动化。
- 安装自动化库
-
安装wd.js
appium支持的JavaScript库有wd.js, webdrerio.js 本次使用wd.js 库作为appium的客户端实现,关于appium的API,更多请参考:appium.io/docs/en/abo…
-
安装方式
在项目的根目录下,执行
npm install wd --save
(备注:可以在CukeTest 文件导航栏项目名称处 右键--在命令行窗口中显示, 导航到项目的根目录下)
4. 生成自动化脚本
CukeTest 提供了将测试用例描述转化为自动化代码的功能,在CukeTest 中打开 step_definitions/definitions1.js 文件,点击测试用例后的灰色按钮即可在definatons1.js文件中自动生成框架代码:
然后只要在框架代码中填充实现就可以了。
5. 完善自动化代码
features目录下新建get_driver.js,定义驱动生成。
let wd = require('wd')function createDriver(){
return wd.promiseChainRemote({
host: '127.0.0.1',
port: 4723
});}
exports.driver = createDriver()
复制代码
在features目录下创建 support.js. 设置运行时
let {BeforeAll,Before,After,AfterAll,setDefaultTimeout} = require('cucumber')
let path = require('path')
let cuketest = require('cuketest');
let {driver} = require('./get_driver')
// 设置超时时间为60秒
setDefaultTimeout(60*1000)
// 所有用例执行前
BeforeAll(async function(){
await cuketest.minimize()
// 模拟器设置
let desiredCaps = {
platformName: "iOS",
platformVersion: "11.4",
deviceName: "iPhone 7",
automationName: "XCUITest",
app: path.resolve('/Users/zack/Library/Developer/Xcode/DerivedData/FoodTracker-bidmzqqybmbcqcbncwomyqmqbvoi/Build/Products/Debug-iphonesimulator/FoodTracker.app')//ios app文件路径
};
await driver.init(desiredCaps);
await driver.setImplicitWaitTimeout(15000);
})
//每个场景执行后截图
After(async function(){
let screenshot = await driver.takeScreenshot();
this.attach(screenshot, 'image/png');
})
//所有场景执行完的操作
AfterAll(async function(){
await cuketest.restore();
return driver.quit();
})
复制代码
完善definitions1.js 文件,根据定义的测试用例,实现相关的操作方法。
const { Given, When, Then } = require('cucumber');
let { driver } = require('../support');
let assert = require('assert');
你的步骤定义 /
Given(/^点击添加按钮$/, async function () {
await driver.elementByAccessibilityId("Add").click();
});
When(/^meal name 中输入"([^"]*)"$/, async function (text) {
let edit = await driver.elementByIosClassChain('**/XCUIElementTypeTextField[`value == "Enter meal name"`]')
await edit.click();
await edit.sendKeys(text);});When(/^点击图片从相册中选择一张图片$/, async function () {
let uploadImage = await driver.elementByAccessibilityId('defaultPhoto')
await uploadImage.click();
let Moments = await driver.elementByAccessibilityId('Moments')
await Moments.click();
let allimge = await driver.elementsByIosClassChain('**/XCUIElementTypeCollectionView/**/XCUIElementTypeCell')
console.log("length is ",allimge.length);
let index = Math.floor(Math.random() * Math.floor(allimge.length))
await allimge[3].click();
});
When(/^选择(\d+)颗星$/, async function (arg1) {
await driver.elementByAccessibilityId('Set 4 star rating').click();
});
When(/^点击保存按钮$/, async function () {
await driver.elementByAccessibilityId('Save').click()
});
Then(/^首页应该会有(\d+)个列表$/, async function (num) {
let itemlist = await driver.elementsByIosClassChain('**/XCUIElementTypeTable/**/XCUIElementTypeCell')
return assert.equal(num,itemlist.length)
});
Given(/^选择点击最后一个列表$/, async function () {
let itemlist = await driver.elementsByIosClassChain('**/XCUIElementTypeTable/**/XCUIElementTypeCell')
await itemlist[itemlist.length - 1].click()});
When(/^文本输入框中的更改值为"([^"]*)"$/, async function (text) {
let edit = await driver.elementByIosClassChain('**/XCUIElementTypeTextField')
await edit.click();
await edit.clear();
await edit.sendKeys(text);
await driver.hideDeviceKeyboard();});
Then(/^首页最后一个列表文本值应该为"([^"]*)"$/, async function (textval) {
let itemlist = await driver.elementsByIosClassChain('**/XCUIElementTypeTable/**/XCUIElementTypeCell')
let last = itemlist.length - 1;
let element = itemlist[last].elementByIosClassChain('**/XCUIElementTypeStaticText')
let text = await element.text();
return assert.equal(text,textval);
});
Given(/^点击Edit按钮转换为可编辑状态$/, async function () {
await driver.elementByAccessibilityId('Edit').click();
});
When(/^选择最后一个列表$/, async function () {
let itemlist = await driver.elementsByIosClassChain('**/XCUIElementTypeTable/**/XCUIElementTypeCell')
let last = itemlist.length - 1;
let del = await itemlist[last].elementByXPath('//XCUIElementTypeButton[5]')
await del.click();
});
When(/^点击删除$/, async function () {
await driver.elementByXPath('(//XCUIElementTypeButton[@name="Delete"])[1]').click();});Then(/^此列表应该被删掉$/, async function () {
let itemlist = await driver.elementsByIosClassChain('**/XCUIElementTypeTable/**/XCUIElementTypeCell')
return assert.ok(itemlist.length == 3)
});
复制代码
此时,feature文件后面的操作步骤后面会变成绿色,表示此步骤有代码实现。
点击绿色按钮,可以跳转到代码实现部分,这样对于修改代码也非常方便。
6. 运行
自动化代码编辑完后,命令行中 启动appium,直接运行appium 即可。
$ > appium
[Appium] Welcome to Appium v1.8.1[Appium] Appium REST http interface listener started on 0.0.0.0:4723
复制代码
CukeTest 中点击”运行“按钮或者点击”运行项目“按钮,即可运行自动化脚本。
7. 生成测试报告
点击”运行项目“按钮,自动化脚本运行完成后会自动生成测试报告,其中自动会包含每个场景运行完的截屏:
获取更多信息,可以关注公众号,也可以加QQ群:707467292 进行node.js自动化相关技术交流。