Pytest实战Web测试框架

https://www.jianshu.com/p/9a03984612c1?utm_campaign=hugo&utm_medium=reader_share&utm_content=note&utm_source=weixin-timeline&from=timeline&isappinstalled=0

项目结构

用例层(测试用例)
  |
Fixtures层(业务流程)
  |
PageObject层
  |
Utils实用方法层  

使用pytest-selenium

基础使用

# test_baidu.py
def test_baidu(selenium):
    selenium.get('https://www.baidu.com')
    selenium.find_element_by_id('kw').send_keys('简书 韩志超')
    selenium.find_element_by_id('su').click()

运行

$ pytest test_baidu.py --driver=chrome

或配置到pytest.ini中

[pytest]
addopts = --driver=chrome

使用chrome options

# conftest.py
import pytest
@pytest.fixture
def chrome_options(chrome_options):  # 覆盖原有chrome_options
    chrome_options.add_argument('--start-maximized')
    # chrome_options.add_argument('--headless')
    return chrome_options  

Page Object层

基本模型

# baidu_page.py
class BaiduPage(object):
    search_ipt_loc = ('id', 'kw')
    search_btn_loc = ('id', 'su')
    
    def __init__(self, driver):
        self.driver = driver
    
    def input_search_keyword(self, text):
        self.driver.find_element(*self.search_ipt_loc).send_keys(text)
    
    def click_search_button(self):
        self.driver.find_element(*self.search_btn_loc).click()
        
    def search(self, text):
        self.input_search_keyword(text)
        self.click_search_button()

调用方法:

# test_baidu_page.py
from baidu_page import BaiduPage

def test_baidu_page(selenium):
    baidu = BaiduPage(selenium)
    baidu.search('简书 韩志超')

使用页面基类

# pages/base_page.py
class BasePage(object):
    def __init__(self, driver):
        self.driver = driver
    def input(self, element_loc, text):
        element = self.driver.find_element(*element_loc)
        element.clear()
        element.send_keys(text)
    
    def click(self, element_loc):
        self.driver.find_element(*element_loc).click()
# pages/baidu_page.py
from pages.base_page import BasePage

class BaiduPage(BasePage):
    search_ipt_loc = ('id', 'kw')
    search_btn_loc = ('id', 'su')
    
    def input_search_keyword(self, text):
        self.input(self.search_ipt_loc, text)
    
    def click_search_button(self):
        self.click(self.search_btn_loc)
        
    def search(self, text):
        self.input_search_keyword(text)
        self.click_search_button()

Fixtures业务层

# conftest.py
import pytest
from pages.baidu_page import BaiduPage()

@pytest.fixture(scope='session')
def baidu_page(selenium):
    return BaiduPage(selenium)

用例层

# test_baidu_page2.py
def test_baidu_page(baidu_page):
    baidu_page.search('简书 韩志超')
    assert '韩志超' in baidu.driver.title

步骤渐进

用例之间不应相互依赖,如果部分用例拥有相同的业务流程,如都需要,打开登录页->登录->点击添加商品菜单->进入添加商品页面
不建议使用以下方式,并使其按顺序执行。

def test_login():
   ...
  
def test_click_menu():
   ...
   
def test_add_goods():
   ...

建议对公共的步骤进行封装,可以使用Fixture方法的相互调用来实现步骤渐进,示例如下。

# conftest.py
import pytest
from pages.login_page import LoginPage
from pages.menu_page import MenuPage
from pages.add_goods_page import AddGoodsPage

@pytest.fixture(scope='session')
def login_page(selenium):
    return LoginPage(selenium)

@pytest.fixture(scope='session')
def menu_page(selenium, login_page):
    """登录后返回菜单页面"""
    login_page.login('默认用户名', '默认密码') # 也可以从数据文件或环境变量中读取
    return MenuPage(selenium)
    
@pytest.fixture(scope='session')
def add_goods_page(selenium, menu_page):
    """从MenuPage跳到添加商品页面"""
    menu_page.click_menu('商品管理', '添加新商品')
    return AddGoodsPage(selenium)
