pytest框架

pytest规则

  1、pytest文件要以test_开头或_test结尾

  2、测试类必须以Test开头,且不能有init方法

  3、测试用例必须以test_开头

  4、断言使用python原生的assert

 

安装pytest

在pycharm中的terminal,或者setting——project interpreter中安装

 

pytest参数(通过命令执行测试用例)

以下述代码讲解pytest的参数

# test_pytest_m.py,简单的测试用例编写
from time import sleep
from config.driver_config import DriverConfig


class TestPytestMClass:
    def test_open_bing(self):
        driver = DriverConfig().driver_config()
        driver.get('https://cn.bing.com')
        sleep(3)
        driver.quit()

    def test_open_baidu(self):
        driver = DriverConfig().driver_config()
        driver.get('https://www.baidu.com')
        sleep(3)
        driver.quit()

 

之前运行测试用例时,用鼠标右键run,现可以在terminal中输入命令运行

pytest testcases/test_pytest_m.py

 

也可以指定类和方法

# 运行类中所有的方法
pytest testcases/test_pytest_m.py::TestPytestMClass

# 运行类的指定方法
pytest testcases/test_pytest_m.py::TestPytestMClass::test_open_baidu

 

使用pytest命令执行

  1、-m:只运行被标记的测试用例

  2、-k:模糊匹配文件名、类名、方法名,执行匹配到的方法

  3、-s:在终端中打印调试信息

  4、-v:显示执行详细信息

  5、-q:显示简洁的执行信息

  6、--collect-only:只收集用例,不执行

 

pytest -m

1、只运行baidu标记(只运行test_open_baidu()方法):pytest -m baidu

 问题:不知道为何,还是会执行非baidu标记的其他文件用例

 

执行其他用例的发现解决:

 

2、运行标记为bing或baidu的(运行test_open_bing()、test_open_baidu()方法):pytest -m "bing or baidu"

from time import sleep

import pytest

from config.driver_config import DriverConfig


class TestPytestMClass:
    @pytest.mark.bing
    def test_open_bing(self):
        driver = DriverConfig().driver_config()
        driver.get('https://cn.bing.com')
        sleep(3)
        driver.quit()

    @pytest.mark.baidu
    def test_open_baidu(self):
        driver = DriverConfig().driver_config()
        driver.get('https://www.baidu.com')
        sleep(3)
        driver.quit()

 

3、运行同时标记为bing和baidu的(只运行test_open_baidu()方法):pytest -m "baidu and bing"

from time import sleep

import pytest

from config.driver_config import DriverConfig


class TestPytestMClass:
    @pytest.mark.bing
    def test_open_bing(self):
        driver = DriverConfig().driver_config()
        driver.get('https://cn.bing.com')
        sleep(3)
        driver.quit()

    @pytest.mark.bing
    @pytest.mark.baidu
    def test_open_baidu(self):
        driver = DriverConfig().driver_config()
        driver.get('https://www.baidu.com')
        sleep(3)
        driver.quit()

 

4、运行标记为baidu,标记不为bing,其他标记忽略的(只运行test_open_baidu()方法):pytest test_pytest_m.py -m "baidu and not bing"

from time import sleep

import pytest

from config.driver_config import DriverConfig


class TestPytestMClass:
    @pytest.mark.bing
    def test_open_bing(self):
        driver = DriverConfig().driver_config()
        driver.get('https://cn.bing.com')
        sleep(3)
        driver.quit()

    @pytest.mark.baidu
    def test_open_baidu(self):
        driver = DriverConfig().driver_config()
        driver.get('https://www.baidu.com')
        sleep(3)
        driver.quit()

    @pytest.mark.runoob
    def test_open_runoob(self):
        driver = DriverConfig().driver_config()
        driver.get('https://www.runoob.com/python3/python3-os-file-methods.html')
        sleep(3)
        driver.quit()

 

pytest -k

-k:模糊匹配文件名、类名、方法名,执行匹配到的方法

1、pytest -k pytest:文件名中包含pytest,即会运行test_pytest_m文件

 

2、pytest -k class:类名中包含class,即会运行TestPytestMClass类

 

3、pytest -k open:函数名中包含open,即会运行包含open的3个方法

 

pytest -s

-s:在终端中打印调试信息

 

pytest -v

-v:显示执行详细信息(打印日志相对其他的更详细一些),一般是调试时使用

 

pytest -q

-q:显示简洁的执行信息(打印日志相对简洁一些),Jenkins集成执行时一般用-q

 

pytest --collect-only

--collect-only:只收集用例,不执行

 

fixture的使用和作用范围

fixture的用途:包裹测试用例

定义fixture:@pytest.fixture()

使用fixture:fixture的函数名作为用例的参数

fixture的作用范围:

  • session
  • class:每个类执行前只会执行一次fixture
  • module:模块里所有用例执行前只执行一次fixture
  • function(默认):@pytest.fixture(scope="function"),每个用例执行前都会执行一次fixture
from time import sleep

import pytest

from config.driver_config import DriverConfig


class TestPytestMClass:
    @pytest.fixture(scope="class")  # 一个类中只执行一次,即使所有的函数都调用该方法
    def scope_class(self):
        print('我是class级别,我只执行一次')

    @pytest.fixture(scope="function")  # 调用该方法的每个function都会执行
    def driver(self):
        """
        下方的每个用例都初始化了一次driver,为了简化代码,可以定义一个driver方法,
        后续要用到时,将fixture的函数名作为参数传递给测试用例
        :return:
        """
        get_driver = DriverConfig().driver_config()
        return get_driver

    @pytest.mark.bing
    def test_open_bing(self, driver, scope_class):
        # driver = DriverConfig().driver_config()
        driver.get('https://cn.bing.com')
        sleep(1)
        driver.quit()

    @pytest.mark.baidu
    def test_open_baidu(self, driver, scope_class):
        # driver = DriverConfig().driver_config()
        print("*****************************")
        driver.get('https://www.baidu.com')
        sleep(1)
        driver.quit()

    @pytest.mark.runoob
    def test_open_runoob(self, driver, scope_class):
        # driver = DriverConfig().driver_config()
        driver.get('https://www.runoob.com/python3/python3-os-file-methods.html')
        sleep(1)
        driver.quit()

 

使用场景1: 测试⽤例执⾏时,有的⽤例需要登陆才能执⾏,有些⽤例不需要登陆。setup 和 teardown ⽆法满⾜,fixture 可以。默认 scope(范围)function。
步骤:
  1.导⼊ pytest
  2.在登陆的函数上⾯加@pytest.fixture()
  3.在要使⽤的测试⽅法中传⼊(登陆函数名称),就先登陆
  4.不传⼊的就不登陆直接执⾏测试⽅法。

"""
    pytest之fixture
"""
import pytest


@pytest.fixture()
def login():
    print("登录成功")
    return "token"


# 第一种引用方式:直接将函数名当成参数传递
def test_fix(login):
    print("接口返回token:", login)


# 第二种引用方式:使用pytest.mark.usefixtures
@pytest.mark.usefixtures("login")
def test_two():
    print(login)


def test_thr():
    print("test 3")

 

fixture使用到项目中

添加商品的测试用例使用fixture实现:

1、使用fixture之前

# 使用fixture前
from time import sleep

from config.driver_config import DriverConfig
from page.login_page import LoginPage
from page.left_menu_page import LeftMenuPage
from page.goods_page import GoodsPage


class TestAddGoods:
    def test_add_goods_001(self):
        driver = DriverConfig().driver_config()
        print("start test_add_goods_001 ______")
        LoginPage().login(driver, 'william')
        LeftMenuPage().click_level_one_menu(driver, '产品')
        sleep(1)
        LeftMenuPage().click_level_two_menu(driver, '新增二手商品')
        sleep(1)
        GoodsPage().add_new_goods(driver, '新增商品测试William', '新增商品详情测试William', 1, ['商品图片一.jpg'],
                                  123, '上架', '提交')
        sleep(3)
        print("end test_add_goods_001 ____________")
        driver.quit()

 

2、使用fixture的实现流程

 

3、使用fixture:

# 使用fixture
from time import sleep

import pytest

from config.driver_config import DriverConfig
from page.login_page import LoginPage
from page.left_menu_page import LeftMenuPage
from page.goods_page import GoodsPage


class TestAddGoods:
    @pytest.fixture()
    def driver(self):
        """
        @pytest.fixture():默认为 @pytest.fixture(scope="function")
        稍后可以在测试用例的传参中引用fixture函数的名称,以便在运行测试之前调用它
        用例的执行过程:
          1.先执行 get_driver 的实例化,返回 get_driver
          2.然后将get_driver的返回值传到 test_add_goods_001 用例中,执行测试操作
          3.测试用例执行完成后,返回driver函数中,继续执行 yield get_driver后面的代码
        :return:
        """
        print("start driver!!!!!!!!!!!!")
        get_driver = DriverConfig().driver_config()
        yield get_driver
        print("mid  driver!!!!!!!!!!!!")
        get_driver.quit()
        print("end  driver!!!!!!!!!!!!")

    def test_add_goods_001(self, driver):
        """
        在test_pytest_m.py文件解释了fixture的使用:
        定义一个fixture的driver方法,后续要用到时,将fixture的函数名(driver)作为参数传递给测试用例
        :return:
        """
        # driver = DriverConfig().driver_config()
        print("start test_add_goods_001 ______")
        LoginPage().login(driver, 'william')
        LeftMenuPage().click_level_one_menu(driver, '产品')
        sleep(1)
        LeftMenuPage().click_level_two_menu(driver, '新增二手商品')
        sleep(1)
        GoodsPage().add_new_goods(driver, '新增商品测试William', '新增商品详情测试William', 1, ['商品图片一.jpg'],
                                  123, '上架', '提交')
        sleep(3)
        print("end test_add_goods_001 ____________")
        # driver.quit()

 

 

conftest的使用

在上述 添加商品的测试用例 fixture使用中,fixture的driver函数在每个页面对象中都需要实现一遍,这样太过麻烦。所以引出conftest文件的使用,将driver函数提到conftest文件中封装。

conftest的使用原则:

  • conftest可以跨文件调用(在testcases目录下的文件都可以调用)。
  • conftest文件名称是固定的
  • 就近原则(同级用例先找同级的conftest.py文件,如果同级没有再找其他层级的。比如说在testcases目录下有一个conftest.py文件,再在testcases目录下新建一个my_conftest目录,my_conftest目录下也有一个conftest.py文件。此时my_conftest目录下的用例执行my_conftest目录下的conftest.py文件;testcases目录下的用例执行testcases目录下的conftest.py文件)。

          

  • conftest不能被其他文件导入。
  • conftest可以设置多个pytest内置的钩子方法。

 

执行流程:

 

 

1、driver函数提出到conftest文件:

import pytest

from config.driver_config import DriverConfig


@pytest.fixture()
def driver():
    """
    @pytest.fixture():默认为 @pytest.fixture(scope="function")
    可以在测试用例的传参中引用fixture函数的名称(driver),以便在运行测试之前调用它。conftest文件中的方法,在调用时不需要导入文件和方法,直接使用driver进行传参
    用例的执行过程:
      1.发现测试用例入参有driver,进入到conftest中执行driver
      2.先执行 get_driver 的实例化,yield返回 get_driver
      3.然后将get_driver的返回值传到 测试用例中,执行测试操作
      4.测试用例执行完成后,返回到conftest的driver函数中,继续执行 yield get_driver后面的代码
    :return:
    """
    print("start driver!!!!!!!!!!!!")
    get_driver = DriverConfig().driver_config()
    yield get_driver
    get_driver.quit()
    print("end  driver!!!!!!!!!!!!")

 

2、test_add_goods.py文件

from time import sleep

from page.login_page import LoginPage
from page.left_menu_page import LeftMenuPage
from page.goods_page import GoodsPage


class TestAddGoods:
    def test_add_goods_001(self, driver):
        """
        在test_pytest_m.py文件解释了fixture的使用:
        定义一个fixture的driver方法,后续要用到时,将fixture的函数名(driver)作为参数传递给测试用例
        :return:
        """
        print("start test_add_goods_001 ______")
        LoginPage().login(driver, 'william')
        LeftMenuPage().click_level_one_menu(driver, '产品')
        sleep(1)
        LeftMenuPage().click_level_two_menu(driver, '新增二手商品')
        sleep(1)
        GoodsPage().add_new_goods(driver, '新增商品测试William', '新增商品详情测试William', 1, ['商品图片一.jpg'],
                                  123, '上架', '提交')
        sleep(3)
        print("end test_add_goods_001 ____________")

 

 

参数化

实战:上述的conftest.py文件中,用例固定了新增商品的填写信息。现在通过参数化@pytest.mark.parametrize("goods_info", goods_info_list),在用例的入参中传入goods_info,循环遍历goods_info_list中的商品信息,实现新增多个商品。

from time import sleep

import pytest

from page.login_page import LoginPage
from page.left_menu_page import LeftMenuPage
from page.goods_page import GoodsPage

