什么是UI自动化

自动化分层

单元自动化测试,指对软件中最小可测试单元进行检查和验证,一般需要借助单元测试框架,如java的JUnit,python的unittest等

接口自动化测试,主要检查验证模块间的调用返回以及不同系统、服务间的数据交换,常见的接口测试工具有postman、jmeter、loadrunner等;

UI自动化测试,UI层是用户使用产品的入口,所有功能通过这一层提供给用户,测试工作大多集中在这一层,常见的测试工具有UFT、Robot Framework、Selenium、Appium等;

大部分公司只要求到第二层,及接口自动化测试,主要因为UI层经常改动,代码可维护性弱,且如果需求经常变更时,对代码逻辑也要经常改动。 但如果对于一些需求较为稳定,测试重复性工作多的使用UI自动化则能大量减少人力物力在一些简单的手动重复工作上。

具体UI自动化实现

编程语言的选择

python,是一门可读性很强,很容易上手的编程语言,对于测试来说,可以在短时间内学会,并开始写一下小程序。而且相对其Java来说,python可以用20行代码完成Java100行代码的功能,并且避免了重复造轮子。

自动化测试工具的选择

appium,是一个开源的自动化测试工具,支持android、ios、mobile web、混合模式开发。在selenium的基础上增加了对手机客户端的特定操作,例如手势操作和屏幕指向。

测试框架的选择

unittest,是python的单元测试框架,使用unittest可以在有多个用例一起执行时,一个用例执行失败,其他用例还能继续执行。 且unittest引入了很多断言,则测试过程中十分方便去判读测试用例的执行失败与否。

PageObject,是一种设计模式,一般使用在selenium自动化测试中。通过对页面元素、操作的封装,使得在后期对代码的维护减少了很多冗余工作。

代码框架

框架中主要是两大块,分别是result和testset,result用来存放执行用例后的html报告和日志以及失败时的截图。

result 中主要以日期为文件夹,里面文件为每次执行用例的测试报告及日志,以及image文件夹,保存用例执行失败时的截图。

TestRunner

首先介绍testRunner,这是整个系统的运行的开始。

-- coding: utf-8 --

import threading

import unittest

from testSet.testcase.test_flight import Test_flight as testcase1

from testSet.testcase.test_test import test as testcase

import testSet.common.report as report

import testSet.page.basePage as basePage

from testSet.common.myServer import myServer

import time

from testSet.common.log import logger

import testSet.util.date as date

createReport = report.report() # 创建测试报告

class runTest():

def init(self):

pass

def run(self, config, device):
time.sleep(8)
basePage.setconfig(config, device) # 将设备号和端口号传给basepage
suite = unittest.TestLoader().loadTestsFromTestCase(testcase1) # 将testcase1中的测试用例加入到测试集中
runner = createReport.getReportConfig()
runner.run(suite) # 开始执行测试集
ms.quit() # 退出appium服务 def getDriver(self, driver):
return driver

class myThread(threading.Thread):

def init(self, device, config):

threading.Thread.init(self)

self.device = device

self.config = config

def run(self):
if __name__ == '__main__':
test = runTest()
test.run(self.config, self.device)
# test.driverquit()
createReport.getfp().close() # 关闭测试报告文件
log = logger(date.today_report_path).getlog()
log.info(self.device + "test over")

if name == 'main':

try:

devices = ["192.168.20.254:5555"]

theading_pool = []

for device in devices: # 根据已连接的设备数,启动多个线程

ms = myServer(device)

config = ms.run()

t = myThread(device, config)

theading_pool.append(t)

for t in theading_pool:

t.start()

time.sleep(5)

for t in theading_pool:

        t.join()
except:
print("线程运行失败")
raise

testRunner包括runTest和myThead两个类,myThead负责创建线程,runTest在线程中执行测试用例。

common

不具体说每个文件的作用及代码了,举例两个比较重要的。

myServer

为了可以实现多设备并行测试,不能手动启动appium客户端后在执行用例,这样只有1个设备分配到了appium的端口,也只能执行1个设备。因此需要用代码实现启动appium服务,并为不同的设备分配不同的端口。

import os

import unittest

from time import sleep

from .driver import driver

from selenium.common.exceptions import WebDriverException

import subprocess

import time

import urllib.request, urllib.error, urllib.parse

import random

import socket

from .log import logger

import testSet.util.date as date

启动appium

class myServer(object):

def init(self, device):

# self.appiumPath = "D:\Appium"

self.appiumPath = "F:\Appium"

self.device = device

self.log = logger(date.today_report_path).getlog()