# test_ecshop.py
def test_login(login_page):
    login_page.login('测试用户名', '测试密码')
    assert login_page.get_login_fail_msg() is None

def test_add_goods(add_goods_page):
    add_goods_page.input_goods_name('dell电脑')
    add_goods_page.input_goods_category("电脑")
    add_goods_page.input_goods_price('3999')
    add_goods_page.submit()
    assert add_goods_page.check_success_tip() is True

使用日志

在项目中必要的输出信息可以帮助我们显示测试步骤的一些中间结果和快速的定位问题,虽然Pytest框架可以自动捕获print信息并输出屏幕或报告中,当时更规范的应使用logging的记录和输出日志。
相比print, logging模块可以分等级记录信息。

日志等级

实用方法层、页面对象层、Fixture业务层、用例层都可以直接使用logging来输出日志, 使用方法。

# test_logging.py
import logging

def test_logging():
    logging.debug('调试信息')
    logging.info('步骤信息')
    logging.warning('警告信息,一般可以继续进行')
    logging.error('出错信息')
    try:
       assert 0
    except Exception as ex:
        logging.exception(ex)  # 多行异常追溯信息,Error级别
    logging.critical("严重出错信息")

使用pytest运行不会有任何的log信息,因为Pytest默认只在出错的信息中显示WARNING以上等级的日志。
要开启屏幕实时日志,并修改log显示等级。

Log等级: NOTSET < DEBUG < INFO < WARNING(=WARN) < ERROR < CRITICAL

# pytest.ini
[pytest]
log_cli=True
log_cli_level=INFO

运行pytest test_logging.py,查看结果:

--------------------------------------------- live log call ----------------------------------------------
INFO     root:test_logging.py:5 步骤信息
WARNING  root:test_logging.py:6 警告信息,一般可以继续进行
ERROR    root:test_logging.py:7 出错信息
ERROR    root:test_logging.py:11 assert 0
Traceback (most recent call last):
  File "/Users/apple/Desktop/demo/test_logging.py", line 9, in test_logging
    assert 0
AssertionError: assert 0
CRITICAL root:test_logging.py:12 严重出错信息

由于日志等级设置的为INFO级别,因此debug的日志不会输出。

对于不同层日志级别的使用规范,可以在实用方法层输出debug级别的日志,如组装的文件路径,文件读取的数据,执行的sql,sql查询结果等等。

在PageObject层输出info级别的日志,如执行某个页面的某项操作等。
Fixtures层和用例层可以根据需要输出一些必要的info,warning或error级别的信息。

日志格式

默认的日志格式没有显示执行时间,我们也可以自定义日志输出格式。

# pytest.ini
...
log_cli_format=%(asctime)s %(levelname)s %(message)s
log_cli_date_format=%Y-%m-%d %H:%M:%S
  • %(asctime)s表示时间,默认为Sat Jan 13 21:56:34 2018这种格式,我们可以使用log_cli_date_format来指定时间格式。
  • %(levelname)s代表本条日志的级别
  • %(message)s为具体的输出信息

再次运行pytest test_logging.py,显示为以下格式:

--------------------------------------------- live log call ----------------------------------------------
2019-11-06 21:44:50 INFO 步骤信息
2019-11-06 21:44:50 WARNING 警告信息,一般可以继续进行
2019-11-06 21:44:50 ERROR 出错信息
2019-11-06 21:44:50 ERROR assert 0
Traceback (most recent call last):
  File "/Users/apple/Desktop/demo/test_logging.py", line 9, in test_logging
    assert 0
AssertionError: assert 0
2019-11-06 21:44:50 CRITICAL 严重出错信息

更多日志显示选项

  • %(levelno)s: 打印日志级别的数值
  • %(pathname)s: 打印当前执行程序的路径,其实就是sys.argv[0]
  • %(filename)s: 打印当前执行程序名
  • %(funcName)s: 打印日志的当前函数
  • %(lineno)d: 打印日志的当前行号
  • %(thread)d: 打印线程ID
  • %(threadName)s: 打印线程名称
  • %(process)d: 打印进程ID

