pytest

pytest命名规则

pytest测试用例编写规则

类型规则
文件test_开头或者_test结尾
Test开头
方法/函数test开头
测试类中不可添加__init__构造函数

pycharm配置与界面化运行

  • 首先进行添加pytest环境
  •  其次在settings中的搜索框搜索pytest在default test runner中选择pytest

 pytest用例结构

用例结构(缺一不可)

  • 用例名称
  • 用例步骤
  • 用例断言
def test_XXX(self):
    # 测试步骤1
    # 测试步骤2
    # 断言  实际结果 对比 预期结果
    assert ActualResult == ExpectedResult

 类级别的用例示例

class TestXXX:
    def setup(self):
        # 资源准备
        pass

    def teardown(self):
        # 资源销毁
        pass

    def test_XXX(self):
        # 测试步骤1
        # 测试步骤2
        # 断言  实际结果 对比 预期结果
        assert ActualResult == ExpectedResult

用例断言

概念:断言(assertion)是一种在程序中的一阶逻辑(如:一个结果为真或假的逻辑判断式),目的为了表示与验证软件开发者预期的结果——当程序执行到断言的位置时,对应的断言应该为真。若断言不为真时,程序会中止执行,并给出错误信息

  • 断言写法
    • assert <表达式>
    • assert <表达式>,<描述>

示例1

def test_sum():
    a = 1
    b = 2
    expect = 3
    assert a + b == expect,f'{a} + {b} = {expect} ,结果为真'

示例2//查看当前环境下是否为linux环境

import sys
def test_plat():
    assert ('linux' in sys.platform), "该代码只能在 Linux 下执行"

 pytest测试结构框架(set up/tear down)

  • set up为一些准备的前置工作
  • tear down为完成一些后置工作
