当前位置:网站首页>APP automation testing practice based on UiAutomator2+PageObject mode
APP automation testing practice based on UiAutomator2+PageObject mode
2022-08-10 11:40:00 【51CTO】
前言
在上一篇《 APP自动化测试框架-UiAutomator2基础》中,重点介绍了uiautomator2的项目组成、运行原理、Basic introductory knowledge of environment construction and element positioning,本篇将介绍如何基于uiautomator2设计PageObject模式(以下简称PO模式)、carry out mobileAPP的自动化测试实践.
一、PO模式简介
1.起源
POThe pattern is a foreign godMartin Fowler于2013a design pattern,The basic idea is to emphasize the separation of code logic and business logic. https://martinfowler.com/bliki/PageObject.html

2.PO六大原则

翻译成中文就是:
- 公共方法表示页面提供的服务
- Try not to expose the internal implementation of the page
- Do not add assertions to the page,Assertion Loading
- method returns another page object
- No need to encapsulate all page elements
- 相同的行为、不同的结果,Need to encapsulate into different methods
3.PO设计模式分析
- 用Page Object表示UI
- Reduce duplication of sample code
- Keep the scope of changes under controlPage Object内
- Essentially object-oriented programming
4.POThe main components of the package
- Driver对象:完成对WEB、Android、iOS、接口的驱动
- Page对象:完成对页面的封装
- 测试用例:调用Page对象实现业务并断言
- 数据封装:配置文件和数据驱动
- Utils:其他功能/工具封装,Improve the lack of native framework
5.Common layered models in the industry

1)四层模型
- Driver层完成对webdriverSecondary encapsulation of common methods,如:定位元素方法;
- Elements层:store element attribute value,如图标、按钮的resourceId、className等;
- Page层:store page object,通常一个UIInterface encapsulates an object class;
- Case层:Call each page object class,组合业务逻辑、形成测试用例;
2)三层模型(推荐)
The only difference between the four-layer model and the three-layer model is that thePage层与ElementsLayers are stored together,Each page object file also contains each icon in the current page、按钮的resourceId、className等属性值,以便随时调用;
二、GUITwo or three things about automated testing
1.什么是自动化
Automation, as the name implies, is the process or practice of converting human operation behavior of software into machine execution testing through code or tools..
2.为什么要做自动化
There's so much to say about this,不做过多赘述,For details, please refer to my《软件测试52讲》Content in class notes:

3.什么样的项目适合做自动化
- 需求稳定,不会频繁变更(尤其是GUI测试,Page layout and elements cannot change frequently)
- 研发和维护周期长,需要频繁执行回归测试
- Manual testing is not possible or expensive,Need to replace implementation with automation
- Test scenarios that need to be run repeatedly
- ......
三、APP自动化测试实战
1.设计项目结构