输出日志到文件

在pytest.ini中添加以下配置

...
log_file = logs/pytest.log
log_file_level = debug
log_file_format = %(asctime)s %(levelname)s %(message)s
log_file_date_format = %Y-%m-%d %H:%M:%S

log_file是输出的文件路径,输入到文件的日志等级、格式、日期格式要单独设置。
遗憾的是,输出到文件的日志每次运行覆盖一次,不支持追加模式。

使用Hooks

使用Hooks可以更改Pytest的运行流程,Hooks方法一般也写在conftest.py中,使用固定的名称。
Pytest的Hooks方法分为以下6种:

  1. 引导时的钩子方法
  2. 初始化时的的钩子方法
  3. 收集用例时的钩子方法
  4. 测试运行时的钩子方法
  5. 生成报告时的钩子方法
  6. 断点调试时的钩子方法

Pytest完整Hooks方法API,可以参考:API参考-04-钩子(Hooks)

修改配置

以下方法演示了动态生成测试报告名。

# conftest.py
import os
from datetime import datetime
def pytest_configure(config):
    """Pytest初始化时配置方法"""
    if config.getoption('htmlpath'):  # 如果传了--html参数
        now = datetime.now().strftime('%Y%m%d_%H%M%S')
        config.option.htmlpath = os.path.join(config.rootdir, 'reports', f'report_{now}.html')

以上示例中无论用户--html传了什么,每次运行,都会在项目reports目录下,生成report_运行时间.html格式的新的报告。
pytest_configure是Pytest引导时的一个固定Hook方法,我们在conftest.py或用例文件中重新这个方法可以实现在Pytest初始化配置时,挂上我们要执行的一些方法(因此成为钩子方法)。
config参数是该方法的固定参数,包含了Pytest初始化时的插件、命令行参数、ini项目配置等所有信息。

可以使用Python的自省方法,print(config.dict)来查看config对象的所有属性。

通常,可以通过config.getoption('--html')来获取命令行该参数项的值。使用config.getini('log_file')可以获取pytest.ini文件中配置项的值。

添加自定义选项和配置

假设我们要实现一个运行完发送Email的功能。
我们自定义一个命令行参数项--send-email,不需要参数值。当用户带上该参数运行时,我们就发送报告,不带则不发,运行格式如下:

pytest test_cases/ --html=report.html --send-email

这里,一般应配合--html先生成报告。
由于Pytest本身并没有--send-email这个参数,我们需要通过Hooks方法进行添加。

# conftest.py
def pytest_addoption(parser):
    """Pytest初始化时添加选项的方法"""
    parser.addoption("--send-email", action="store_true", help="send email with test report")

另外,发送邮件我们还需要邮件主题、正文、收件人等配置信息。我们可以把这些信息配置到pytest.ini中,如:

# pytest.ini
...
email_subject = Test Report
email_receivers = superhin@126.com,hanzhichao@secco.com
email_body = Hi,all\n, Please check the attachment for the Test Report.

这里需要注意,自定义的配置选项需要先注册才能使用,注册方法如下。

# conftest.py
def pytest_addoption(parser):
    ...
    parser.addini('email_subject', help='test report email subject')
    parser.addini('email_receivers', help='test report email receivers')
    parser.addini('email_body', help='test report email body')

实现发送Email功能

前面我们只是添加了运行参数和Email配置,我们在某个生成报告时的Hook方法中,根据参数添加发送Email功能,示例如下。

from utils.notify import Email
# conftest.py
def pytest_terminal_summary(config):
    """Pytest生成报告时的命令行报告运行总结方法"""
    send_email = config.getoption("--send-email")
    email_receivers = config.getini('email_receivers').split(',')
    if send_email is True and email_receivers:
        report_path = config.getoption('htmlpath')
        email_subject = config.getini('email_subject') or 'TestReport'
        email_body = config.getini('email_body') or 'Hi'
        if email_receivers:
            Email().send(email_subject, email_receivers, email_body, report_path)

