unittest

继承TestCase,以test开头的方法均为一条测试用例,可以添加setup和teardown方法

运行方式

1、unittest.main()会自动执行每个继承unittest.TestCase类的test开头方法。

2、创建TestSuite,并添加用例,利用TextTestRunner的run方法执行测试套

3、通过unittest.defaultTestLoader.discover('./', pattern='*.py')方法查找生成测试套,利用TextTestRunner的run方法执行测试套

报告模板

使用pycharm自带模板

使用BeautifulReport模板

安装BeautifulReport, 使用BeautifulReport类,传入测试套对象,返回BeautifulReport对象,使用他的report方法生成

from BeautifulReport import BeautifulReport as bf

run = bf(tss)

run.report(filename, description)

pytest

安装pytest

同样需要用例名以test开头,结合assert 断言

运行方式

1、python py文件

需要pytest.main([py文件名])

2、pytest py文件

不需要pytest.main([py文件名])

用例编写

可以以单个函数作为用例,也可以以类中的函数作为用例(不需要继承任何类,但是类名默认要以Test开头)

setup和teardown函数支持函数级别和类级别

函数级别

setup_module和teardown_module

setup_function和teardown_function

类级别

setup_class和teardown_class

setup_method和teardown_method

pytest配置文件

pytest的配置文件名称为pytest.ini

[pytest]

addopts = -s ... # 配置pytest命令行运行参数

testpaths = ./scripts # 配置测试搜索的路径

pythonfiles = test*.py # 配置测试搜索的文件名

pythonclasses = Test* # 配置测试搜索的测试类名

pythonfunctions = test* # 配置测试搜索的测试函数名

常用插件

pytest-html 生成html xml测试报告

pytest-ordering 控制函数执行顺序

标记于被测函数, @pytest.mark.run(order=x)

0 > 较小整数> 较大的整数 > 无标记 > 较小的负数 > 较大的负数

pytest-rerunfailures 配置失败函数的重试次数

pytest常用命令行参数(可配置在配置文件)

-s:输出调试信息,包括print打印的信息。

-s是输出程序运行信息

—html=./report.html在当前目录下生成report.html文件(需要安装pytest-html)

—reruns 2 : 失败重试2次(需要安装pytest-rerunfailures)

-v 显示更详细的信息。

pytest fixtures使用

@pytest.fixture(scope='function', params=None, autouse=False, ids=None, name=None) 

 - scope: 被标记方法的作用域; 

  "function": 默认值,表示每个测试方法都要执行一次 

  "class": 作用于整个类, 表示每个类的所有测试方法只运行一次

   "module": 作用于整个模块, 每个module的所有测试方法只运行一次. 

   "session": 作用于整个session, 每次session只运行一次. 

- params: list类型,默认None, 接收参数值,对于param里面的每个值,fixture都会去遍历执行一次.

- autouse: 是否自动运行,默认为false,

以上的所有参数也可以不传

基本使用

作为参数应用

作为函数应用

@pytest.mark.usefixtures("before")

带参数和返回值

@pytest.fixture(params=[1, 2, 3]) 

def need_data(request): # 传入参数request 系统封装参数 

  return request.param # 取列表中单个值,默认的取值方式 

可以通过yield实现setup和teardown的效果

pytest其他函数方法

@pytest.mark.skipif(condition, reason='xxx') 跳过测试函数

@pytest.mark.xfail(condition, reason="xxx") 标记为预期失败函数

@pytest.mark.parametrize("a", [3,6]) def test_b(self, a):

print("test data:a=%d" % a)

assert a%3 == 0

函数数据参数化,参数值为N个,测试方法就会运行N次

pytest使用allure

安装allure-pytest插件

pytest配置文件参数加上--alluredir=path 指定生成结果保存的目录名

安装allure github

allure serve path 生成临时目录打开测试报告

allure generate path -o path1 根据path下结果生path1目录包含测试报告

allure open path1 打开测试报告

allure特性

@allure.feature(功能名称)

@allure.story(子功能名称 )

@allure.title(测试标题)

@allure.description(用例描述)

@allure.step(‘步骤’)

@allure.attach(‘附件’)

@allure.severity(用例等级)

allure.severity_level.*

@allure.issue('140', '缺陷描述')

--allure-link-pattern=issue:http://www.mytesttracker.com/issue/{}

@allure.testcase("用例连接", '测试用例标题')

@allure.link("网址", name='Click me')

yaml解析和更改

安装pyyaml

yaml.safe_load(f)

yaml.safe_dump(data, f)

