先介绍下什么是POM(摘抄)
Python中的单元测试框架unittest,以后我们所有的测试类文件,都采用unittest来辅助我们进行debug和脚本开发。搞定了debug机制和确定了unittest来进行创建和管理我们的自动化测试脚本,接下来我们来考虑下,框架设计中一种很普遍的设计思想-POM(Page Object Model)
POM,中文字母意思是,页面对象模型,POM是一种最近几年非常流行的自动化测试模型,或者思想,POM不是一个框架,就是一个解决问题的思想。采用POM的目的,是为了解决前端中UI变化频繁,从而造成测试自动化脚本维护的成本越来越大。
采取了POM设计思路和不采取的区别,左侧把测试代码和页面元素都写在一个类文件,如果需要更改页面,那么就要修改页面元素定位,从而要修改这个类中测试代码,这个看起来和混乱。右侧,采取POM后,主要的区别就是,把页面元素和业务逻辑和测试脚本分离出来到两个不同类文件。ClassA只写页面元素定位,和业务逻辑代码操作的封装,ClassB只写测试脚本,不关心如何元素定位,只写调用ClassA的代码去覆盖不同的测试场景。如果前端页面发生变化,只需要修改ClassA的元素定位,而不需要去修改ClassB中的测试脚本代码。
POM主要有以下优点:
1.把web ui对象仓库从测试脚本分离,业务代码和测试脚本分离。
2.每一个页面对应一个页面类,页面的元素写到这个页面类中。
3.页面类主要包括该页面的元素定位,和和这些元素相关的业务操作代码封装的方法。
4.代码复用,从而减少测试脚本代码量。
5.层次清晰,同时支持多个编写自动化脚本开发,例如每个人写哪几个页面,不影响他人。
6.建议页面类和业务逻辑方法都给一个有意义的名称,方便他人快速编写脚本和维护脚本。
关于基类,是这样定义的:把一些常见的页面操作的selenium封装到base_page.py这个类文件,以后每个POM中的页面类,都继承这个基类,这样每个页面类都有基类的方法
然后介绍下uiautomator2下POM的实现
framework:代码复用,从而减少测试脚本代码量
logs:运行日志
modules:业务代码
screenshots:测试失败截图
testsuites:测试脚本
test_report:测试报告
tools:其他第三方库
# coding=utf-8
import time
import unittest
import uiautomator2 as u2
from framework.failed import Failed
from framework.logger import Logger
logger = Logger(logger="BrowserEngine").getlog()
d = u2.connect()
#d = u2.connect('192.168.1.173')
now=time.strftime("%Y%m%d%H%M%S",time.localtime())
class Entrance(unittest.TestCase):
def entrance_test(self):
logger.info("执行进入小程序.")
print(now + ":" + "进入小程序")
d.app_start("com.tencent.mm")
logger.info("打开微信.")
time.sleep(1)
while True:
logger.info("判断是否到微信主界面.")
if d(resourceId="com.tencent.mm:id/jb").exists:
logger.info("当前在微信主界面,执行测试微信小程序.")
d(resourceId="com.tencent.mm:id/jb").click()
time.sleep(1)
d(text=u'搜索').set_text("火眼看看")
time.sleep(1)
d(resourceId="com.tencent.mm:id/qm").click()
time.sleep(2)
'''
while True:
if d.xpath("//android.widget.TextView[@text='拍照识别']").exists:
logger.info("已经到达识别界面")
break
else:
logger.info("进入小程序不是识别界面,寻找识别界面")
d.press("back")
time.sleep(2)
# 断言
if (d(text=u"点我了解").exists):
print(now + ":" + '进入小程序成功')
else:
print(now + ":" + '进入小程序失败')
Failed.failed_test(self)
'''
break
else:
logger.info("在非微信主界面,执行返回.")
d.press("back")
time.sleep(1)
3.可复用性代码(framework)
执行结果失败时截图和标记用例失败(failed.py)
# coding=utf-8
import time
import unittest
import os
import uiautomator2 as u2
from framework.logger import Logger
logger = Logger(logger="BasePage").getlog()
d = u2.connect()
#d = u2.connect('192.168.1.173')
now=time.strftime("%Y%m%d%H%M%S",time.localtime())
class Failed(unittest.TestCase):
def failed_test(self):
logger.error("出现错误!" )
now = time.strftime("%Y%m%d%H%M%S", time.localtime())
report_path = os.path.dirname(os.path.abspath('.')) + '/screenshots/'
picture = report_path + now + "error.jpg"
d.screenshot(picture)
a = "P"
b = "F"
self.assertEqual(a, b)
判断手机是否需要解锁(lock.py)
# coding=utf-8
import time
import unittest
import uiautomator2 as u2
from framework.logger import Logger
logger = Logger(logger="BrowserEngine").getlog()
d = u2.connect()
#d = u2.connect('192.168.1.173')
now=time.strftime("%Y%m%d%H%M%S",time.localtime())
class Lock(unittest.TestCase):
def lock_test(self): #手机屏幕解锁
logger.info("检查是否需要解锁.")
if d(resourceId="com.smartisanos.keyguard:id/desk_kg").exists: #检查是否存在灭屏状态的元素属性
logger.info("需要解锁.")
d.screen_on()
time.sleep(3)
d.swipe_points([(0.485, 0.708), (0.481, 0.286)], 0.05) # 滑动解锁界面
time.sleep(1)
d.swipe_points([(0.762, 0.394), (0.489, 0.525), (0.777, 0.529), (0.503, 0.651), ], 0.05) # 解九宫锁
time.sleep(1)
logger.info("解锁成功.")
else:
logger.info("当前未锁屏,无需解锁.")
运行日志(logger.py)
# _*_ coding: utf-8 _*_
import logging
import os.path
import time
class Logger(object):
def __init__(self, logger):
'''''
指定保存日志的文件路径,日志级别,以及调用文件
将日志存入到指定的文件中
'''
# 创建一个logger
self.logger = logging.getLogger(logger)
self.logger.setLevel(logging.DEBUG)
# 创建一个handler,用于写入日志文件
rq = time.strftime('%Y%m%d%H%M', time.localtime(time.time()))
# log_path = os.path.dirname(os.getcwd()) + '/Logs/' # 项目根目录下/Logs 保存日志
log_path = os.path.dirname(os.path.abspath('.')) + '/logs/'
# 如果case组织结构式 /testsuit/featuremodel/xxx.py , 那么得到的相对路径的父路径就是项目根目录
log_name = log_path + rq + '.log'
fh = logging.FileHandler(log_name)
fh.setLevel(logging.INFO)
# 再创建一个handler,用于输出到控制台
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
# 定义handler的输出格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# 给logger添加handler
self.logger.addHandler(fh)
self.logger.addHandler(ch)
def getlog(self):
return self.logger
滑动屏幕,寻找目标(sliding.py)
# coding=utf-8
import time
import unittest
import uiautomator2 as u2
from framework.failed import Failed
from framework.logger import Logger
logger = Logger(logger="BrowserEngine").getlog()
d = u2.connect()
#d = u2.connect('192.168.1.173')
now=time.strftime("%Y%m%d%H%M%S",time.localtime())
class Sliding(unittest.TestCase):
def sliding_test(self):
logger.info("滑动查找目标.")
print(now + ":" + "滑动开始")
count=1
while count<=3:
print('第', count, "次滑动.")
count=count+1
logger.info("滑动查找目标")
if d(className="android.view.View123", instance=450).exists:
logger.info("找到目标")
time.sleep(1)
logger.info('查看目标属性.')
d(className="android.view.View", instance=450).click()
time.sleep(3)
if d(text=u"最大宽度:").exists:
print(now + ":" + '查看病害属性成功')
time.sleep(3)
else:
print(now + ":" + '查看属性成失败')
time.sleep(3)
Failed.failed_test(self)
time.sleep(3)
break
else:
logger.info('未找到目标,继续滑动查找.')
d.swipe_points([(0.457, 0.674), (0.465, 0.336) ], 0.08) # 滑动界面
time.sleep(3)
# coding=utf-8
import time
import unittest
import uiautomator2 as u2
from modules.entrance import Entrance
from framework.lock import Lock
from modules.location import Location
from modules.identification import Identification
from framework.logger import Logger
logger = Logger(logger="BrowserEngine").getlog()
d = u2.connect()
#d = u2.connect('192.168.1.173')
class Relicl(unittest.TestCase):
#@classmethod
def setUp(self):
self.watcherson()
#Lock.test_lock(self)
print ("----------SetUp -----\n")
#@classmethod
def tearDown(self):
d.watchers.remove()
print ("-----------TearDown----\n")
def watcherson(self):
d.watcher(u'注意').when(text=u'你在此次选择后可以到“手机管理 - 权限管理”中修改此项设置').click(text=u'允许', className='android.widget.Button',resourceId='android:id/button1')
d.watcher(u'无响应').when(text=u'关闭应用').click(text=u'关闭应用', className='android.widget.Button')
d.watcher(u'删除').when(description=u'关闭').click(description=u'关闭', className='android.widget.ImageView',resourceId='com.android.mms:id/quick_message_close_sign')
d.watcher(u'关闭').when(description=u'关闭').click(description=u'关闭', className='android.widget.ImageView',resourceId='com.android.systemui:id/pop_up_dialog_quick_message_close_sign')
d.watchers.run()
pass
def test_entrance(self):
Lock.lock_test(self)
Entrance.entrance_test(self)
while True:
logger.info("尝试执行选择位置")
try:
Location.location_test(self)
logger.info("可以执行")
break
except:
logger.info("不能执行选择位置,寻找选择位置界面继续执行")
d.press("back")
def test_identification(self):
logger.info("开始执行识别")
for count in list(range(1,6)):
Identification.identification_test(self)
启动测试代码(TestRunner.py)
# coding=utf-8
import HTMLTestRunner
import os
import unittest
import time
if __name__ =='__main__':
suite = unittest.TestLoader().discover("testsuites")
# 初始化一个HTMLTestRunner实例对象,用来生成报告
# 设置报告文件保存路径
report_path = os.path.dirname(os.path.abspath('.')) + '/test_report/'
# 获取系统当前时间
now = time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime(time.time()))
# 设置报告名称格式
HtmlFile = report_path + now + "HTMLtemplate.html"
fp = open(HtmlFile, "wb")
# 构建suite
runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title=u"自动化测试报告", description=u"用例测试情况")
# 开始执行测试套件
runner.run(suite)
fp.close()