使用allure-pytest

allure是一款样式十分丰富的报告框架。
安装方法:pip install allure-pytest


  allure报告

参考文档:https://docs.qameta.io/allure/#_installing_a_commandline

Allure报告包含以下几块:

  • Overview: 概览
  • Categories: 失败用例分类
  • Suites:测手套件,对应pytest中的测试类
  • Graphs: 图表,报告用例总体的通过状态,标记的不同严重等级和执行时间分布。
  • Timeline: 执行的时间线
  • Behaviors: BDD行为驱动模式,按史诗、功能、用户场景
    等来标记和组织用例。
  • Pachages: 按包目录来查看用例

标记用例

pytest-allure可以自动识别pytest用例的失败、通过、skip、xfail等各种状态原因,并提供更多额外的标记,来完善用例信息。

此外,allure提供许多的额外标记来组织用例或补充用例信息等。

标记测试步骤

@allure.step('')

@allure.step
def func():
    pass

当用例调用该方法时,报告中会视为一个步骤,根据调用关系识别步骤的嵌套。

为用例添加额外信息

添加附件
  • @allure.attach.file('./data/totally_open_source_kitten.png', attachment_type=allure.attachment_type.PNG)
添加标题和描述
  • @allure.description('')
  • @allure.description_html('')
  • @allure.title("This test has a custom title")
添加链接、issue链接、用例链接
  • @allure.link('http://...')
  • @allure.issue('B140', 'Bug描述')
  • @allure.testcase('http://...', '用例名称')

BDD模式组织用例

  • @allure.epics('')
  • @allure.feature('')
  • @allure.story('')
  • @allure.step('')

可以按story或feature运行

  • --allure-epics
  • --allure-features
  • --allure-stories

标记严重级别

  • @allure.severity(allure.severity_level.TRIVIAL)
  • @allure.severity(allure.severity_level.NORMAL)
  • @allure.severity(allure.severity_level.CRITICAL)

通过以下方式选择优先级执行

--allure-severities normal,critical

生成allure报告

pytest --alluredir=报告文件夹路径

运行后该文件夹下会有一个xml格式的报告文件。
这种报告文件在jenkinz中直接使用插件解析。
如果想本地查看html格式的报告,需要安装allure。
安装方法:

  • Mac: brew install allure
  • CentOS: yum install allure
  • Windows: 点击下载, 下载外解压,进入bin目录,使用allure.bat即可。
    使用方法,生成html报告:
allure generate 生成allure报告的文件夹

Windows可以在allure的bin目录用allure.bat generate ...

或直接启动报告的静态服务:

allure serve 生成allure报告的文件夹

会自动弹出浏览器访问生成的报告。

Pytest实战APP测试框架

APP和Web同属于UI层,我们可以使用包含Page Object模式的同样的分层结构。不同的是我们需要自定义driver这个Fixture。

# conftest.py
import pytest
from appium import webdriver
@pytest.fixture(scope='session')
def driver():
    caps = {
        "platformName": "Android",
        "platformVersion": "5.1.1",
        "deviceName": "127.0.0.1:62001",
        "appPackage": "com.lqr.wechat",
        "appActivity": "com.lqr.wechat.ui.activity.SplashActivity",
        "unicodeKeyboard": True,
        "resetKeyboard": True,
        "autoLaunch": False
      }
    driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', caps)
    driver.implicitly_wait(10)
    yield driver
    driver.quit()

然后用其他Fixture或用例中直接以参数形式引入driver使用即可。

# test_weixin.py
def test_weixin_login(driver):
    driver.find_element_by_xpath('//*[@text="登录"]').click()
    ...

使用pytest-variables

通过pip install pytest-variables安装
假如我们需要在运行时指定使用的设备配置以及Appium服务地址,我们可以把这些配置写到一个JSON文件中,然后使用pytest-variables插件加载这些变量。
caps.json文件内容:

{
  "caps": {
    "platformName": "Android",
    "platformVersion": "5.1.1",
    "deviceName": "127.0.0.1:62001",
    "appPackage": "com.lqr.wechat",
    "appActivity": "com.lqr.wechat.ui.activity.SplashActivity",
    "unicodeKeyboard": true,
    "resetKeyboard": true,
    "autoLaunch": false
  },
  "server": "http://localhost:4723/wd/hub"
}

Fixtures中使用:

# conftest.py
...
@pytest.fixture(scope='session')
def driver(variables):
    caps = variables['caps']
    server = variables['server']
    driver = webdriver.Remote(server, caps)
    ...

运行方法:

pytest test_weixin.py --variables caps.json

如果有多个配置可以按caps.json格式,保存多个配置文件,运行时加载指定的配置文件即可。运行参数也可以添加到pytest.ini的addopts中。

设置和清理

为了保证每条用例执行完不相互影响,我们可以采取每条用例执行时启动app,执行完关闭app,这属于用例方法级别的Fixture方法。
同时,由于第一条用例执行时也会调用该Fixture启动app,这里我们需要设置默认连接设备是不自动启动app,即caps中配置autoLaunch=False。
在conftest.py中添加以下Fixture方法:

# conftest.py
...
@pytest.fixture(scope='function', autouse=True)
def boot_close_app(driver):
    driver.boot_app()
    yield
    driver.close_app()

其他Fixture层的页面对象和业务封装可以参考Web框架的模式。



作者:韩志超
链接:https://www.jianshu.com/p/9a03984612c1
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。  

原文地址:https://www.cnblogs.com/QaStudy/p/11828978.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