ADB常用命令

adb --help

adb start-server

adb kill-server

adb devices

获取系统版本
adb shell getprop ro.build.version.release

adb push 电脑端⽂件路径/需要发送的文件,手机端存储的路径
adb push C:\Users\win\Desktop\xx.png /sdcard

adb pull 手机端的路径/拉取文件名 电脑端存储文件路径
adb pull /sdcard/xx.png C:\Users\win\Desktop

查看手机运行日志
adb logcat

进入到手机终端
adb shell

获取app启动包名和启动名*******
adb shell dumpsys window windows | findstr mFocusedApp

adb install 路径/xxx.apk

adb uninstall app

获取app启动时间
adb shell am start -W 包名/.启动名

Appium

环境搭建

bluestacks模拟器安装

127.0.0.1:5555

appuim Desktop安装

https://github.com/appium/appium-desktop

安装后启动服务器即可

appuim inspect安装

https://github.com/appium/appium-inspector

远程主机 127.0.0.1

远程端口 4723

远程路径 /wd/hub

添加capability, 然后启动会话

{
  "platformName": "Android",
  "platformVersion": "9",
  "deviceName": "127.0.0.1:5555",
  "automationName": "UiAutomator2"
}

安装appium的python

peotry add Appium-Python-Client

HelloWorld

# 从appium库里面导入driver对象,帮助我们进行脚本和手机间交互
from appium import webdriver
# 导入time
import time
from appium.options.android import UiAutomator2Options
# server 启动参数
capabilities = {
        'platformName': 'Android',
        'platformVersion': '9',
        'deviceName': '127.0.0.1:5555',
        'appPackage': 'com.android.settings',
        'appActivity': '.Settings',
        "automationName": "UiAutomator2"
    }
desired_caps = {}
options = UiAutomator2Options().load_capabilities(capabilities)
driver = webdriver.Remote('http://localhost:4723/wd/hub', options=options) #声明driver对象,让手机完成脚本操作
time.sleep(5)
#关闭驱动对象
driver.quit()

Appium API

基础操作

# 判断app是否已经安装
if not driver.is_app_installed('org.qtproject.example.appHelloAndroid'):
    # 安装apk
    driver.install_app(os.path.abspath('1.apk'))
# 卸载app
driver.remove_app('org.qtproject.example.appHelloAndroid')

with open('1.apk', 'rb') as f:
    data = f.read()
    b64_data = base64.b64encode(data)
    # 发送文件到手机, 发送base64编码的字符串
    driver.push_file('/sdcard/Download/1.apk', str(b64_data, 'utf-8'))

# 从手机里面拉取文件, 返回base64编码的字符串
content = driver.pull_file('/sdcard/Download/1.apk')
with open('2.apk', 'wb') as f:
    f.write(base64.b64decode(content))

# 获取当前屏幕内的元素结构
page_data = driver.page_source
print(page_data)

元素定位

id

elements 即可定位一组元素

from appium.webdriver.common.appiumby import AppiumBy
driver.find_element(AppiumBy.ID, 'com.bluestacks.gamecenter:id/main_et_search')

class

elements 即可定位一组元素

from appium.webdriver.common.appiumby import AppiumBy
driver.find_element(AppiumBy.CLASS_NAME, 'android.widget.ImageView')

xpath

elements 即可定位一组元素

from appium.webdriver.common.appiumby import AppiumBy
driver.find_element(AppiumBy.XPATH, '//android.widget.TextView[@text="返回"]')

显示等待

一个超时时间范围内,每隔一段时间去搜索一次元素是否存在, 如果存在返回定位对象,如果不存在直到超时时间到达,报超时异元素信息操作元素事件操作

from selenium.webdriver.support.ui import WebDriverWait
try:
    # 查找元素前时间
    print(time.strftime("%H:%M:%S",time.localtime()))
    # 显示等待
    WebDriverWait(driver,15,1).until(lambdax:x.find_element_by_id("com.android.settings:id/search")).click()
except Exception as e:
    # 查找元素后时间
    print(time.strftime("%H:%M:%S",time.localtime()))

隐式等待

定位某一元素失败,那么就会触发隐式等待有效时长,在指定时长内加载完毕,则继续执行,否则抛出异常,如果元素在第一次就定位到则不会触发隐式等待时长

driver.implicitly_wait(10)

StaleElementReferenceException

当获取的元素因为某种原因不可用时进行使用会抛出StaleElementReferenceException异常

解决办法:重新获取元素

from selenium.common.exceptions import StaleElementReferenceException