类型规则
setup_module/teardown_module全局模块级
setup_class/teardown_class类级,只在类中前后运行一次(一般常用​​​​​​​
setup_function/teardown_function函数级,在类外
setup_method/teardown_method方法级,类中的每个方法执行前后
setup/teardown在类中,运行在调用方法的前后(一般常用​​​​​​​
def test_case1():
    print('case1')
def setup_function():
    print("资源准备,setup function")
def teardown_function():
    print('资源已损毁 teardown_function')

运行代码后,在终端开启两个button,一个show passed和一个show ignored,如下图

def setup_module():
    print('资源准备,setup module')
def teardown_module():
    print('资源销毁,teardown_module')
def test_case1():
    print('case1')
def test_case2():
    print('case2')
def setup_function():
    print("资源准备,setup function")
def teardown_function():
    print('资源已损毁 teardown_function')


运行结果是:
pytest_study.py::test_case1 资源准备,setup module //module级别的只执行一次
资源准备,setup function //一般每执行一次测试用例就会执行一次
PASSED                                       [ 50%]case1
资源已损毁 teardown_function

pytest_study.py::test_case2 资源准备,setup function
PASSED                                       [100%]case2
资源已损毁 teardown_function
资源销毁,teardown_module


============================== 2 passed in 0.01s ===============================

 pytest 参数化

  • 通过参数的方式传递数据,从而实现数据和脚本分离
  • 并且可以实现用力的重复生成与执行

参数化实现方案

装饰器:@pytest.mark.parametrize

测试登陆场景

  • 测试登陆成功,登陆失败(账号错误,密码错误)
  • 创建多种账号,中文文本账号,英文文本账号
不使用pytest
def test_param_login_ok():
    # 登录成功
    username = "right"
    password = "right"
    login(username,password)

def test_param_login_fail():
    # 登录失败
    username = "wrong"
    password = "wrong"
    login(username,password)
使用pytest的场景
@pytest.mark.parametrize("username,password",[["right","right"],["wrong","wrong"]])
def test_param(username,password):
    login(username,password)

 参数化测试函数使用

  • 单参数
    • 每一条测试数据就会生成一个测试用例
  • search_list = ['appium', 'selenium', 'pytest']
    
    
    @pytest.mark.parametrize('search_key', ['appium', 'pytest', 'allure', 'abc'])
    def test_search_param(search_key):
        assert search_key in search_list
    

  • 多参数

# 第二种:多参数情况,用列表嵌套列表的方式
@pytest.mark.parametrize("username,password", [["rightusername", "right username"],
                                               ["wrong username", "wrong password"],
                                               [" ", "password"]
                                               ])
def test_login(username, password):
    print(f"登陆的用户名:{username},登陆的密码:{password}")

默认的命名方式测试数据用“-”相连接 如“pytest.pytest_study.test_login”

​​​​​​​

  • 用例重命名
# 第二种:多参数情况,用列表嵌套列表的方式
@pytest.mark.parametrize("username,password", [["rightusername", "right username"],
                                               ["wrong username", "wrong password"],
                                               [" ", "password"]
                                               ],
                         ids = ["正常的用户和正确的密码","错误的用户名和错误的密码","用户名为空"]
                         )
def test_login(username, password):
    print(f"登陆的用户名:{username},登陆的密码:{password}")


#第三种给我们的用例进行重命名,默认的命名方式测试数据用“-”相连接 如“pytest.pytest_study.test_login”

如上这样我们的运行结果是不支持中文的,如下为运行后的结果

 如想要其支持中文的话,需要在文件夹内添加conftest.py

# 创建conftest.py 文件 ,将下面内容添加进去,运行脚本
def pytest_collection_modifyitems(items):
    """
    测试用例收集完成时,将收集到的用例名name和用例标识nodeid的中文信息显示在控制台上
    """
    for i in items:
        i.name=i.name.encode("utf-8").decode("unicode_escape")
        i._nodeid=i.nodeid.encode("utf-8").decode("unicode_escape")

如上运行结果,测试用例将被重命名为中文的格式

  • 笛卡尔积(在接口测试中应用比较广)
    • 两组数据
    • a = [1,2,3]
    • b = [a,b,c]
    • 对应有几种组形式
    • (1,a)(1,b)(1,c)
    • (2,a)(2,b)(2,c)
    • (3,a)(3,b)(3,c)
    • @pytest.mark.parametrize("b", ["a", "b", "c"])
      @pytest.mark.parametrize("a", [1, 2, 3])
      def test_param1(a,b):
          print(f'笛卡尔积形式的参数中a = {a},b = {b}')

               测试结果如下:

                

pytest标记测试用例

pytest设置跳过及预期失败测试用例

  • 调试时不想运行这个用例
  • 标记无法在某些平台上运行的测试功能
  • 在某些版本中执行,其他版本中跳过
  • 比如:当前的外部资源不可用时跳过
    • 如果测试数据是从数据库中取到的,
    • 连接数据库的功能如果返回结果未成功就跳过,因为执行也都报错
  • 解决 1:添加装饰器
    • 无条件跳过@pytest.mark.skip
      • ​​​​​​​​​​​​​​使用方法:@pytest.mark.skip标记在需要跳过的测试用例上。
      • @pytest.mark.skip
        def test_login():
            print('该条测试用例未开发完成')
            assert True
        
        
    • 有条件跳过@pytest.mark.skipif(reason = 'xxx')//需要添加具体的原因
      • ​​​​​​​
        @pytest.mark.skipif(reson = '代码未实现')
        def test_bbb():
            print('该条测试用例,代码未实现')
            assert False
    • 执行完成后:
  • 解决 2:代码中添加跳过代码
    • pytest.skip(reason)
    • def check_login():
          return True
      
      
      def test_function():
          print('start')
          # 如果未登陆则跳过后续步骤
          if not check_login():
              pytest.skip('unsupported configuration')
          print('end')
      
    •  在mac上跳过,在windows上运行
    • import sys
      import pytest
      
      print(sys.platform)
      @pytest.mark.skipif(sys.platform == 'darwin',reason='does not run on mac')
      def test_aaa():
          assert True
      
      @pytest.mark.skipif(sys.platform == 'win',reason='does not run on windows')
      def test_bbb():
          assert True
  • xfail使用场景:
    •  与skip类似,预期结果为fail,标记用例为fail
    • 用法:添加装饰器@pytest.mark.xfail

 pytest运行用例

  • 运行 某个/多个 用例包
  • 运行 某个/多个 用例模块
  • 运行 某个/多个 用例类
  • 运行 某个/多个 用例方法

pytest只执行上次失败的测试用例

 常用命令行

 python执行pytest代码

  • 使用main函数
  • 使用python -m pytest 调用pytest(jeckins持续集成)

if __name__ == '__main__':     # 1、运行当前目录下所有符合规则的用例,包括子目录(test_*.py 和 *_test.py)    

pytest.main()     # 2、运行test_mark1.py::test_dkej模块中的某一条用例     pytest.main(['test_mark1.py::test_dkej','-vs'])     # 3、运行某个 标签     pytest.main(['test_mark1.py','-vs','-m','dkej']) 运行方式 `python test_*.py `

常用的异常处理方法

Pytest如何对捕获的异常的类型和内容进行断言_pytest捕获异常_redrose2100的博客-CSDN博客

 pytest结合数据驱动-yaml

什么是数据驱动

  • 数据驱动就是数据的改变从而驱动自动化测试的执行,最终引起测试结果的改变,简单来说,就是参数化的应用,
    • 数据量小的测试用例可以使用代码的参数化来实现数据驱动,
    • 数据量大的情况下使用结构化的文件(例如yaml,json)来对数据进行存储,然后用测试用例读取这些数据
  • 应用:
    • app、web、接口自动化测试
    • 测试步骤的数据驱动
    • 测试数据的数据驱动
    • 配置的数据驱动

yaml文件介绍

  • 对象:键值对的集合,用冒号“:”表示
  • 数组:一组按次序排列的值,前加 “-”
  • 纯量:单个的,不可再分的值
    • 字符串
    • 布尔值
    • 整数
    • 浮点数
    • NULL
    • 时间
    • 日期
# 编程语言
languages: # 相当于是一对键值 languages:["php","Java","Python"]
  - PHP
  - Java
  - Python
book:
  Python入门: # 书籍名称
    price: 55.5
    author: Lily
    available: True
    repertory: 20
    date: 2018-02-17
  Java入门:
    price: 60
    author: Lily
    available: False
    repertory: Null
    date: 2018-05-11

yaml文件使用

  • 查看yaml文件
    • pycharm
    • txt记事本
  • 读取yaml文件
    • 安装:pip install pyyaml
    • 方法:yaml.safe_load(f) #将yaml转化为python对象
    • 方法:yaml.safe_dump(f)#将python对象转化为yaml文件
    • import yaml
      file_path = './my.yaml'
      with open(file_path, 'r', encoding='utf-8') as f:
          data = yaml.safe_load(f)

工程目录文件 

  • data 目录:存放 yaml 数据文件
  • func 目录:存放被测函数文件
  • testcase 目录:存放测试用例文件

ps:创建data时,可以创建为文件夹,也可以创建为packages

 operation.py#被测函数

def my_add(x, y):
    result = x + y
    return result

test_add.py#测试case

import pytest
from data_driver_yaml.func.operation import my_add#如果不写这个会造成 下面的my_add函数找不到,会报错


class TestWithYAML:
  @pytest.mark.parametrize('x,y,expected', [[1, 1, 2],[1,2,3],[5,6,7]])
  def test_add(self, x, y, expected):
    assert my_add(int(x), int(y)) == int(expected)

当测试数据量很大时,我们可以从yaml中再读取出来数据

import pytest
import yaml
from data_driver_yaml.func.operation import my_add
#windows电脑需要将with open中再添加一个参数 encoding = “utf-8”
def get_data():
  with open("../data/data.yaml") as f:
    data = yaml.safe_load(f)
  return data

def test_get_data():
  print(get_data())

class TestWithYAML:
  @pytest.mark.parametrize('x,y,expected', get_data())
  def test_add(self, x, y, expected):
    assert my_add(int(x), int(y)) == int(expected)

data_yaml#测试数据

#嵌套三个列表[[1,1,2],[3,6,9],[100,200,300]]
-
  - 1
  - 1
  - 2
-
  - 3
  - 6
  - 9
-
  - 100
  - 200
  - 300

pytest结合数据驱动Excel

  • 第三方库

    • xlrd
    • xlwings
    • pandas
  • openpyxl

    • 官方文档: https://openpyxl.readthedocs.io/en/stable/
  • openpyxl库的安装
    • 安装:pip install openpyxl
    • 导入:import openpyxl
import openpyxl

# 获取工作簿
book = openpyxl.load_workbook('../data/params.xlsx')

# 读取工作表
sheet = book.active

# 读取单个单元格
cell_a1 = sheet['A1']
cell_a3 = sheet.cell(column=1, row=3)  # A3

# 读取多个连续单元格
cells = sheet["A1":"C3"]

# 获取单元格的值
cell_a1.value

注意xlsx文件必须用wps进行创建,在文件下创建file重命名为xlsx文件会出现报错的问题

testcases.py文件

from openpyxl_study.read_excel.func.operation import my_add
import pytest
import openpyxl


def get_excel():
    book = openpyxl.load_workbook("../data/params.xlsx")
    sheet = book.active
    cells = sheet["A1":"C3"]
    print(cells)
    values = []
    for row in cells:
        data = []
        for cell in row:
            data.append(cell.value)
        values.append(data)
    return values


class TestWithEXCEL:
    @pytest.mark.parametrize('x,y,expected', get_excel())
    def test_add(self, x, y, expected):
        assert my_add(int(x), int(y)) == int(expected)

pytest结合csv实现数据驱动

csv文件介绍

  • csv:逗号分隔值
  • 是comma-separated values的缩写
  • 是纯文本形式存储数字和文本
  • 文件由任意数目的记录组成
  • 每行记录由多个字段组成

以下例子:

Linux从入门到高级,linux,¥5000
web自动化测试进阶,python,¥3000
app自动化测试进阶,python,¥6000
Docker容器化技术,linux,¥5000
测试平台开发与实战,python,¥8000
  • 读取数据

    • 内置函数:open()
    • 内置模块:csv //有相关读取csv文件的方法
      • ​​​​​​​​​​​​​​方法:csv.reader(iterable)
        • 参数:iterable ,文件或列表对象
        • 返回:迭代器,每次迭代会返回一行数据。

工程目录结构

# 工程目录结构
.
├── data
│   └── params.csv
├── func
│   ├── __init__.py
│   └── operation.py
└── testcase
    ├── __init__.py
    └── test_add.py
  • data 目录:存放 csv 数据文件
  • func 目录:存放被测函数文件
  • testcase 目录:存放测试用例文件
import pytest
import csv

from openpyxl_study.read_csv.func.operation import my_add


def get_csv():
    with open("../data/demo.csv") as f:
        #读取这个文件
        raw = csv.reader(f)
        data = []
        for line in raw:
            data.append(line)
        print(data)
        return data

class TestWithCsv:
    @pytest.mark.parametrize('x,y,expected',get_csv())
    def test_add(self,x,y,expected):
        assert my_add(int(x),int(y)) == int(expected)

pytest结合数据驱动json

  • json是js对象
  • 全称是javascript object notation
  • 是一种轻量级的数据交换格式
  • json结构
    • 对象{'key':value}
    • 数组[value1,value2....]
  • 例子
  • {
      "name:": "hogwarts ",
      "detail": {
        "course": "python",
        "city": "北京"
      },
      "remark": [1000, 666, 888]
    }
  • 查看 json 文件
    • pycharm
    • txt 记事本
  • 读取 json 文件
    • 内置函数 open()
    • 内置库 json
    • 方法:json.loads() //返回值是dict 字典格式的
    • 方法:json.dumps() //返回值是字符串类型的格式
import json
def get_json():
    with open("demo.json") as file:
       data = json.load(file)
       print(data,type(data))
       s = json.dumps(data,ensure_ascii=False)
       print(s,type(s))
if __name__ == "__main__":
    get_json()

运行结果是:
{'name:': 'hogwarts ', 'detail': {'course': 'python', 'city': '北京'}, 'remark': [1000, 666, 888]} <class 'dict'>
{"name:": "hogwarts ", "detail": {"course": "python", "city": "北京"}, "remark": [1000, 666, 888]} <class 'str'>
{
  "case1": [1, 1, 2],
  "case2": [3, 6, 9],
  "case3": [100, 200, 300]
}
import json

from json_study.func.operation import my_add
import pytest

def get_json():
    with open("../data/params.json",'r') as file:
        data = json.loads(file.read())
        print(data)
        return list(data.values())


class TestWithJSON:
    @pytest.mark.parametrize('x,y,expected', get_json())
    def test_add(self, x, y, expected):
        assert my_add(int(x), int(y)) == int(expected)
4. 使用values()方法取值

Python字典提供了values()方法,该方法可以将字典的所有值以列表(list)的形式返回。我们可以通过遍历列表来取出对应的值。具体可以通过以下方式实现:

```

person = {"name": "张三", "age": 18, "gender": "男"}

for value in person.values():

print(value)

```

在上述代码中,我们使用for循环遍历了字典person中的所有值,然后使用value变量取出对应的值。输出结果为:

```

张三

18

男

```

需要注意的是,values()方法返回的值是无序的,因此输出结果的顺序与字典中各个键值对的顺序无关。

 Fixture用法

Python测试框架pytest(04)fixture - 测试用例调用fixture、fixture传递测试数据_pytest用例之间值传递fixture_wangmcn的博客-CSDN博客

Fixture特点及优势

  1. 命令灵活:对于setup,teardown,可以不起这两个名字
  2. 数据共享:在conftest.py配置里写方法可以实现数据共享,不需要import导入,可以跨文件共享
  3. scope的层次及神奇的yield组合相当于各种setup和teardown
  4. 实现参数化

fixture在自动化中的应用

  • 场景
  • 测试用例执行时,有时用例需要登陆才能执行,有些不需要登陆。setup和teardown无法满足,fixture可以。默认scope(范围)function
  • 步骤
    • 导入pytest
    • 在登陆的函数上面加@pytest.fixture()
    • 在要使用的测试方法中传入(登陆函数名称)
    • 不穿入的就不登陆直接执行测试方法
  • import pytest
    
    def login():
        print("登陆操作")
        
    def test_search():
        print("搜索")
        
    def test_cart():
        login()
        print("购物车")
    
    def test_order():
        login()
        print("购物车")
    import pytest
    
    @pytest.fixture()
    def login():
        print("登陆操作")
    
    def test_search():
        print("搜索")
    
    def test_cart(login):
        print("购物车")
    
    def test_order(login):
        print("购物车")

 yield的用法

  • 场景:
    • 你已经可以将测试方法【当前要执行的或依赖的】解决了,测试方法后销毁清楚数据的要如何进行呢?
  • 解决:
    • 通过在fixture函数中加入yield关键字,yield时调用第一次返回结果,第二次执行他下面的语句返回
  • 步骤:
    • 在@pytest.fixture(scope=module)
    • 在登陆的方法中加yield,之后加销毁清除的步骤
import pytest


@pytest.fixture()
def login():
    # setup操作
    print("登陆操作")
    token = '123456'
    username = 'hogwarts'
    yield token, username#相当于return
    #teardown操作
    print('完成登出操作')

def test_search(login):
    token,username = login
    print(f'token:{token},username:{username}')
    print("搜索")

 fixture在自动化中的应用-数据共享

  • 场景:
    • 你与其他测试工程师合作一起开发时,公共的模块要在不同文件中,要在大家都访问到的地方
  • 解决:
    • 使用conftest.py这个文件进行数据共享,并且它可以放在不同位置起着不同范围的共享作用
  • 前提:
    • conftest文件名是不能换的
    • 放在项目下是全局的数据共享的地方
  • 执行:
    • 系统执行到参数login时先从本模块中查找是否有这个名字的变量
    • 之后在conftest.py中找是否有
  • 步骤:
    • 将登陆模块带@pytest.fixture写在conftest.py

 conftest.py

import pytest


@pytest.fixture()
def login():
    # setup操作
    print("登陆操作")
    token = '123456'
    username = 'hogwarts'
    yield token, username#相当于return
    #teardown操作
    print('完成登出操作')

test_fixturedemo1.py

def test_search(login):
    token,username = login
    print(f'token:{token},username:{username}')
    print("搜索")

def test_cart(login):
    print("购物车")

def test_order(login):
    print("购物车")

 fixture在自动化中的应用-自动应用

  • 场景:
    • 不想原测试方法有任何改动,或全部都自动实现自动应用,没特例,也都不需要返回值时可以选择自动应用
  • 解决:
    • 使用fixture中参数autouse=True实现
  • 步骤:
    • 在放上面加@pytest.fixture(autouse = True)

conftest.py

import pytest


@pytest.fixture(scope="function",autouse=True)
def login():
    # setup操作
    print("登陆操作")
    token = '123456'
    username = 'hogwarts'
    yield token, username#相当于return
    #teardown操作
    print('完成登出操作')

 test_fixturedemo1.py

def test_search(login):
    token,username = login
    print(f'token:{token},username:{username}')
    print("搜索")

def test_cart():
    print("购物车")

def test_order():
    print("购物车")

在test方法中没有调用login参数的测试用例,也会默认加了login,在执行每个测试用例之前,都会执行一遍登入登出操作

fixture在自动化中的应用-参数化

  • 场景:
    • 测试离不开数据,为了数据灵活,一般数据都是通过参数传的
  • 解法
    • fixture通过固定参数request传递
  • 步骤
    • 在fixture中增加@pytest.fixture(params=[1,2,3,'linda'])
    • 在方法参数写request,方法体里面使用request.param接收参数
import pytest

@pytest.fixture(params=["selenium","appium"])
def login(request):
    print(f"用户名:{request.param}")
    return request.param

def test_demo1(login):
    print(f"demo1 case:数据为:{login}")

运行结果是:
/Users/cangqiongqiyuyunxi/PycharmProjects/pythonProject/venv/bin/python /Applications/PyCharm CE.app/Contents/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py --target test_fixture_param.py::test_demo1 
Testing started at 07:48 ...
Launching pytest with arguments test_fixture_param.py::test_demo1 --no-header --no-summary -q in /Users/cangqiongqiyuyunxi/PycharmProjects/pythonProject/test_fixture

============================= test session starts ==============================
collecting ... collected 2 items

test_fixture_param.py::test_demo1[selenium] 用户名:selenium
PASSED                       [ 50%]demo1 case:数据为:selenium

test_fixture_param.py::test_demo1[appium] 用户名:appium
PASSED                         [100%]demo1 case:数据为:appium


============================== 2 passed in 0.01s ===============================

Process finished with exit code 0

使用 yield可以让每条测试用例执行yield后的语句,return的话将不会执行return后的语句

import pytest

@pytest.fixture(params=["selenium","appium"])
def login(request):
    print(f"用户名:{request.param}")
    yield request.param
    print("完成登出操作")

def test_demo1(login):
    print(f"demo1 case:数据为:{login}")

运行结果是:
/Users/cangqiongqiyuyunxi/PycharmProjects/pythonProject/venv/bin/python /Applications/PyCharm CE.app/Contents/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py --path /Users/cangqiongqiyuyunxi/PycharmProjects/pythonProject/test_fixture/test_fixture_param.py 
Testing started at 07:49 ...
Launching pytest with arguments /Users/cangqiongqiyuyunxi/PycharmProjects/pythonProject/test_fixture/test_fixture_param.py --no-header --no-summary -q in /Users/cangqiongqiyuyunxi/PycharmProjects/pythonProject/test_fixture

============================= test session starts ==============================
collecting ... collected 2 items

test_fixture_param.py::test_demo1[selenium] 用户名:selenium
PASSED                       [ 50%]demo1 case:数据为:selenium
完成登出操作

test_fixture_param.py::test_demo1[appium] 用户名:appium
PASSED                         [100%]demo1 case:数据为:appium
完成登出操作


============================== 2 passed in 0.01s ===============================

Process finished with exit code 0

pytest.ini是什么

  • pytest.ini是pytest的配置文件
  • 可以修改pytest的默认行为
  • 不能使用任何中文符号,包括汉字、空格、引号、冒号等等

pytest.ini

  • 修改用例的命名规则
  • 配置日志格式,比代码配置更方便
  • 添加标签,防止运行过程报警告错误
  • 制定执行目录
  • 排除搜索目录

pytest配置-改变运行规则

  • pytest.ini             //是用来配置哪些命名方式的函数、类或者python文件可以运行
  • check_demo.py //以check_开头命名的测试文件
  • test_first.py       //以test_开头命名的测试文件

pytest.ini

[pytest]
;执行check_开头和 test_开头的所有的文件,后面一定要加*
python_files = check_* test_*
;执行所有的以Test和Check开头的类
python_classes = Test*  Check*
;执行所有以test_和check_开头的方法
python_functions= test_* check_*

check_demo.py //check开头的类及函数也是需要执行

class CheckDemo:
    def check_demo1(self):
        pass

    def check_demo2(self):
        pass

 test_first.py //test开头的文件名/函数名/类名也是需要执行

import logging


def inc(x):
    return x + 1


def test_answer():
    logging.info("这是 answer 测试用例")
    logging.info("断言 assert inc(3) == 5 ")

    assert inc(3) == 5

class TestDemo:
    def test_demo1(self):
        logging.info("这是 demo1 测试用例")
        pass

    def test_demo2(self):
        logging.info("这是 demo2 测试用例")
        pass

在pytest.ini中添加

addopts = -v -s --alluredir=./results

在终端中键入 pytest -vs可以将所有的测试结果打印出来

 

(venv) cangqiongqiyuyunxi@cangqiongqiyuyunxideMacBook-Air testini % pytest -vs
==================================================== test session starts =====================================================
platform darwin -- Python 3.9.6, pytest-7.4.0, pluggy-1.3.0 -- /Users/cangqiongqiyuyunxi/PycharmProjects/pythonProject/venv/bin/python
cachedir: .pytest_cache
rootdir: /Users/cangqiongqiyuyunxi/PycharmProjects/pythonProject/testini
configfile: pytest.ini
collected 3 items                                                                                                            

test_first.py::test_answer FAILED
test_first.py::TestDemo::test_demo1 PASSED
test_first.py::TestDemo::test_demo2 PASSED

========================================================== FAILURES ==========================================================
________________________________________________________ test_answer _________________________________________________________

    def test_answer():
        logging.info("这是 answer 测试用例")
        logging.info("断言 assert inc(3) == 5 ")
    
>       assert inc(3) == 5
E       assert 4 == 5
E        +  where 4 = inc(3)

test_first.py:12: AssertionError
================================================== short test summary info ===================================================
FAILED test_first.py::test_answer - assert 4 == 5
================================================ 1 failed, 2 passed in 0.04s =================================================

pytest配置-指定/忽略执行目录

;设置执行的路径
;testpaths = bilibili baidu
;忽略某些文件夹/目录
norecursedirs = result logs datas test_demo*

pytest logging收集日志

[pytest]
;日志开关 true false
log_cli = true
;日志级别
log_cli_level = info
;打印详细日志,相当于命令行加 -vs
addopts = --capture=no
;日志格式
log_cli_format = %(asctime)s [%(levelname)s] %(message)s (%(filename)s:%(lineno)s)
;日志时间格式
log_cli_date_format = %Y-%m-%d %H:%M:%S
;日志文件位置
log_file = ./log/test.log
;日志文件等级
log_file_level = info
;日志文件格式
log_file_format = %(asctime)s [%(levelname)s] %(message)s (%(filename)s:%(lineno)s)
;日志文件日期格式
log_file_date_format = %Y-%m-%d %H:%M:%S

log_file = ./log/test.log会将logging的输出存放在test.log中

pytest插件开发[进阶]

  • pytest插件分类
    • 外部插件:pip install 安装的插件
    • 本地插件:pytest自动模块发现机制(conftest.py存放的)
    • 内置插件:代码内部的_pytest目录加载
  • pytest hook介绍
  • pytest hook执行顺序
  • pytest常用插件
    • pip install pytest-ordering  控制用例的执行顺序(重点)
      pip install pytest-xdist    分布式并发执行测试用例(重点)
      pip install pytest-dependency   控制用例的依赖关系 (了解)
      pip install pytest-rerunfailures   失败重跑(了解)
      pip install pytest-assume          多重较验(了解)
      pip install pytest-random-order  用例随机执行(了解)
      pip install pytest-html            测试报告(了解)

      可以通过​​​​​​​PyPI · The Python Package Index

    • 搜索pytest看相关的插件

    • pytest执行顺序控制

      • 场景:
        • 对于集成测试,经常会有上下文依赖关系的测试用例
        • 对于10个步骤,拆成10条case,这时候能知道到底执行到哪步报错
        • 用例默认执行顺序:自上而下执行
      • 解决:
        • 可以通过setup,teardown和fixture来解决,也可以使用对应的插件
        • 安装:pip install pytest-ordering
        • 用法:@pytest.mark.run(order = 2)
        • 注意:多个插件装饰器(>2)的时候,有可能会发生冲突 

pytest并行与分布式执行

场景 1:

测试用例 1000 条,一个用例执行 1 分钟,一个测试人员执行需要 1000 分钟。

通常我们会用人力成本换取时间成本,加几个人一起执行,时间就会 缩短。

如果 10 人一起执行只需要 100 分钟,这就是一种分布式场景。

场景 2:

假设有个报名系统,对报名总数统计,数据同时进行修改操作的时候有可能出现问题,

需要模拟这个场景,需要多用户并发请求数据。

解决:

使用分布式并发执行测试用例。分布式插件:pytest-xdist

安装及运行: pip install pytest-xdist

注意: 用例多的时候效果明显,多进程并发执行,同时支持 allure

from time import sleep

import pytest

def test_foo():
    sleep(1)
    assert True

def test_bar():
    sleep(1)
    assert True

def test_bar1():
    sleep(1)
    assert True


def test_foo1():
    sleep(1)
    assert True


def test_bar2():
    sleep(1)
    assert True


def test_bar3():
    sleep(1)
    assert True

在终端中执行pytest -n auto -vs将会分布式执行我们的测试用例

如果串型执行我们的测试用例 总共会用6s的执行时间

 

 如果分布式执行我们的测试用例 我们将大大缩短执行我们测试用例的时间

 pytest hook介绍

hook是一个勾子函数

  •  hook是个函数,在系统消息触发时被系统调用
  • 自动触发机制
  • hook函数的名称是确定的
  • pytest有非常多的勾子函数
  • 使用时直接编写函数体
root
└── pytest_cmdline_main
├── pytest_plugin_registered
├── pytest_configure
│ └── pytest_plugin_registered
├── pytest_sessionstart
│ ├── pytest_plugin_registered
│ └── pytest_report_header
├── pytest_collection #在收集测试用例时就需要hook
│ ├── pytest_collectstart
│ ├── pytest_make_collect_report
│ │ ├── pytest_collect_file
│ │ │ └── pytest_pycollect_makemodule
│ │ └── pytest_pycollect_makeitem
│ │ └── pytest_generate_tests
│ │ └── pytest_make_parametrize_id
│ ├── pytest_collectreport
│ ├── pytest_itemcollected
│ ├── pytest_collection_modifyitems
│ └── pytest_collection_finish
│ └── pytest_report_collectionfinish
├── pytest_runtestloop
│ └── pytest_runtest_protocol
│ ├── pytest_runtest_logstart
│ ├── pytest_runtest_setup
│ │ └── pytest_fixture_setup
│ ├── pytest_runtest_makereport
│ ├── pytest_runtest_logreport
│ │ └── pytest_report_teststatus
│ ├── pytest_runtest_call #将会在调用测试用例时实现
│ │ └── pytest_pyfunc_call
│ ├── pytest_runtest_teardown
│ │ └── pytest_fixture_post_finalizer
│ └── pytest_runtest_logfinish
├── pytest_sessionfinish
│ └── pytest_terminal_summary
└── pytest_unconfigure

原始链接
pytest官方关于hook的说明
https://docs.pytest.org/en/stable/reference.html?#hooks 134

https://docs.pytest.org/en/stable/_modules/_pytest/hookspec.html#pytest_cmdline_parse 76

流程说明原始链接
https://github.com/pytest-dev/pytest/issues/3261 81



找到hook的源码:python3.9/site-packages/_pytest/hookspec.py

conftest.py //将setup和teardown引入在测试用例前后进行调用

from typing import Optional


def pytest_runtest_setup(item: "Item") -> None:
    print("setup")

def pytest_runtest_teardown(item: "Item", nextitem: Optional["Item"]) -> None:
    print("teardown")

test_hook.py // 实际跑的测试用例

def test_demo1():
    print("test hook")

 hook函数总结

  • hook函数名字固定
  • hook函数会被自动执行
  • 执行时有先后顺序的
  • pytest定义了很多hook函数,可以在不同阶段实现不同的功能
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值