2.封装BasePage
即Driver层,对uiautomator2进行二次封装,所有PageClasses inherit directly or indirectlyBasePage
# coding:utf-8
DEFAULT_SECONDS
=
10
class
BasePage(
object):
"""
第一层:对uiAutomator2进行二次封装,定义一个所有页面都继承的BasePage
封装uiAutomator2基本方法,如:元素定位,元素等待,Navigation pages, etc.
不需要全部封装,Pack as much as you use
"""
def
__init__(
self,
device):
self.
d
=
device
def
by_id(
self,
id_name):
"""通过id定位单个元素"""
try:
self.
d.
implicitly_wait(
DEFAULT_SECONDS)
return
self.
d(
resourceId
=
id_name)
except
Exception
as
e:
print(
"page not foundid为%s的元素"
%
id_name)
raise
e
def
by_id_matches(
self,
id_name):
"""通过idKeyword matching targets a single element"""
try:
self.
d.
implicitly_wait(
DEFAULT_SECONDS)
return
self.
d(
resourceIdMatches
=
id_name)
except
Exception
as
e:
print(
"page not foundid为%s的元素"
%
id_name)
raise
e
def
by_class(
self,
class_name):
"""通过class定位单个元素"""
try:
self.
d.
implicitly_wait(
DEFAULT_SECONDS)
return
self.
d(
className
=
class_name)
except
Exception
as
e:
print(
"page not foundclass为%s的元素"
%
class_name)
raise
e
def
by_text(
self,
text_name):
"""通过text定位单个元素"""
try:
self.
d.
implicitly_wait(
DEFAULT_SECONDS)
return
self.
d(
text
=
text_name)
except
Exception
as
e:
print(
"page not foundtext为%s的元素"
%
text_name)
raise
e
def
by_class_text(
self,
class_name,
text_name):
"""通过text和classMultiple positioning of an element"""
try:
self.
d.
implicitly_wait(
DEFAULT_SECONDS)
return
self.
d(
className
=
class_name,
text
=
text_name)
except
Exception
as
e:
print(
"page not foundclass为%s、text为%s的元素"
% (
class_name,
text_name))
raise
e
def
by_text_match(
self,
text_match):
"""通过textMatchesKeyword matching targets a single element"""
try:
self.
d.
implicitly_wait(
DEFAULT_SECONDS)
return
self.
d(
textMatches
=
text_match)
except
Exception
as
e:
print(
"page not foundtext为%s的元素"
%
text_match)
raise
e
def
by_desc(
self,
desc_name):
"""通过description定位单个元素"""
try:
self.
d.
implicitly_wait(
DEFAULT_SECONDS)
return
self.
d(
description
=
desc_name)
except
Exception
as
e:
print(
"page not founddesc为%s的元素"
%
desc_name)
raise
e
def
by_xpath(
self,
xpath):
"""通过xpath定位单个元素【特别注意:只能用d.xpath,千万不能用d(xpath)】"""
try:
self.
d.
implicitly_wait(
DEFAULT_SECONDS)
return
self.
d.
xpath(
xpath)
except
Exception
as
e:
print(
"page not foundxpath为%s的元素"
%
xpath)
raise
e
def
by_id_text(
self,
id_name,
text_name):
"""通过id和text多重定位"""
try:
self.
d.
implicitly_wait(
DEFAULT_SECONDS)
return
self.
d(
resourceId
=
id_name,
text
=
text_name)
except
Exception
as
e:
print(
"page not foundresourceId、text为%s、%s的元素"
% (
id_name,
text_name))
raise
e
def
find_child_by_id_class(
self,
id_name,
class_name):
"""通过id和class定位一组元素,and find child elements"""
try:
self.
d.
implicitly_wait(
DEFAULT_SECONDS)
return
self.
d(
resourceId
=
id_name).
child(
className
=
class_name)
except
Exception
as
e:
print(
"page not foundresourceId为%s、className为%s的元素"
% (
id_name,
class_name))
raise
e
def
is_text_loc(
self,
text):
"""Locate a text object(Mostly used to determine whether a text exists)"""
return
self.
by_text(
text_name
=
text)
def
is_id_loc(
self,
id):
"""定位某个id对象(used to judge aid是否存在)"""
return
self.
by_id(
id_name
=
id)
def
fling_forward(
self):
"""Swipe up on the current page"""
return
self.
d(
scrollable
=
True).
fling.
vert.
forward()
def
swipe_up(
self):
"""Swipe up on the current page,步长为10"""
return
self.
d(
scrollable
=
True).
swipe(
"up",
steps
=
10)
def
swipe_down(
self):
"""The current page slide down,步长为10"""
return
self.
d(
scrollable
=
True).
swipe(
"down",
steps
=
10)
def
swipe_left(
self):
"""Swipe left on current page,步长为10"""
return
self.
d(
scrollable
=
True).
swipe(
"left",
steps
=
10)
def
swipe_right(
self):
"""Swipe right on the current page,步长为10"""
return
self.
d(
scrollable
=
True).
swipe(
"right",
steps
=
10)
- 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.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
3.define individual pagesPage
所有页面Page类都继承BasePage.根据POOne of the six principles of the model“No need to encapsulate all page elements”,Encapsulate as many page elements as you use.例如:Currently under testAPP有3个界面,则定义3个页面Page:
- home_page.py
- chat_page.py
- group_page.py
1)home_page.py
# coding:utf-8
from
pages.
u2_base_page
import
BasePage
class
HomePage(
BasePage):
def
__init__(
self,
device):
super(
YueYunHome,
self).
__init__(
device)
self.
msg_icon
=
"com.zhoulesin.imuikit2:id/icon_msg"
self.
friend_icon
=
"com.zhoulesin.imuikit2:id/icon_friend"
self.
find_icon
=
"com.zhoulesin.imuikit2:id/icon_find"
self.
mine_icon
=
"com.zhoulesin.imuikit2:id/icon_mine"
self.
add_icon
=
"com.zhoulesin.imuikit2:id/iv_chat_add"
self.
create_group_btn
=
"com.zhoulesin.imuikit2:id/ll_create_group"
self.
chat_list
=
"com.zhoulesin.imuikit2:id/rv_message_list"
self.
chat_list_child
=
"com.zhoulesin.imuikit2:id/ll_content"
def
msg_icon_obj(
self):
"""会话图标"""
return
self.
by_id(
id_name
=
self.
msg_icon)
def
click_msg_icon(
self):
"""Click the conversation icon at the bottom"""
return
self.
by_id(
id_name
=
self.
msg_icon).
click()
def
click_friend_icon(
self):
"""Click the Contacts icon at the bottom"""
return
self.
by_id(
id_name
=
self.
friend_icon).
click()
def
click_find_icon(
self):
"""Click the discover icon at the bottom"""
return
self.
by_id(
id_name
=
self.
find_icon).
click()
def
click_mine_icon(
self):
"""Click at the bottom of my icon"""
return
self.
by_id(
id_name
=
self.
mine_icon).
click()
def
click_add_icon(
self):
"""点击右上角+号图标"""
return
self.
by_id(
id_name
=
self.
add_icon).
click()
def
click_create_group_btn(
self):
"""点击右上角+号图标"""
return
self.
by_id(
id_name
=
self.
create_group_btn).
click()
- 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.
- 40.
- 41.
- 42.
- 43.
2)chat_page.py
# coding:utf-8
from
pages.
u2_base_page
import
BasePage
class
ChatPage(
BasePage):
def
__init__(
self,
device):
super(
SingleChat,
self).
__init__(
device)
self.
msg_icon
=
"com.zhoulesin.imuikit2:id/icon_msg"
self.
friend_icon
=
"com.zhoulesin.imuikit2:id/icon_friend"
self.
find_icon
=
"com.zhoulesin.imuikit2:id/icon_find"
self.
mine_icon
=
"com.zhoulesin.imuikit2:id/icon_mine"
self.
content
=
"com.zhoulesin.imuikit2:id/et_content"
self.
send_button
=
"com.zhoulesin.imuikit2:id/btn_send"
self.
more_button
=
"com.zhoulesin.imuikit2:id/btn_more"
self.
album_icon
=
"com.zhoulesin.imuikit2:id/photo_layout"
self.
finish_button
=
"com.zhoulesin.imuikit2:id/btn_ok"
def
open_chat_by_name(
self,
name):
"""Open session by session name"""
return
self.
by_text(
text_name
=
name).
click()
def
send_text(
self,
text):
"""发送文本消息"""
return
self.
by_id(
id_name
=
self.
content).
send_keys(
text)
def
click_send_button(
self):
"""点击发送按钮"""
return
self.
by_id(
id_name
=
self.
send_button).
click()
def
click_bottom_side(
self):
"""Click on the session at the bottom of the interface area、唤起键盘"""
return
self.
d.
click(
0.276,
0.973)
def
click_more_button(
self):
"""点击+号按钮"""
return
self.
by_id(
id_name
=
self.
more_button).
click()
def
album_icon_obj(
self):
"""相册图标"""
return
self.
by_id(
id_name
=
self.
album_icon)
def
click_album_icon(
self):
"""Click on the photo album icon to open the photo album"""
return
self.
by_id(
id_name
=
self.
album_icon).
click()
def
select_picture(
self,
range_int):
"""Click on the picture in the album to select the picture"""
return
self.
by_xpath(
'//*[@resource-id="com.zhoulesin.imuikit2:id/recycler"]/android.widget.FrameLayout[%d]'
%
range_int).
click()
def
click_finish_button(
self):
"""点击完成按钮、发送图片"""
return
self.
by_id(
id_name
=
self.
finish_button).
click()
- 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.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
3)group_page.py
from
pages.
u2_base_page
import
BasePage
class
GroupPage(
BasePage):
def
__init__(
self,
device):
super().
__init__(
device)
self.
friend_list
=
"com.zhoulesin.imuikit2:id/rv_friend_list"
self.
friend_list_child
=
"com.zhoulesin.imuikit2:id/iv_select"
self.
confirm_btn
=
"com.zhoulesin.imuikit2:id/tv_confirm"
self.
more_icon
=
"com.zhoulesin.imuikit2:id/img_right"
self.
group_name
=
"群聊名称"
self.
group_name_edit_context
=
"com.zhoulesin.imuikit2:id/et_group_name"
self.
finish_btn
=
"com.zhoulesin.imuikit2:id/tv_btn"
self.
group_icon
=
"com.zhoulesin.imuikit2:id/ll_my_group"
self.
group_list
=
"com.zhoulesin.imuikit2:id/rv_group_list"
self.
group_list_child
=
"com.zhoulesin.imuikit2:id/name"
def
select_group_member(
self):
"""Select group members,全部选择"""
friend_list
=
self.
by_id(
self.
friend_list).
child(
resourceId
=
self.
friend_list_child)
for
i
in
range(
len(
friend_list)):
friend_list[
i].
click()
def
click_confirm_btn(
self):
"""点击确认按钮"""
return
self.
by_id(
id_name
=
self.
confirm_btn).
click()
def
click_more_icon(
self):
"""Click the More icon in the upper right corner of the group chat settings"""
return
self.
by_id(
id_name
=
self.
more_icon).
click()
def
modify_group_name(
self,
group_name):
"""Click the More icon in the upper right corner of the group chat settings"""
self.
by_text(
self.
group_name).
click()
self.
by_id(
self.
group_name_edit_context).
send_keys(
group_name)
self.
by_id(
self.
finish_btn).
click()
def
click_group_icon(
self):
"""Click the group icon,enter group list"""
return
self.
by_id(
self.
group_icon).
click()
- 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.
- 40.
4.编写测试用例
A test case is actually a collection of business logic composed of invoking various page objects,Add some control structures in the middle(选择结构if...else、循环结构for)、断言等,form the final test case.
# coding:utf-8
import
random
import
uiautomator2
as
u2
from
pages.
home_page
import
HomePage
from
pages.
chat_page
import
ChatPage
class
TestYueYun:
def
setup(
self):
device
=
'tkqkssgirgaipblj'
# 设备序列号
apk
=
'com.zhoulesin.imuikit2'
# 包名
self.
d
=
u2.
connect(
device)
self.
d.
app_start(
apk)
self.
home
=
HomePage(
self.
d)
self.
chat
=
ChatPage(
self.
d)
def
test_send_msg(
self):
"""Test sending a text message"""
self.
home.
click_msg_icon()
# Click the bottom message icon,进入主页
self.
chat.
open_chat_by_name(
"张三")
# 点开名为“张三”The contact session
self.
chat.
click_bottom_side()
# Click on the bottom area,唤起键盘
self.
chat.
send_text(
"开始发送消息...")
# 输入框输入文字
self.
chat.
click_send_button()
# 点击发送按钮
for
i
in
range(
1,
10):
# 发送10条消息:1-10,The scope and sent content can also be customized
self.
chat.
send_text(
i)
self.
chat.
click_send_button()
self.
chat.
send_text(
"测试完成!")
self.
chat.
click_send_button()
# 返回主页
while
not
self.
home.
msg_icon_obj().
exists():
self.
d.
press(
"back")
def
test_send_picture(
self):
"""Test sending pictures"""
self.
home.
click_msg_icon()
# Click the bottom message icon,进入主页
self.
chat.
open_chat_by_name(
"群聊一")
# 点开名为“群聊一”的会话
self.
chat.
click_bottom_side()
# Click on the bottom area,唤起键盘
self.
chat.
send_text(
"Test sending pictures...")
# 输入框输入文字
self.
chat.
click_send_button()
# 点击发送(+)号按钮,Pop up album options
for
i
in
range(
2):
# Number of times the icon was sent
# Determine when the album icon does not exist,点击(+)No. switch from keyboard mode to select pictures, videos, etc.
if
not
self.
chat.
album_icon_obj().
exists():
self.
chat.
click_more_button()
self.
chat.
click_album_icon()
# Click the album icon,Enter the album to select a picture
for
a
in
range(
3):
# 一次性选择3张图片
# 从相册childRandomly select from the specified range in the sublist3张图片
self.
chat.
select_picture(
range_int
=
random.
randint(
1,
20))
self.
chat.
click_finish_button()
# 点击发送按钮,发送图片
if
not
self.
chat.
album_icon_obj().
exists():
self.
chat.
click_more_button()
self.
chat.
send_text(
"测试完成!")
self.
chat.
click_send_button()
# 返回主页
while
not
self.
home.
msg_icon_obj().
exists():
self.
d.
press(
"back")
- 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.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
5.运行效果