try:
        phb = driver.find_element(AppiumBy.XPATH, '//android.widget.TextView[@text="排行榜"]')
        phb.click()
except StaleElementReferenceException as e:
        print(e)
        phb = driver.find_element(AppiumBy.XPATH, '//android.widget.TextView[@text="排行榜"]')
        phb.click()

元素信息操作

search = driver.find_element(AppiumBy.ID, 'com.bluestacks.gamecenter:id/main_et_search')
# 发送数据到输入框
search.send_keys('王者')
# 清空输入框内容
search.clear()
search.send_keys('王者荣耀')

# 获取元素的文本内容
print(search.text)

# 获取元素的属性值
print(search.get_attribute('resource-id'))

# 获取元素在屏幕上的坐标
print(search.location)

# 获取app包名和启动名
print(driver.current_package)
print(driver.current_activity)

元素事件操作

end = driver.find_element(AppiumBy.XPATH, '//android.widget.TextView[@text="精品游戏"]')
begin = driver.find_element(AppiumBy.XPATH, '//android.view.View[@content-desc="首页"]')
endlocation = end.location
beginlocation = begin.location
# swipe滑动事件 从一个坐标位置滑动到另⼀个坐标位置,是两个点之间的滑动.
driver.swipe(endlocation['x'], endlocation['y'], beginlocation['x'], beginlocation['y'], 2000)

# scroll滚动事件 从一个元素滚动到另外一个元素,直到页面自动停止
driver.scroll(end, begin, 2000)

# drag拖拽事件 从一个元素滑动到另外一个元素,第二个元素替代第一个元素原本屏幕上的位置
driver.drag_and_drop(end, begin)

# 将应用放置到后台,模拟热启动
driver.background_app(5)

模拟手势操作

end = driver.find_element(AppiumBy.XPATH, '//android.widget.TextView[@text="精品游戏"]')
begin = driver.find_element(AppiumBy.XPATH, '//android.view.View[@content-desc="首页"]')
endlocation = end.location
beginlocation = begin.location
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.actions import interaction
from selenium.webdriver.common.actions.action_builder import ActionBuilder
from selenium.webdriver.common.actions.pointer_input import PointerInput

actions = ActionChains(driver)
# 把actions.w3c_actions对应的ActionBuilder的鼠标操作改为touch
actions.w3c_actions = ActionBuilder(driver, mouse=PointerInput(interaction.POINTER_TOUCH, "touch"))
# 点击
actions.w3c_actions.pointer_action.click(el)
actions.perform()
# 长按指定时间
actions.w3c_actions.pointer_action.click_and_hold(el).pause(20).release()
actions.perform()

# 模拟从一个位置滑动到另一个位置 1
actions.w3c_actions.pointer_action.move_to(end)
actions.w3c_actions.pointer_action.pointer_down()
actions.w3c_actions.pointer_action.pause(2)
actions.w3c_actions.pointer_action.move_to(begin)
actions.w3c_actions.pointer_action.release()
actions.perform()

# 模拟从一个位置滑动到另一个位置 2
actions.w3c_actions.pointer_action.click_and_hold(end)
actions.w3c_actions.pointer_action.pause(2)
actions.w3c_actions.pointer_action.move_to(begin)
actions.w3c_actions.pointer_action.release()
actions.perform()

手机操作

# 获取手机时间
print(driver.device_time)

# 获取手机宽高
print(driver.get_window_size())
for i in range(3):
        driver.keyevent(24)

# 打开手机通知栏 可以获取通知栏的相关信息和元素操作
driver.open_notifications()

# 获取当前手机网络
print(driver.network_connection)

# 设置手机网络 0 (None) 1 (Airplane Mode) 2 (Wifi only) 4 (Data only) 6 (All network on)
driver.set_network_connection(1)

# 手机截图
driver.get_screenshot_as_file(os.path.dirname('.') + './test.png')

PO模型

PO模型是Page Object Model的简写,页面对象模型.

作用, 把测试页面和测试脚本进行分离,即把页面封装成类,供测试脚本进行调用.

案例:

项目目录结构  
- base
- - __init__.py    # 存放主要的变量如app_package,app_activity
- - base_action.py  # 封装click 和 input 和 查找元素,以及处理查找元素的异常
- - base_driver.py # 封装driver的初始化
- page
- - __init__.py
- - index.py # 封装页面上的操作和保存元素获取的信息,调用base
- script
- - __init__.py
- - test_index.py # 调用page面上的操作进行测试
- pytest.ini