def isOpen(self, ip, port):  # 判断端口是否被占用
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((ip, int(port)))
s.shutdown(2) # shutdown参数表示后续可否读写
# print '%d is ok' % port
return True
except Exception as e:
return False def getport(self): # 获得端口号
port = random.randint(4700, 4900)
# 判断端口是否被占用
while self.isOpen('127.0.0.1', port):
port = random.randint(4700, 4900)
return port def run(self):
"""
启动appium服务
:return: aport 端口号
"""
aport = self.getport()
bport = self.getport()
self.log.info("--------appium server start----------")
# startCMD = "node D:\\Appium\\node_modules\\appium\\bin\\appium.js"
# startCMD = "node Appium\\node_modules\\appium\\bin\\appium.js"
cmd = 'appium' + ' -p ' + str(aport) + ' --bootstrap-port ' + str(bport) + ' -U ' + str(self.device) + " --session-override"
rootDirection = self.appiumPath[:2] # 获得appium服务所在的磁盘位置
# 启动appium
# os.system(rootDirection + "&" + "cd" + self.appiumPath + "&" + startCMD)
try:
subprocess.Popen(rootDirection + "&" + "cd" + self.appiumPath + "&" + cmd, shell=True) # 启动appium服务
return aport
except Exception as msg:
self.log.error(msg)
raise def quit(self):
"""
退出appium服务
:return:
"""
os.system('taskkill /f /im node.exe')
self.log.info("----------------appium close---------------------")

driver

driver 负责连接手机,并启动测试app

-- coding: utf-8 --

from appium import webdriver

from .log import logger

from . import report

import os

import testSet.util.date as date

import appium

from selenium.common.exceptions import WebDriverException

dr = webdriver.Remote

class driver(object):

def init(self, device):

self.device = device

self.desired_caps ={}

self.desired_caps['platformName'] = 'Android'

self.desired_caps['platformVersion'] = '5.0.2'

self.desired_caps['udid'] = self.device

self.desired_caps['deviceName'] = 'hermes'

self.desired_caps['noReset'] = True

self.desired_caps['appPackage'] = 'com.igola.travel'

self.desired_caps['appActivity'] = 'com.igola.travel.ui.LaunchActivity'

self.log = logger(date.today_report_path).getlog()

def connect(self, port):
url = 'http://localhost:%s/wd/hub' % str(port)
self.log.debug(url)
try:
global dr
dr = webdriver.Remote(url, self.desired_caps)
self.log.debug("启动接口为:%s,手机ID为:%s" % (str(port), self.device))
except Exception:
self.log.info("appium 启动失败")
os.popen("taskkill /f /im adb.exe")
raise def getDriver(self):
return dr

report

使用htmlTestRunner生成html测试报告

-- coding: utf-8 --

import HTMLTestRunner

import time

import os

import testSet.util.date as date

class report:

def init(self):

self.runner = ""

self.fp = ""

self.sendReport()

def sendReport(self):
now = time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime(time.time()))
if not os.path.isdir(date.today_report_path):
os.mkdir(date.today_report_path)
report_abspath = os.path.join(date.today_report_path, now + '_report.html')
self.fp = open(report_abspath, 'wb')
self.runner = HTMLTestRunner.HTMLTestRunner(
stream=self.fp, title="appium自动化测试报告", description="用例执行结果:") def getReportConfig(self):
return self.runner def getfp(self):
return self.fp

测试用例

实现机票预订流程

from ddt import ddt, data, unpack

import testSet.util.excel as excel

from . import testcase

import unittest

from testSet.common.sreenshot import screenshot

from testSet.page.homePage import homePage

from testSet.page.flightPage import FlightPage

from testSet.page.timelinePage import TimelinePage

from testSet.page.summaryPage import SummaryPage

from testSet.page.bookingPage import BookingPage

from testSet.page.bookingDetailPage import BookingDetailPage

from testSet.page.paymentPage import PaymentPage

from testSet.page.orderDetailPage import OrderDetailPage

from testSet.page.orderListPage import OrderListPage

Excel = excel.Excel("flight", "Sheet1")

isinit = False

@ddt

class Test_flight(testcase.Testcase):

def setUp(self):

super().setUp()

self.cabin = ""