小结
以上就是利用uiautomator2结合POMode test mobileAPP的一次实践,介绍了:
- POPattern Related Concepts:六大原则、设计模式、POPackage element composition、Common layered models in the industry
- GUI自动化测试:Why do automation is the pros and cons of automation、什么样的项目适合做自动化
- APP自动化测试实践:How to design a project structure、Encapsulates the page base class、define page object、编写测试用例
当然,You can also use some common industryPO库,如page_objects,This makes it easier to design test frameworks、Organizational use cases, etc.,But the core idea remains the same,It is all to achieve the separation of code logic and business logic,to achieve flexible reuse、With the purpose of changing.
More test development practical dry goods,欢迎扫码关注!

边栏推荐
- 【小程序 | 启航篇】一文打通任督二脉
- Open Office XML 格式里如何描述多段具有不同字体设置的段落
- AUTOCAD - reducing spline curve control points, the advanced CAD practice (3)
- 三个绘图工具类详解Paint(画笔)Canvas(画布)Path(路径)
- OSSCore 开源解决方案介绍
- HDU 1520 Anniversary party (tree dp)
- 零基础想自学软件测试,有没有大佬可以分享下接下来的学习书籍和路线?
- Will SQL and NoSQL eventually converge?
- From the product dimension, why can't we fully trust Layer2?
- 使用.NET简单实现一个Redis的高性能克隆版(六)
猜你喜欢