base

init.py

app_package = 'com.bluestacks.gamecenter'
app_activity = ".AppCenterActivity"

base_action.py

装饰器不可调用,也就是装饰器不给参数

from selenium.common.exceptions import StaleElementReferenceException

def decorator(func):
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except StaleElementReferenceException as e:
            print(e)
            return func(*args, **kwargs)
    return wrapper

class BaseAction:
    def __init__(self, driver):
        self.driver = driver

    def input_ele(self, ele, content):
        self.find_element(ele).clear()
        self.find_element(ele).send_keys(content)

    @pro_StaleElementReferenceException
    def click_ele(self, ele):
        self.find_element(ele).click()

    def find_element(self, ele):
        return self.driver.find_element(ele[0], ele[1])

装饰器可调用,也就是装饰器可以指定参数,那么就需要在嵌套一个函数,这个函数用户接受装饰器的参数

from selenium.common.exceptions import StaleElementReferenceException

def pro_StaleElementReferenceException():
    def decorator(func):
        def wrapper(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except StaleElementReferenceException as e:
                print(e)
                return func(*args, **kwargs)
        return wrapper
    return decorator

class BaseAction:
    def __init__(self, driver):
        self.driver = driver

    def input_ele(self, ele, content):
        self.find_element(ele).clear()
        self.find_element(ele).send_keys(content)

    @pro_StaleElementReferenceException()
    def click_ele(self, ele):
        self.find_element(ele).click()

    def find_element(self, ele):
        return self.driver.find_element(ele[0], ele[1])

base_driver.py

from appium import webdriver
from appium.options.android import UiAutomator2Options
def init_driver(appPackage, appActivity):
    capabilities = {
        'platformName': 'Android',
        'platformVersion': '9',
        'deviceName': '127.0.0.1:5555',
        "automationName": "UiAutomator2",
        'appPackage': 'com.bluestacks.gamecenter',
        'appActivity': '.AppCenterActivity',
    }
    options = UiAutomator2Options().load_capabilities(capabilities)
    return webdriver.Remote('http://localhost:4723/wd/hub', options=options)  # 声明driver对象,让手机完成脚本操作

page

index.py

from base import app_package, app_activity
from base.base_driver import init_driver
from base.base_action import BaseAction
from appium.webdriver.common.appiumby import AppiumBy
class PageIndex(BaseAction):
    search_input = (AppiumBy.ID, 'com.bluestacks.gamecenter:id/main_et_search')
    searchbtn = (AppiumBy.ID, 'com.bluestacks.gamecenter:id/main_iv_search')
    returnbtn = (AppiumBy.XPATH, '//android.widget.TextView[@text="返回"]')
    phb = (AppiumBy.XPATH, '//android.widget.TextView[@text="排行榜"]')
    def __init__(self, driver):
        BaseAction.__init__(self, driver)
    def input_search(self):
        self.input_ele(self.search_input, '王者')

    def click_search(self):
        self.click_ele(self.searchbtn)

    def click_return(self):
        self.click_ele(self.returnbtn)

    def click_paihang(self):
        self.click_ele(self.phb)

script

test_index.py

import time

import pytest

from base import app_package, app_activity
from base.base_driver import init_driver
from page.index import PageIndex
class TestIndex:
    def setup_method(self):
        self.driver = init_driver(app_package, app_activity)
        self.page_index = PageIndex(self.driver)
    def teardown_method(self):
        self.driver.quit()

    @pytest.mark.parametrize("count", [1,2,3,4,5])
    def test_index(self, count):
        self.page_index.input_search()
        self.page_index.click_search()
        self.page_index.click_return()
        self.page_index.click_paihang()
        time.sleep(5)

pytest.ini

[pytest]
addopts = -s --alluredir=result
testpaths = ./script
python_files = test_*.py
python_classes = Test*
python_functions = test_*

装饰器

# 装饰器不可调用,也就是装饰器不给参数
def dec1(*args, **kwargs):
    print(args)
    print(kwargs)
    def decorator(func):
        def wrapper(*args, **kwargs):
                func(*args, **kwargs)
        return wrapper
    return decorator
# 装饰器可调用,也就是装饰器可以指定参数,那么就需要在嵌套一个函数,这个函数用户接受装饰器的参数
def dec2(func):
    def wrapper(*args, **kwargs):
            func(*args, **kwargs)
    return wrapper
# @dec2
@dec1(id, 1, 2, 3, name=123)
def test(a):
    print(a)
test(123)

results matching ""

    No results matching ""