@screenshot
def step01_go_flightpage(self, expected_result):
"""
跳转到找飞机页面
"""
homePage().go_flightPage() @screenshot
def step02_search(self, expected_result):
"""搜索跳转
"""
flight = FlightPage()
self.assertTrue(flight.verify_page(), "找机票页面进入错误")
flight.select_ways(expected_result["type"])
flight.select_cabin(expected_result["cabin"])
FlightPage().search() @screenshot
def step03_timeline(self, expected_result):
"""
验证timeline的航程详情是否正确
"""
timeline = TimelinePage()
for type in range(0, int(expected_result["type"])):
self.assertTrue(timeline.verify_page(), "timeline页面进入错误")
# actual_result = timeline.get_flight_info()
# self.assertDictContainsSubset(actual_result, expected_result, "航程详情错误")
timeline.select_flight(expected_result["price"]) @screenshot
def step04_summary(self, expected_result):
"""
验证summary页面的航程详情是否正确
:return:
"""
summary = SummaryPage()
self.assertTrue(summary.verify_page(), "summary页面进入错误")
summary.collapse()
actual_result = summary.get_flight_info(expected_result["type"])
trips = []
for trip in expected_result.keys():
if "trip_type" in trip:
trips.append(expected_result[trip])
leg_cabin = summary.check_cabin(expected_result["type"], *trips)
if isinstance(leg_cabin, tuple):
for key in leg_cabin[1].keys():
actual_result[key] = leg_cabin[1][key]
self.cabin = leg_cabin[0]
else:
self.cabin = leg_cabin
self.assertDictContainsSubset(actual_result, expected_result, "航程详情错误")
summary.collapse()
summary.select_ota() @screenshot
def step05_go_booking(self, expected_result):
"""
验证booking航程详情是否正确
"""
booking = BookingPage()
self.assertTrue(booking.verify_page(), "booking页面进入错误")
self.assertEqual(self.cabin, booking.check_cabin())
booking.go_detail() @screenshot
def step06_booking_detail(self, expected_result):
booking_detail = BookingDetailPage()
self.assertTrue(booking_detail.verify_page(), "booking航程详情页面进入错误")
actual_result = booking_detail.get_flight_info(expected_result["type"])
self.assertDictContainsSubset(actual_result, expected_result, "航程详情错误")
booking_detail.back_to_booking() @screenshot
def step07_submit(self, expected_result):
booking = BookingPage()
booking.submit_order() @screenshot
def step08_payment(self, expected_result):
payment = PaymentPage()
self.assertTrue(payment.verify_page(), "payment 页面进入错误")
self.assertEqual(self.cabin, payment.check_cabin())
payment.pay_later() @screenshot
def step09_go_order(self, expected_result):
homePage().go_order()
self.assertTrue(OrderListPage().verify_page(), "订单列表页面进入错误")
OrderListPage().go_order_detail() @screenshot
def step10_check_order(self, expected_result):
detail = OrderDetailPage()
self.assertTrue(detail.verify_page(), "订单详情页面进入错误")
detail.collapse()
actual_result = detail.get_flight_info(expected_result["type"])
self.assertDictContainsSubset(actual_result, expected_result, "航程详情错误") def _steps(self):
for name in sorted(dir(self)):
if name.startswith("step"):
yield name, getattr(self, name) @data(*Excel.next())
def test_flights_detail(self, data):
for name, step in self._steps():
step(data)

Test_filght使用ddt,以数据为驱动,在excel中保存各种测试数据,一行测试数据则为一条用例,这样可以避免测试用例的冗余,毕竟类似登录就要测试各种情况。在测试用例中将每个步骤单独分成一个函数,在函数前后都要使用断言判断是否执行成功,如果不成功则后面的步骤都不执行,直接跳出开始吓一条用例的执行。

测试步骤中有数字01、02等,是为了确定测试的步骤顺序,

def _steps(self):

for name in sorted(dir(self)):

if name.startswith("step"):

yield name, getattr(self, name)

这一步则用来对测试用例中所有的以step开头的函数进行排序,但需要注意的是,如果步骤超过10个,需要在1前面加上0,成为01,因为在函数名中,数字是以string格式保存的,排序时,会先比较第一位,再比较第二位,这样执行顺序可能会变成1,10,11...,2,3。所有需要在1前面加上0,这样执行顺序就正确了。

在测试用例中只显示每一步操作,具体判断逻辑代码都交由每个页面的具体实现代码完成,即page文件夹中的文件。

而page文件也只执行相关业务判断逻辑模块,具体调用driver的手势操作,例如click,swipe等,则交给basepage完成,page继承于basepage