目录1、前言2、mark的使用(一)注册自定义标记(二)在测试用例上标记(三)执行3、扩展(一)在同一个测试用例上使用多个标记(二)在测试类上使用标记1、前言在自动化测试工作中我们有时候并不需要测试所有的测试用例,比如在冒烟测试阶段,我们只需要测试基本功能是否正常就可以了。在pytest中提供
用例执行状态用例执行完成后,每条用例都有自己的状态,常见的状态有passed:测试通过failed:断言失败error:用例本身写的质量不行,本身代码报错(譬如:fixture不存在,fixture里面有报错)xfail:预期失败,加了 @pytest.mark.xfail()  error的栗子一:参数不存在 defpwd():prin
什么是conftest.py可以理解成一个专门存放fixture的配置文件 实际开发场景多个测试用例文件(test_*.py)的所有用例都需要用登录功能来作为前置操作,那就不能把登录功能写到某个用例文件中去了 如何解决上述场景问题?conftest.py的出现,就是为了解决上述问题,单独管理一些全局的
前言pytest默认执行用例是根据项目下的文件名称按ascii码去收集运行的;文件中的用例是从上往下按顺序执行的。pytest_collection_modifyitems这个函数顾名思义就是收集测试用例、改变用例的执行顺序的。【严格意义上来说,我们在用例设计原则上用例就不要有依赖顺序,这样才能更好
当我们对测试用例进行参数化时,使用@pytest.mark.parametrize的ids参数自定义测试用例的标题,当标题中有中文时,控制台和测试报告中会出现Unicode编码问题,这看起来特别像乱码,我们想让中文正常展示出来,需要用到pytest框架的钩子函数pytest_collection_modifyitems。先看问题:#file_n
前言:什么是元数据?元数据是关于数据的描述,存储着关于数据的信息,为人们更方便地检索信息提供了帮助。pytest框架里面的元数据可以使用pytest-metadata插件实现。文档地址https://pypi.org/project/pytest-metadata/未安装插件pytest-metadata之前执行:环境搭建:使用
前言前面一篇讲了setup、teardown可以实现在执行用例前或结束后加入一些操作,但这种都是针对整个脚本全局生效的如果有以下场景:用例1需要先登录,用例2不需要登录,用例3需要先登录。很显然无法用setup和teardown来实现了fixture可以让我们自定义测试用例的前置条件 
前言:写完一个项目的自动化用例之后,发现有些用例运行较慢,影响整体的用例运行速度,于是领导说找出运行慢的那几个用例优化下。--durations参数可以统计出每个用例运行的时间,对用例的时间做个排序。pytest-h查看命令行参数,关于--durations=N参数的使用方式--durations=N
钩子函数之pytest_addoption介绍:①pytest_addoption钩子函数可以让用户注册一个自定义的命令行参数,以便于用户在测试开始前将数据从外部(如:控制台)传递给程序;【程序根据获取的用户传递的自定义的参数值来做一些事情】②pytest_addoption钩子函数一般和内置fixturepytestcon
[pytest]#命令行参数----空格分隔,可添加多个命令行参数-所有参数均为插件包的参数addopts=-s-reruns1--html=..eporteport.html#测试路径----当前目录下的scripts文件夹-可自定义testpaths=../scripts#搜索文件名----当前目录下的scripts文件夹下,以test_开头,以.py
python通用测试框架大多数人用的是unittest+HTMLTestRunner,这段时间看到了pytest文档,发现这个框架和丰富的plugins很好用,所以来学习下pytest. image.pngpytest是一个非常成熟的全功能的Python测试框架,主要有以下几个特点:简单灵活,容易上手支持参数化能够支持简单的单
1、装饰器,放在函数前面,跳过用例 @pytest.mark.skip(reason="nowayofcurrentlytestingthis")importpytestdeftest1():print('操作1')print("-----------------------------------------------")@pytest.mark.skip(reason="nowayofcur
本文实例为大家分享了python下载微信公众号相关文章的具体代码,供大家参考,具体内容如下目的:从零开始学自动化测试公众号中下载“pytest"一系列文档1、搜索微信号文章关键字搜索2、对搜索结果前N页进行解析,获取文章标题和对应URL主要使用的是requests和bs4中的Beautifulsoup
From:https://www.jianshu.com/p/54b0f4016300一.fixture介绍fixture是pytest的一个闪光点,pytest要精通怎么能不学习fixture呢?跟着我一起深入学习fixture吧。其实unittest和nose都支持fixture,但是pytest做得更炫。fixture是pytest特有的功能,它用pytest.fixture标识,定义在函
参数化有两种方式:1、@pytest.mark.parametrize2、利用conftest.py里的pytest_generate_tests 1中的例子如下:@pytest.mark.parametrize("test_input,expected",[("3+5",8),("2+4",6),("6*9",42)])deftest_eval(test_input,expected):
pytest优于其他测试框架的地方:1、简单的测试可以简单的写2、复杂的测试也可以简单的写3、测试的可读性强4、易于上手5、断言失败仅使用原生assert关键字,而不是self.assertEqual()或者self.assertLessThan()6、pytest可以运行有unitest和nose编写的测试用例pytest不依赖pyth
学习python的pytest框架需要的基础知识和学习准备测试从业者学习python应该掌握的内容:首先是变量和数据类型,其次列表、字典以及Json的一些处理,再者就是循环判断以及函数或类这些内容。其中的重点:1.循环判断以及字典这块是重点2.函数和类,类的学习这块要花较多时间去学
前言pytest可以支持自定义标记,自定义标记可以把一个web项目划分多个模块,然后指定模块名称执行。app自动化的时候,如果想android和ios公用一套代码时,也可以使用标记功能,标明哪些是ios用例,哪些是android的,运行代码时候指定mark名称运行就可以mark标记1.以下用例,标记test_send_http(
unittest参考文档: https://docs.python.org/3/library/unittest.htmlunittest笔记TheunittestunittestingframeworkwasoriginallyinspiredbyJUnitandhasasimilarflavorasmajorunittestingframeworksinotherlanguages.Itsupportstestautomation,shar
fixture场景一:参数传入代码如下:运行结果: