互联网行业有别于传统行业,面临发布周期频繁、多端、多环境,多机型、多版本共存等问题。为了解决日益增长的快速迭代需求与落后的测试生产力之间的主要矛盾,任何公司都需要一套完备的测试体系,对软件质量进行多重保障。然而对于频繁回归的大量测试需求,人力不足以支撑,手动+自动化测试才是产品快速迭代的根基。本文将对移动端UI自动化测试展开介绍。
UI自动化测试介绍业界对于UI自动化测试往往会有一些认知的误区,认为UI自动化测试稳定性不足、执行效率低、复用率不高,产生不了太大的价值。研发代码的单元自测、集成测试、接口测试已经能覆盖后端的绝大部分问题,然而UI交互是最直观的用户体验,对于一些前端展示层、重要的流程变化所带来的兼容性问题,如小数点遗漏、排序、布局、颜色是否正确等问题,这些接口层面不关注的问题不应在测试中遗漏。
面对UI自动化测试的各种难题,我们不应退缩放弃,而应落实软件质量保障的最后一公里,合理的使用UI自动化测试,可以从以下两个角度着手。
一 策略改进使用分层测试策略,控制UI自动化测试用例规模,将少数核心用例自动化,大部分的基础回归测试交给自动遍历,新功能探索测试交给人工测试。
二 技术改进使用良好的维护模型,降低维护成本:PageObject及其他一些简单的封装策略,更好的框架支持:增加Watch,智能等待,失败重试等机制。
UI自动化测试框架——Appium 介绍如今各种UI自动化测试框架层出不穷,其中笔者推荐Appium,理由如下:
跨语言:Java、Python、nodejs等
跨平台:Android、IOS、Windows、Mac
底层多种引擎切换:
Android:uiautomator2、espresso、selendroid
IOS:uiautomation、xcuitest
生态丰富,社区庞大
github地址:https://github.com/appium/appium
Appium 生态工具介绍1Appium Desktop内嵌了Appium Server 和 Inspector元素验证的综合GUI工具
2Appium Desktop 使用Appium Desktop是对初学者友好的图形化界面工具,内嵌了Appium Server,更重要的一个原因是,相比于命令行运行方式,Desktop多了一个Inspector模块,可以很方便地在预览页面中查看UI元素的层级结构和详细控件属性,极大地提高编写测试脚本的效率。
Capabilities设置是使用Appium进行自动化测试的第一步,针对安卓平台,以下列举一些必要配置信息。
更多具体用法可参考文档:https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/caps.md
{
"platformName": "Android",
"deviceName": "test",
"appPackage": "com.mengtuiapp.mall",
"appActivity": "com.mengtuiapp.mall.SplashActivity",
"noReset": true
"autoGrantPermissions": "true"
}
Appium Desktop具有录制用例,自动生成各语言脚本的功能,有助于初学者快速高效的生成简单的测试脚本以解决一些日常工作中重复性高的任务。
自动录制代码事例:
# This sample code uses the Appium python client# pip install Appium-Python-Client# Then you can paste this into a file and simply run with Pythonfrom appium import webdrivercaps = {}caps["platformName"] = "Android"caps["deviceName"] = "test"caps["appPackage"] = "com.mengtuiapp.mall"caps["appActivity"] = "com.mengtuiapp.mall.SplashActivity"caps["noReset"] = Truedriver = webdriver.Remote("http://localhost:4723/wd/hub", caps)# 追加一句driver.implicitly_wait(10)el1 = driver.find_element_by_xpath("/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.view.ViewGroup/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.HorizontalScrollView/android.widget.FrameLayout/android.widget.LinearLayout[1]/android.widget.TextView[3]")el1.click()driver.quit()
3Appium Server
Appium的核心服务,命令行工具,用于持续集成,远程测试
4Appium Server 安装npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm install -g appium
5Appium Client各语言的客户端封装库,用于连接Appium Server
Appium 自动化脚本编写UI自动化测试脚本编写具有三大要素,笔者将逐一展开介绍。
1、元素定位
2、交互操作
3、断言
一 元素定位安卓App页面整个后台是一个大的xml文件,我们的需求是唯一定位目标元素以进行相对应的操作,元素定位的方式主要分为以下几种:
1、ID定位
2、Xpath定位
3、Class Name定位
元素定位官方文档:http://appium.io/docs/en/commands/element/find-elements/
xpath用法总结整理: https://www.jianshu.com/p/62ba163c5be5
二 元素定位示例//*[contains(@resource-id, 'login')]//*[@text='限时秒杀' and @instance='10']//[@resource-id='com.mengtuiapp.mall:id/item1']//[@text='常见问题']//android.widget.EditText 反例:不要使用绝对定位,使用相对定位 /hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.view.ViewGroup/android.widget.FrameLayout
三
交互操作
Appium提供多种元素操作接口,如点击,滑动,输入文字等,常用操作如下:
1、click
2、sendKeyEvent
3、TouchAction(self.driver)
参考文档:http://appium.io/docs/en/writing-running-appium/touch-actions/
四 百分比滑动操作示例self.action.long_press(x=self.rect['width']*0.5, y=self.rect['height']*0.8).move_to(x=self.rect['width']*0.5, y=self.rect['height']*0.4).release().perform()
五
断言
完成测试用例步骤的执行后,需要进行断言验证是否达到预期,Appium提供元素的多种属性验证,举例如下:
1、Text
2、Selected
3、Displayed
4、Size
断言示例:
assert self.find(self._username).is_displayed()assert self.find(self._ensure).text == "确认交易"
六
脚本封装技巧
Appium Driver封装
注解返回类型,driver = '' 返回的就是字符窜类型
from appium import webdriverfrom appium.webdriver.webdriver import WebDriverclass AndroidClient(): driver:WebDriver @classmethod def init_appium(cls) -> WebDriver: caps = {} caps["platformName"] = "Android" caps["deviceName"] = "test" caps["appPackage"] = "com.mengtuiapp.mall" caps["appActivity"] = "com.mengtuiapp.mall.SplashActivity" caps['noReset'] = "true" caps["unicodeKeyboard"] = "true" caps["resetKeyboard"] = "true" cls.driver = webdriver.Remote("http://localhost:4723/wd/hub", caps) cls.driver.implicitly_wait(10) return cls.driver
PageObject 封装
为了解决UI界面、操作流程、元素定位方式等频繁变动造成UI自动化脚本维护成本高的问题,引入PageObject表示UI,减少重复样板代码,让变更控制在对象内,本质上是面向对象编程的思想。
普通方式的用例代码之间没有联系,往往一个地方的变动,需要在所有的case中进行适配,与其他用例之间没有复用关联,灵活性不足。
普通模式
def test_user_login(): driver = webdriver.Edge() base_url = 'https://mail.qq.com/' username = '3494xxxxx' # qq号码 password = 'kemixxxx' # qq密码 driver.get(base_url) driver.switch_to.frame('login_frame') #切换到登录窗口的iframe driver.find_element(By.ID, "u").send_keys(username) #输入账号 driver.find_element(By.ID, "p").send_keys(password) #输入密码 driver.find_element(By.ID, "login_button").click() #点击登录
PO模式
对象库层:
# 基础页面class BasePage(): element_black = [By.XPATH, "//*[@content-desc='萌推']/android.view.View[2]"] def __init__(self): self.driver: WebDriver = self.getDriver() self.op = OperateElement(self.driver) self.rect = self.driver.get_window_rect() self.action = TouchAction(self.driver) # Android/IOS/WebView/小程序 @classmethod def getDriver(cls): cls.driver = AndroidClient.driver return cls.driver def find(self, kv) -> WebElement: return self.finds(*kv)# 消息列表Pageclass MessagePage(BasePage): _offical = (By.XPATH, "萌推官方客服") _store_message = (By.XPATH, "//*[@text='萌点食品专营店']") def official_service(self): self.find(self._offical).click() return CustomerServicePage() def choose_adv(self): time.sleep(5) self.action.tap(x=self.rect['width']*0.24, y=self.rect['height']*0.77).perform()
操作逻辑层:
# 具体测试用例class TestDemo(): @classmethod def setup_class(cls): cls.mainPage = App.main() def setup_method(self): self.mainPage: MainPage = TestTemp.mainPage allure.story("136:抽奖") def test_136(self): with allure.step("判断登陆状态并登陆账号"): self.mainPage.goto_mine().goto_logout() with allure.step("用户登录,推币中心,抽奖"): MinePage().goto_message_page() def teardown_method(self): pass
业务层数据驱动:
解耦业务中具体的操作步骤,比如进入某个场景,分离点击的顺序与定位符这些频繁变化的东西,独立抽出来进行维护。
address_info: - element_info: name by: id action: send_keys text: test - element_info: phone by: id action: send_keys text: 18810578985 - element_info: detail by: id action: send_keys text: 中关村南大街5号 - element_info: select by: id action: click - element_info: btnSubmit by: id action: click - element_info: submit by: id action: click
PO模式小结:
面对相同的测试用例,初看之下PO模式的实现方式导致代码量的增加,那么分层之后真的易于维护么?只有当测试用例数量较多时PO模式才能发挥其真正的优势。当某个元素发生变化时,我们仅需要在对象库层找到对应的元素进行修改,那么与之关联的所有用例都不需要再进行改动,复用越多收益越大。
报告展示:
总结Appium只是实现UI自动化测试的一部分,做过自动化测试的人应该都会有这样一种体会,写一个自动化测试用例demo很容易,但是要真正将自动化测试落地,对成百上千的自动化测试用例实现完整的可复用性、可维护性封装并不容易。针对UI自动化测试,笔者推荐Appium+pytest+PageObject+Allure的形式去实现测试框架封装。
要做好自动化测试,除了需要掌握多种测试理论方法外,还需要根据待测对象,选择对应的测试工具,按症下药。测试围度也会很多,除了最基础的功能测试外,对Web前端进行页面兼容性测试、对app进行客户端性能专项测试(启动时间、启动内存、流畅度、包大小等),对后台服务器端进行性能测试(响应时间、吞吐量等),对系统安全进行测试等等。
面对种种困境,希望大家能保持持续学习的热情,逢山开路,遇水搭桥,落实到实际项目中,为产品的软件质量带来切实利益!
参考资料Appium:https://github.com/appium/appium
Pytest: https://docs.pytest.org/en/latest/
Allure: http://allure.qatools.ru/
PageObject:https://martinfowler.com/bliki/PageObject.html