Python+appium+unittest UI自动化测试资料

什么是UI自动化

自动化分层
  1. 单元自动化测试,指对软件中最小可测试单元进行检查和验证,通常须要借助单元测试框架,如java的JUnit,python的unittest等
  2. 接口自动化测试,主要检查验证模块间的调用返回以及不一样系统、服务间的数据交换,常见的接口测试工具备postman、jmeter、loadrunner等;
  3. UI自动化测试,UI层是用户使用产品的入口,全部功能经过这一层提供给用户,测试工做大多集中在这一层,常见的测试工具备UFT、Robot Framework、Selenium、Appium等;

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

具体UI自动化实现

编程语言的选择

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

自动化测试工具的选择

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

测试框架的选择

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

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

代码框架

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

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

TestRunner

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

# -*- 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

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

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中保存各类测试数据,一行测试数据则为一条用例,这样能够避免测试用例的冗余,毕竟相似登陆就要测试各类状况。在测试用例中将每一个步骤单独分红一个函数,在函数先后都要使用断言判断是否执行成功,若是不成功则后面的步骤都不执行,直接跳出开始吓一条用例的执行。

测试步骤中有数字0一、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。

相关文章
相关标签/搜索