goods_info_list = [
    {
        'goods_title': '新增批量商品测试1',
        'goods_details': '新增批量商品详情测试1',
        'goods_num': 1,
        'goods_pic_list': ['商品图片一.jpg'],
        'goods_price': 100,
        'goods_status': '上架',
        'goods_button_name': '提交'
    },
    {
        'goods_title': '新增批量商品测试2',
        'goods_details': '新增批量商品详情测试2',
        'goods_num': 2,
        'goods_pic_list': ['商品图片一.jpg'],
        'goods_price': 200,
        'goods_status': '上架',
        'goods_button_name': '提交'
    }
]


class TestAddGoods:
    @pytest.mark.parametrize("goods_info", goods_info_list)
    def test_add_goods(self, driver, goods_info):
        """
        直接调用conftest.py文件中的driver方法作为入参,
        使用@pytest.mark.parametrize()对test_add_goods_01.py文件进行了参数化优化调整,循环遍历goods_info_list的元素
        """
        print("start test_add_goods_001 ______")
        LoginPage().login(driver, 'william')
        LeftMenuPage().click_level_one_menu(driver, '产品')
        sleep(1)
        LeftMenuPage().click_level_two_menu(driver, '新增二手商品')
        sleep(1)
        GoodsPage().add_new_goods(
            driver,
            goods_info['goods_title'],
            goods_info['goods_details'],
            goods_info['goods_num'],
            goods_info['goods_pic_list'],
            goods_info['goods_price'],
            goods_info['goods_status'],
            goods_info['goods_button_name']
        )
        sleep(3)
        print("end test_add_goods_001 ____________")

 

执行 test_add_goods用例时,会先新增商品标题为“新增批量商品测试1”的商品,走一个完成的测试用例流程(打开浏览器,执行用例,关闭浏览器),后再新增商品标题为“新增批量商品测试2”的商品。

 

分布式运行用例插件pytest-xdist

通过多线程的方式跑用例,会提高测试的效率。

安装插件pytest-xdist

 

执行命令

1、pytest -n 3(3表示同时打开3个浏览器运行,也可以设置为其他值)

 

2、pytest -n auto(auto表示根据CPU内核自动打开对应个数的浏览器)

 

3、pytest -n auto --dist=loadscope(表示同一个文件的测试用例,在一个浏览器进程中执行。在测试用例有执行的先后顺序时使用,但是除非迫不得已尽量不要写有先后关联的测试用例)

 

测试用例失败重跑插件pytest-rerunfailures

1、安装pytest-rerunfailures插件

 

2、浅试一下失败重跑:

import random


class TestRerun:
    def test_rerun(self):
        num = random.randint(0, 3)
        print('num:', num)
        if num != 1:
            print("失败")
            raise Exception("出错了")
        else:
            print("成功")

 

3、执行重跑(一般时在命令行中对全局用例进行设置)

3.1、通过命令行执行重跑:

pytest -s testcases/test_rerun.py --reruns 5(此处最多重跑5次)

 

pytest -s testcases/test_rerun.py --reruns 5 --reruns-delay 1(重跑一次,延时1秒,下方的执行时间明显慢3秒)

 

3.2、在用例里面设置重跑:

import random

import pytest


class TestRerun:
    @pytest.mark.flaky(reruns=5, reruns_delay=1)    # 设置重跑
    def test_rerun(self):
        num = random.randint(0, 3)
        print('num:', num)
        if num != 1:
            print("失败")
            raise Exception("出错了")
        else:
            print("成功")

 

pytest报告插件pytest-html

1、安装插件:pip install pytest-html

 

2、执行生成报告的命令:pytest -s -q test_switch_window_handle.py --html=report.html

      在下方路径中生成了测试报告和assets样式目录,如果没有assets,报告样式无法显示。

 

 

3、生成的报告内容:

 

删掉assets目录后,样式丢失

 

 

4、为解决步骤2中,删除assets目录导致样式丢失的问题,在执行命令中直接指定只包含html,这样只会生成report.html文件,样式直接包含在html文件中:

     pytest -s -q test_switch_window_handle.py --html=report.html --self-contained-html

 

pytest配置文件pytest.ini

pytest.ini 文件一般放在项目的根目录下

[pytest]
addopts = -s -q -n auto --html=report.html
testpaths = testcases
python_files = test*.py
python_classes = Test*
python_functions = test*
log_cli = True
log_data_format = %Y-%m-%d %H-%M-%S
log_level = debug
log_format = %(asctime)s - %(filename)s - %(funcName)s - %(lineno)d - %(levelname)s - %(message)s
log_file = test.log

log_cli:在控制台中也输出日志

 

posted @ 2023-03-16 00:00  雪儿来  阅读(690)  评论(0编辑  收藏  举报