用 Pytest+Appium+Allure 做 UI 自动化的那些事~(有点干)

news2024/11/28 14:33:32

目录

前言:

Appium 不常见却好用的方法

Appium 直接执行 adb shell 方法

Appium 直接截取元素图片的方法

Appium 直接获取手机端日志

Appium 直接与设备传输文件

Pytest 与 Unittest 初始化上的区别

1.Pytest 与 unitest 类似,有些许区别,以下是 Pytest

2.使用 pytest.fixture()

初始化实例

1.setup_class 方式调用

2.pytest.fixture() 方式调用

Pytest 参数化方法

1.第一种方法 parametrize 装饰器参数化方法

2.第二种方法,使用 pytest hook 批量加参数化

Pytest 用例依赖关系

Pytest 自定义标记,执行用例筛选作用

1.使用 @pytest.mark 模块给类或者函数加上标记,用于执行用例时进行筛选

2.根据 test 节点选择用例

3.使用 pytest hook 批量标记用例

用例错误处理截图,app 日志等

Pytest 另一些 hook 的使用方法

1.自定义 Pytest 参数

2.Pytest 过滤测试目录

Pytest 一些常用方法

Pytest 用例优先级(比如优先登录什么的)

Pytest 用例失败重试

Pytest 其他常用参数


前言:

Pytest、Appium和Allure是一组强大的工具和框架,可以帮助开发人员实现高效的UI自动化测试。Pytest是一个功能强大的Python测试框架,Appium是一个用于移动应用自动化测试的开源工具,而Allure是一个用于生成漂亮测试报告的框架。

文本主要介绍下 Pytest+Allure+Appium 记录一些过程和经历,一些好用的方法什么的。

主要用了啥:

  • Python3
  • Appium
  • Allure-pytest
  • 用 Pytest+Appium+Allure 做 UI 自动化的那些事~(有点干)

Appium 不常见却好用的方法

Appium 直接执行 adb shell 方法
# Appium 启动时增加 --relaxed-security 参数 Appium 即可执行类似adb shell的方法
> appium -p 4723 --relaxed-security
# 使用方法
def adb_shell(self, command, args, includeStderr=False):
    """
    appium --relaxed-security 方式启动
    adb_shell('ps',['|','grep','android'])

    :param command:命令
    :param args:参数
    :param includeStderr: 为 True 则抛异常
    :return:
    """
    result = self.driver.execute_script('mobile: shell', {
        'command': command,
        'args': args,
        'includeStderr': includeStderr,
        'timeout': 5000
        })
    return result['stdout']
Appium 直接截取元素图片的方法
element = self.driver.find_element_by_id('cn.xxxxxx:id/login_sign')
pngbyte = element.screenshot_as_png
image_data = BytesIO(pngbyte)
img = Image.open(image_data)
img.save('element.png')
# 该方式能直接获取到登录按钮区域的截图

Appium 直接获取手机端日志
# 使用该方法后,手机端 logcat 缓存会清除归零,从新记录
# 建议每条用例执行完执行一边清理,遇到错误再保存减少陈余 log 输出
# Android
logcat = self.driver.get_log('logcat')

# iOS 需要安装 brew install libimobiledevice 
logcat = self.driver.get_log('syslog')

# web 获取控制台日志
logcat = self.driver.get_log('browser')

c = '\n'.join([i['message'] for i in logcat])
allure.attach(c, 'APPlog', allure.attachment_type.TEXT)
#写入到 allure 测试报告中
Appium 直接与设备传输文件
# 发送文件
#Android
driver.push_file('/sdcard/element.png', source_path='D:\works\element.png')

# 获取手机文件
png = driver.pull_file('/sdcard/element.png')
with open('element.png', 'wb') as png1:
    png1.write(base64.b64decode(png))

# 获取手机文件夹,导出的是zip文件
folder = driver.pull_folder('/sdcard/test')
with open('test.zip', 'wb') as folder1:
    folder1.write(base64.b64decode(folder))

# iOS
# 需要安装 ifuse
# > brew install ifuse 或者 > brew cask install osxfuse 或者 自行搜索安装方式

driver.push_file('/Documents/xx/element.png', source_path='D:\works\element.png')

# 向 App 沙盒中发送文件
# iOS 8.3 之后需要应用开启 UIFileSharingEnabled 权限不然会报错
bundleId = 'cn.xxx.xxx' # APP名字
driver.push_file('@{bundleId}:Documents/xx/element.png'.format(bundleId=bundleId), source_path='D:\works\element.png')


Pytest 与 Unittest 初始化上的区别

很多人都使用过 unitest 先说一下 pytest 和 unitest 在 Hook method 上的一些区别

1.Pytest 与 unitest 类似,有些许区别,以下是 Pytest

class TestExample:
    def setup(self):
        print("setup             class:TestStuff")

    def teardown(self):
        print ("teardown          class:TestStuff")

    def setup_class(cls):
        print ("setup_class       class:%s" % cls.__name__)

    def teardown_class(cls):
        print ("teardown_class    class:%s" % cls.__name__)

    def setup_method(self, method):
        print ("setup_method      method:%s" % method.__name__)

    def teardown_method(self, method):
        print ("teardown_method   method:%s" % method.__name__)

2.使用 pytest.fixture()

@pytest.fixture()
def driver_setup(request):
    request.instance.Action = DriverClient().init_driver('android')
    def driver_teardown():
        request.instance.Action.quit()
    request.addfinalizer(driver_teardown)

初始化实例

1.setup_class 方式调用

class Singleton(object):
    """单例 
    ElementActions 为自己封装操作类"""
    Action = None

    def __new__(cls, *args, **kw):
        if not hasattr(cls, '_instance'):
            desired_caps={}
            host = "http://localhost:4723/wd/hub"
            driver = webdriver.Remote(host, desired_caps)
            Action = ElementActions(driver, desired_caps)
            orig = super(Singleton, cls)
            cls._instance = orig.__new__(cls, *args, **kw)
            cls._instance.Action = Action
        return cls._instance

class DriverClient(Singleton):
    pass

测试用例中调用

class TestExample:
    def setup_class(cls):
        cls.Action = DriverClient().Action

    def teardown_class(cls):
        cls.Action.clear()


    def test_demo(self)
        self.Action.driver.launch_app()
        self.Action.set_text('123')

2.pytest.fixture() 方式调用

class DriverClient():

    def init_driver(self,device_name):
        desired_caps={}
        host = "http://localhost:4723/wd/hub"
        driver = webdriver.Remote(host, desired_caps)
        Action = ElementActions(driver, desired_caps)
        return Action



# 该函数需要放置在 conftest.py, pytest 运行时会自动拾取
@pytest.fixture()
def driver_setup(request):
    request.instance.Action = DriverClient().init_driver()
    def driver_teardown():
        request.instance.Action.clear()
    request.addfinalizer(driver_teardown)

测试用例中调用

#该装饰器会直接引入driver_setup函数
@user3res('driver_setup')
class TestExample:

    def test_demo(self):
        self.Action.driver.launch_app()
        self.Action.set_text('123')

Pytest 参数化方法

1.第一种方法 parametrize 装饰器参数化方法

@user4ize(('kewords'), [(u"小明"), (u"小红"), (u"小白")])
def test_kewords(self,kewords):
    print(kewords)

# 多个参数    
@user5ize("test_input,expected", [
    ("3+5", 8),
    ("2+4", 6),
    ("6*9", 42),
])
def test_eval(test_input, expected):
    assert eval(test_input) == expected

2.第二种方法,使用 pytest hook 批量加参数化

#  conftest.py
def pytest_generate_tests(metafunc):
    """
    使用 hook 给用例加加上参数
    metafunc.cls.params 对应类中的 params 参数

    """
    try:
        if metafunc.cls.params and metafunc.function.__name__ in metafunc.cls.params: ## 对应 TestClass params
          funcarglist = metafunc.cls.params[metafunc.function.__name__]
          argnames = list(funcarglist[0])
          metafunc.parametrize(argnames, [[funcargs[name] for name in argnames] for funcargs in funcarglist])
    except AttributeError:
        pass

# test_demo.py
class TestClass:
    """
    :params 对应 hook 中 metafunc.cls.params
    """
    # params = Parameterize('TestClass.yaml').getdata()

    params = {
        'test_a': [{'a': 1, 'b': 2}, {'a': 1, 'b': 2}],
        'test_b': [{'a': 1, 'b': 2}, {'a': 1, 'b': 2}],
    }
    def test_a(self, a, b):
        assert a == b
    def test_b(self, a, b):
        assert a == b

Pytest 用例依赖关系

使用 pytest-dependency 库可以创造依赖关系
当上层用例没通过,后续依赖关系用例将直接跳过,可以跨 Class 类筛选
如果需要跨.py 文件运行 需要将 site-packages/pytest_dependency.py 文件的