Python+appium+unittest UI自动化测试的更多相关文章

  1. Python appium搭建app自动化测试环境

    appium做app自动化测试,环境搭建是比较麻烦的. 也是很多初学者在学习app自动化之时,花很多时间都难跨越的坎. 但没有成功的环境,就没有办法继续后续的使用. 在app自动化测试当中,我们主要是 ...

  2. 基于Appium的UI自动化测试

    为什么需要UI自动化测试 移动端APP是一个复杂的系统,不同功能之间耦合性很强,很难仅通过单元测试保障整体功能.UI测试是移动应用开发中重要的一环,但是执行速度较慢,有很多重复工作量,为了减少这些工作 ...

  3. 基于 appium 的 UI 自动化测试

    其中主要的目录和文件为: /MPTestCases ----------- 存放测试用例 /errorScreenShot ------------ 用例执行失败生成的错误截图 startTest.p ...

  4. django+appium实现UI自动化测试平台---构思版

             背景 UI自动化,在进行的过程中,难免会遇到平台化, 在实际的工作中,有的领导也会想要实现自动化测试的平台化.自动化平台化后,有了更为实际的成果, 在做UI自动化,很想吧现在的自动化 ...

  5. python+appium+unittest自动化测试框架环境搭建

    一.基础软件准备 1.python 版本最新版本,python的IDE使用pycharm.具体的下载链接: python https://www.python.org/ pycharm:https:/ ...

  6. 基于Python3 + appium的Ui自动化测试框架

    UiAutoTest 一.概要 数据驱动的Ui自动化框架 二.环境要求 框架基于Python3 + unittest + appium 运行电脑需配置adb.aapt的环境变量,build_tools ...

  7. Appium App UI 自动化测试理论知识

    (一)App自动化测试背景 随着移动终端的普及,手机应用越来越多,也越来越重要.App的回归测试用例数量越来越多,全量回归也越来越消耗时间.另外移动端碎片化严重(碎片化:兼容性测试,手机品牌多样.An ...

  8. Python+Selenium进行UI自动化测试项目中,常用的小技巧2:读取配置文件(configparser,.ini文件)

    在自动化测试项目中,可能会碰到一些经常使用的但 很少变化的配置信息,下面就来介绍使用configparser来读取配置信息config.ini 读取的信息(config.ini)如下: [config ...

  9. python+appium+unittest

    一个流行语言,一个主流工具,一个实用框架: For android 实例如下: import unittest from appium import webdriver from time impor ...

随机推荐

  1. @bzoj - 2388@ 旅行规划

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 请你维护一个序列,支持两种操作: (1)某个区间 [x, y] ...

  2. Java 内存模型及GC原理 (转)

      来源:http://blog.csdn.net/ithomer/article/details/6252552 一个优秀Java程序员,必须了解Java内存模型.GC工作原理,以及如何优化GC的性 ...

  3. 命名分组(?<name>....)

    捕获组分为: 普通捕获组(Expression) 命名捕获组(?Expression) 普通捕获组 从正则表达式左侧开始,每出现一个左括号"("记做一个分组,分组编号从 1 开始. ...

  4. Oracle/PLSQL存储过程详解

    原文链接:https://blog.csdn.net/zezezuiaiya/article/details/79557621 Oracle/PLSQL存储过程详解 2018-03-14 17:31: ...

  5. poj 3528 Ultimate Weapon (3D Convex Hull)

    3528 -- Ultimate Weapon 一道三维凸包的题目,题目要求求出三维凸包的表面积.看懂了网上的三维凸包的代码以后,自己写的代码,跟网上的模板有所不同.调了一个晚上,结果发现错的只是数组 ...

  6. 【微信小程序】下载并预览文档——pdf、word、excel等多种类型

    .wxml文件 <view data-url="https://XXX/upload/zang." data-type="excel" catchtap= ...

  7. Python--day22--面向对象的交互

    Python里面自带的类和对象: 类名的作用: 类里面的与属性相关的对象self的运用: 实例化:就是创建一个对象 调用方法,类名.方法名(对象名) 执行步骤: 简写:alex.walk()等价于Pe ...

  8. Bi-LSTM-CRF for Sequence Labeling

    做了一段时间的Sequence Labeling的工作,发现在NER任务上面,很多论文都采用LSTM-CRFs的结构.CRF在最后一层应用进来可以考虑到概率最大的最优label路径,可以提高指标. 一 ...

  9. 算法提高 密码锁 (BFS)

    问题描述 你获得了一个据说是古代玛雅人制作的箱子.你非常想打开箱子看看里面有什么东西,但是不幸的是,正如所有故事里一样,神秘的箱子出现的时候总是会挂着神秘的锁. 这个锁上面看起来有  N  个数字,它 ...

  10. C# 简单读取文件

    本文告诉大家如何使用最少的代码把一个文件读取二进制,读取为字符串 现在写了一些代码,想使用最少代码来写简单的读文件,所以我就写了这个文章 读取文件为二进制 private byte[] ReadFil ...