1-IMU参数解析以及选择

关于振弦采集模块及采集仪振弦频率值准确率的问题

Since the media hot style title how to write?Taught you how to write the title

mysql appears: ERROR 1524 (HY000): Plugin '123' is not loaded

即时零售业态下如何实现自动做账?

4 of huawei offer levels, incredibly side is easing the bit in the interview ali?

【勇敢饭饭,不怕刷题之链表】链表反转的几种情况

接口定义与实现

Get started quickly and conquer three different distributed architecture calling schemes

3款不同类型的自媒体免费工具,有效提高创作、运营效率
随机推荐
力扣练习——59 从二叉搜索树到更大和树
Interviewer: How are Dao, Service, Controller, Util, and Model divided in the project?
快速上手,征服三种不同分布式架构调用方案
HCIP ---- VLAN
gpu-admission 源码分析
软件架构简介
微信小程序,全局变量一个地方改变了其他地方的状态也跟着改变。
Licking Exercise - 60 Maximum key-value sum of binary search subtrees
ViT结构详解(附pytorch代码)
Open Office XML 格式里如何描述多段具有不同字体设置的段落
Licking Exercise - 63 Find all anagrams in a string
什么是幂等性?四种接口幂等性方案详解!
Centos7环境使用Mysql离线安装包安装Mysql5.7
【机器学习】浅谈正规方程法&梯度下降
杭电多校-Loop-(不确定性贪心+线段树)
建校仅11年就入选“双一流” ,这所高校是凭什么做到的?
The impact of development mode on testing
力扣练习——62 有效的数独
LeetCode50天刷题计划(Day 19—— 在排序数组中查找元素的第一个和最后一个位置(9.10-10.40)
英特尔推送20220809 CPU微码更新 修补Intel-SA-00657安全漏洞