class DependencyManager(object):
    """Dependency manager, stores the results of tests.
    """

    ScopeCls = {'module':pytest.Module, 'session':pytest.Session}

    @classmethod
    def getManager(cls, item, scope='session'): # 这里修改成 session

如果

> pip install pytest-dependency
class TestExample(object):

    @user7cy()
    def test_a(self):
        assert False

    @user8cy()
    def test_b(self):
        assert False

    @user9cy(depends=["TestExample::test_a"])
    def test_c(self):
        # TestExample::test_a 没通过则不执行该条用例
        # 可以跨 Class 筛选
        print("Hello I am in test_c")

    @user10cy(depends=["TestExample::test_a","TestExample::test_b"])
    def test_d(self):
        print("Hello I am in test_d")
pytest -v test_demo.py    
2 failed
         - test_1.py:6 TestExample.test_a
         - test_1.py:10 TestExample.test_b
2 skipped

Pytest 自定义标记,执行用例筛选作用

1.使用 @pytest.mark 模块给类或者函数加上标记,用于执行用例时进行筛选

@pytest.mark.webtest
def test_webtest():
    pass 


@pytest.mark.apitest
class TestExample(object):
    def test_a(self):
        pass

    @pytest.mark.httptest
    def test_b(self):
        pass

仅执行标记 webtest 的用例

pytest -v -m webtest

Results (0.03s):
       1 passed
       2 deselected

执行标记多条用例

pytest -v -m "webtest or apitest"

Results (0.05s):
       3 passed

仅不执行标记 webtest 的用例

pytest -v -m "not webtest"

Results (0.04s):
       2 passed
       1 deselected

不执行标记多条用例

pytest -v -m "not webtest and not apitest"

Results (0.02s):
       3 deselected

2.根据 test 节点选择用例

pytest -v Test_example.py::TestClass::test_a
pytest -v Test_example.py::TestClass
pytest -v Test_example.py Test_example2.py

3.使用 pytest hook 批量标记用例

# conftet.py

def pytest_collection_modifyitems(items):
    """
    获取每个函数名字,当用例中含有该字符则打上标记
    """
    for item in items:
        if "http" in item.nodeid:
            item.add_marker(pytest.mark.http)
        elif "api" in item.nodeid:
            item.add_marker(pytest.mark.api)

class TestExample(object):
    def test_api_1(self):
        pass

    def test_api_2(self):
        pass

    def test_http_1(self):
        pass

    def test_http_2(self):
        pass
    def test_demo(self):
        pass

仅执行标记 api 的用例

pytest -v -m api
Results (0.03s):
       2 passed
       3 deselected
可以看到使用批量标记之后,测试用例中只执行了带有 api 的方法

用例错误处理截图,app 日志等

1.第一种使用 python 函数装饰器方法

def monitorapp(function):
    """
     用例装饰器,截图,日志,是否跳过等
     获取系统log,Android logcat、ios 使用syslog
    """

    @wraps(function)
    def wrapper(self, *args, **kwargs):
        try:
            allure.dynamic.description('用例开始时间:{}'.format(datetime.datetime.now()))
            function(self, *args, **kwargs)
            self.Action.driver.get_log('logcat')
        except Exception as E:
            f = self.Action.driver.get_screenshot_as_png()
            allure.attach(f, '失败截图', allure.attachment_type.PNG)
            logcat = self.Action.driver.get_log('logcat')
            c = '\n'.join([i['message'] for i in logcat])
            allure.attach(c, 'APPlog', allure.attachment_type.TEXT)
            raise E
        finally:
            if self.Action.get_app_pid() != self.Action.Apppid:
                raise Exception('设备进程 ID 变化,可能发生崩溃')
    return wrapper

2.第二种使用 pytest hook 方法 (与方法一选一)

@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    Action = DriverClient().Action
    outcome = yield
    rep = outcome.get_result()
    if rep.when == "call" and rep.failed:
        f = Action.driver.get_screenshot_as_png()
        allure.attach(f, '失败截图', allure.attachment_type.PNG)
        logcat = Action.driver.get_log('logcat')
        c = '\n'.join([i['message'] for i in logcat])
        allure.attach(c, 'APPlog', allure.attachment_type.TEXT)
        if Action.get_app_pid() != Action.apppid:
                raise Exception('设备进程 ID 变化,可能发生崩溃')

Pytest 另一些 hook 的使用方法

1.自定义 Pytest 参数
> pytest -s -all
# content of conftest.py
def pytest_addoption(parser):
    """
    自定义参数
    """
    parser.addoption("--all", action="store_true",default="type1",help="run all combinations")

def pytest_generate_tests(metafunc):
    if 'param' in metafunc.fixturenames:
        if metafunc.config.option.all: # 这里能获取到自定义参数    
            paramlist = [1,2,3]
        else:
            paramlist = [1,2,4]
        metafunc.parametrize("param",paramlist) # 给用例加参数化

# 怎么在测试用例中获取自定义参数呢
# content of conftest.py
def pytest_addoption(parser):
    """
    自定义参数
    """
    parser.addoption("--cmdopt", action="store_true",default="type1",help="run all combinations")


@pytest.fixture
def cmdopt(request):
    return request.config.getoption("--cmdopt")


# test_sample.py 测试用例中使用
def test_sample(cmdopt):
    if cmdopt == "type1":
        print("first")
    elif cmdopt == "type2":
        print("second")
    assert 1

> pytest -q --cmdopt=type2
second
.
1 passed in 0.09 seconds

2.Pytest 过滤测试目录
#过滤 pytest 需要执行的文件夹或者文件名字
def pytest_ignore_collect(path,config):
    if 'logcat' in path.dirname:
        return True #返回 True 则该文件不执行

Pytest 一些常用方法

Pytest 用例优先级(比如优先登录什么的)
> pip install pytest-ordering
@pytest.mark.run(order=1)
class TestExample:
    def test_a(self):
Pytest 用例失败重试
#原始方法
pytet -s test_demo.py
pytet -s --lf test_demo.py #第二次执行时,只会执行失败的用例
pytet -s --ll test_demo.py #第二次执行时,会执行所有用例,但会优先执行失败用例
#使用第三方插件
pip install pytest-rerunfailures #使用插件
pytest --reruns 2 # 失败case重试两次
Pytest 其他常用参数
pytest --maxfail=10 #失败超过10次则停止运行
pytest -x test_demo.py #出现失败则停止

希望对测试同学们有帮助~

  作为一位过来人也是希望大家少走一些弯路

在这里我给大家分享一些自动化测试前进之路的必须品,希望能对你带来帮助。

(WEB自动化测试、app自动化测试、接口自动化测试、持续集成、自动化测试开发、大厂面试真题、简历模板等等)

相信能使你更好的进步!

点击下方小卡片

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/759363.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【嘉立创EDA】客户端版本嘉立创专业版半离线版更新方法

文章路标👉 文章解决问题主题内容工程文件备份保护问题新版本更新通知文章解决问题 1️⃣ 嘉立创EDA专业版,是时下越发常用的PCBA设计软件之一。该环境除了在规划的设计开发过程中,为响应各用户的建议、需求,其迭代更新速度也是很快。为了使用最新的功能,用户需要使用最…

C++学习——类和对象(一)

C语言和C语言最大的区别在于在C当中引入了面向对象的编程思想,想要完全了解c当中的类和对象,就要从头开始一点一点的积累并学习。 一:什么是面向对象编程 我们之前学习的C语言属于面向过程的编程方法。举一个简单的例子来说:面向过…

Linux地盘上AMD处理器称王了

近日资讯,尽管从全局来看,Linux系统份额远不及Windows,但在程序员、开发者、硬核玩家圈子,Linux则备受推崇。 来自Steam的最新数据显示,在Linux游戏用户中,AMD处理器的份额占据绝对优势,达到了…

Spring Cloud—GateWay之限流

RequestRateLimiter RequestRateLimiter GatewayFilter 工厂使用 RateLimiter 实现来确定是否允许当前请求继续进行。如果不允许,就会返回 HTTP 429 - Too Many Requests(默认)的状态。 这个过滤器需要一个可选的 keyResolver 参数和特定于…

cpuset.cpus.effective: no such file or directory (修改 docker cgroup 版本的方法)

要切换使用 v1 版 cgroup,需要做如下配置: vim /etc/default/grubGRUB_CMDLINE_LINUX"systemd.unified_cgroup_hierarchy0"update-grubreboot完美解决

【条件与循环】——matlab入门

目录索引 if:else与elseif: for: if: if 条件语句块 endelse与elseif: if 条件代码块 elseif 条件代码块 else 代码块 endfor: for 条件循环体 end在matlab里面类似的引号操作都是包头又包尾的。上面的c…

TypeScript基础篇 - TS介绍

目录 Typescript的定义 type.ts 深入了解Typescript Typescript应该学到什么程度? Typescript学习方法 如何学好TS 小节:常见学习误区 一张ai生成图~ Typescript的定义 2012年微软发布的一门编程语言 Transcompiler【翻译编译器】Typescript——…

操作符详解(2)

文章目录 8. 条件操作符9. 逗号表达式10. 下标引用、函数调用和结构成员11. 表达式求值11.1 隐式类型转换11.2 算术转换11.3 操作符的属性 附: 8. 条件操作符 exp1 ? exp2 : exp3 int main() {int a 0;int b 0;if (a > 5){b 3;}else{b -3;}//(a > 5) ? …

【Elasticsearch】黑马旅游案例

目录 4.黑马旅游案例 4.1.酒店搜索和分页 4.1.1.需求分析 4.1.2.定义实体类 4.1.3.定义controller 4.1.4.实现搜索业务 4.2.酒店结果过滤 4.2.1.需求分析 4.2.2.修改实体类 4.2.3.修改搜索业务 4.3.我周边的酒店 4.3.1.需求分析 4.3.2.修改实体类 4.3.3.距离排序…

linux学习笔记(1)----基础知识

1.linux用户 ubuntu有三种用户: 1)初次创建的用户 2)root用户 3)普通用户 linux用户记录在etc/passwd这个文件内 linux用户密码在etc/shadow这个文件内 2.linux用户组 为了方便管理,将用户进行分组&#xff0c…

.NET Native AOT的静态库与动态库

.NET不仅可以使用 C静态库与动态库,也可以将.NET实现的函数导出为C静态库与动态库。在没有Native Aot之前,.NET只能通过P/Invoke享受C/C生态,而在Native Aot之后,不仅可以享受这些生态,还可以开发SDK供其他语言调用。 …

多层、六边形、洋葱和clean架构简介

想想回到过去的美好时光,根本没有所谓架构,那些日子是多么幸福啊,只有了解 GoF 模式,你就能称自己为架构师。 然而,计算机变得更加强大,用户的需求增加,导致应用程序的复杂性增加。 开发人员解决…

一场内容生产的革命 :从PGC、UGC到AIGC

1 概念解读 1.1 什么是PGC? PGC 是指专业生成内容(Professional Generated Content),是由专业的内容创作者或团队进行创作、编辑和发布的内容。PGC创作方式起源于传统媒体时代,如报纸、杂志、电视和电影等&#xff0…

低代码技术在各大行业中的应用探讨

随着低代码开发平台的兴起,越来越多的企业开始探索和采用这一技术,以加速应用程序的开发和数字化转型。低代码开发平台的优势在于简化和加速开发过程,降低了技术门槛,使得非专业开发人员也能参与应用程序的创建。在本篇文章中&…

Linux下挂载NFS服务

描述:在Linux下把文件挂在到Linux开发板中实现文件共享 准备步骤:Ubuntu和一块Linux开发板 网络环境:确保在同一个网段:例如192.168.1.226 和192.168.3.226 是不同的网段 NFS介绍 网络文件系统,英文 Network File …

Linux上安装和使用SSH工具

文章目录 前言一、安装SSH Server1. 安装ssh安装包2. 启动ssh3. 设置ssh开机启动 二、 检查SSH状态三、备注 前言 SSH(Secure Shell,安全外壳)是一种网络安全协议,通过加密和认证机制实现安全的访问和文件传输等业务,…

MySQL 的全局锁、表锁和行锁

在前一篇文章我讲了下 MySQL 的全局锁、表记锁和行级别锁,其中行级锁只提了概念,并没有具体说。 因为行级锁加锁规则比较复杂,不同的场景,加锁的形式还不同,所以这次就来好好介绍下行级锁。 对记录加锁时,加…

PyTorch模型安卓部署流程(NCNN)

上一篇介绍了PyTorch模型部署流程(Onnx Runtime)的相关部署流程,再来简单的回顾一下~ 深度学习模型部署介绍 模型部署指让训练好的深度学习模型在特定环境中运行的过程。模型部署会面临的难题: 运行模型所需的环境难以配置。深度学习模型通…

驱动设备的IOCTL

一、ioctl操作实现 已知成员的地址获得所在结构体变量的地址: container_of(成员地址,结构体类型名,成员在结构体中的名称) long xxx_ioctl (struct file *filp, unsigned int cmd, unsigned long arg); 功能:对相应设备做指定的控制操作&…

GB28181设备接入侧录像查询和录像下载技术探究之实时录像

技术背景 我们在对接GB28181设备接入侧的时候,除了常规实时音视频按需上传外,还有个重要的功能,就是本地实时录像,录像后的数据,在执法记录仪等前端设备留底,然后,到工作站拷贝到专门的平台。 …