pytest知识点

一、单元测试框架
1.什么是单元测试框架
单元测试框架是在自动化测试或者白盒测试中对软件的最小单元(函数、方法)进行测试的框架
2.单元测试框架分类
Python:unittest,pytest(主流)
Java:Testing(主流)和Junit
3.单元测试框架主要做什么
发现测试用例
执行测试用例
判断测试结果
生成测试报告
4.集成度对比
Pytest>UnitTest>RobotFramework>airtest

二、Pytest间接以及常用插件安装
1.pytest是一个非常成熟的单元测试框架,灵活和简单,安装命令:pip install pytest
2.可以结合selenium,requests,appium完成各种不同的自动化
3.可以生成自定义allure报告以及和Jenkins持续集成
4.pytest有很多强大的插件
pytest
pytest-html(生成html报告的插件)
pytest-xdist(多线程运行的插件)
pytest-ordering(改变用例的执行顺序的插件)
pytest-rerunfailures(失败用例重跑的插件)
allure-pytest(生成美观自定义的allure报告)
通过在项目的根目录下新建一个:requirements.txt文件保存插件,通过命令安装:pip install -r requirements.txt

三、pytest默认测试用例的规则以及基础应用
1.模块名必须以test_开头或者test结尾
2.测试类必须以Test开头,并且不能带有init方法
3.测试用例必须以test
开头
4.断言使用基本的assert即可

import pytest

class TestCase:
    def test_01(self):
        print('第一条用例')
        assert 1 == 1

    def test_02(self):
        print('第二条用例')
        assert 2 == 3

执行:
1).通过命令行方法执行

# 执行所有用例
pytest
# 执行某个用例文件
pytest test_case.py
# 执行某用例中某方法
pytest test_case.py::test_01

执行的参数:
-v 输出详细信息,如:pytest -v
-s 输出调试信息
-rA 测试结果的简单统计
-n 多线程运行,如:pytest -vs -n=2
--reruns num 失败重跑(前提安装插件:pytest-rerunfailures)
raise Exception() 抛出异常
try except 解决异常
-x 出现一个用例失败则停止测试 ,如:pytest -vs -x
---maxfail 出现几个失败才终止,如:pytest -vs --maxfail=2
--html 生成html的报告(前提安装插件:pytest-html),如:pytest -vs --html ./report/report.html
-k 运行测试用例名称中包含某个字符串的测试用例, 如:pytest -vs -k "name"

2).通过主函数main()方法执行

if __name__ == '__main__':
    pytest.main()

添加参数:pytest.main(["-vs"])
3).通过全局配置文件pytest.ini文件执行
注意:
a.一般放在项目的根目录,名称必须是pytest.ini,当有中文时可能需要改变编码格式为GB2312
b.pytest.ini文件可以改变默认的测试用例规则
不管是命令行运行还是主函数运行,都会加载这个配置文件
c.Pytest默认寻找当前路径下所有的文件与子文件中以test开头的文件夹、文件、函数作为识别对象
d.Pytest默认不输出任何打印信息,如果需要看打印信息,要在运行时添加-s指令,如:pytest.main(['-s'])
pytest.ini

[pytest]
addopts = -vs -m "smoke"
testpaths = ./testcases
python_files = test_*.py  # 测试test_开头的文件
python_classes = Test*  # 测试Test开头的类
python_functions = test_* # 测试test_开头的方法
# 测试用例标记分组执行
markers = 
    smoke:冒烟用例
    product_manage:商品管理 

这里的-m "smoke"表示只执行冒烟用例

# 标记分组执行
import pytest

class TestCase:
    # 标记分组执行
    @pytest.mark.smoke
    def test_01(self):
        print("测试pytest案例01")

    def test_02(self):
        print("测试pytest案例02")

四、pytest跳过测试用例
1.无条件跳过

@pytest.mark.skip(reason="无理由跳过")

2.有条件跳过

@pytest.mark.skipif(reason="工作经验少于10年跳过")
# pytest跳过测试用例
import pytest


class TestCase:
    # 无条件跳过
    @pytest.mark.skip(reason="无理由跳过")
    # 有条件跳过
    # @pytest.mark.skip(reason="工作经验不少于10年")
    def test_01(self):
        print("测试pytest案例01")

    def test_02(self):
        print("测试pytest案例02")

五、pytest测试用例的前后置固件
common_util.py

class CommonUtil:
    def setup_class(self):
        print("每个类用例之前执行一次")

    def teardown_class(self):
        print("每个类用例之后执行一次")

    def setup(self):
        print("每个用例之前执行一次")

    def teardown(self):
        print("每个用例之后执行一次")

test_case.py

import pytest
from common.common_util import CommonUtil

class TestCase(CommonUtil):

    def test_01(self):
        print("测试pytest案例01")

    def test_02(self,):
        print("测试pytest案例02")

六、使用fixture实现部分前后置

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

1.scope:作用域
function: 在函数之前和之后执行
1).手动调用的方式是在测试用例的参数里加入fixture的名称

# 使用fixture实现部分前后置

import pytest


@pytest.fixture(scope="function")
def exe_database_sql():
    print("执行sql查询")


class TestCase():

    @pytest.mark.skip(reason="无理由跳过")
    def test_01(self):
        print("测试pytest案例01")

    # 手动执行方法fixture
    def test_02(self, exe_database_sql):
        print("测试pytest案例02")

2).如果说fixture通过return或yield返回值的话,可以把这个值传递到测试用例当中,并且值是通过固件的名字传递的

# 使用fixture实现部分前后置
import pytest


# 自动执行方法fixture
@pytest.fixture(scope="class", autouse=True)
def exe_database_sql():
    print("执行sql查询")
    yield "success"
    print("关闭数据库链接")


class TestCase():

    def test_01(self):
        print("测试pytest案例01")

    def test_02(self, exe_database_sql):
        print("测试pytest案例02")
        print(exe_database_sql)

class: 在类之前和之后执行
1).手动调用的方式是在类的上面加上@pytest.mark.usefixtures("name")装饰器调用

# 使用fixture实现部分前后置
import pytest


@pytest.fixture(scope="class", autouse=False)
def exe_database_sql():
    print("执行sql查询")
    yield "success"
    print("关闭数据库链接")


# 手动执行类fixture
@pytest.mark.usefixtures("exe_database_sql")
class TestCase(CommonUtil):

    def test_01(self):
        print("测试pytest案例01")

    def test_02(self):
        print("测试pytest案例02")
        print(exe_database_sql)

package/session: 在整个项目会话之前和之后执行
1).一般会结合conftest.py文件来实现
module:在模块级别中只执行一次

2.autouse: 自动执行,默认是Flase
如果希望在另外一个py文件中调用需要结合conftest.py文件使用

3.params:实现参数化
1.如何把值传到fixture是通过在fixture函数的参数里加入request来接受参数,然后通过request.param来取值(此处param没有s)

# param实现参数化
import pytest


# 使读取数据方法
def read_yaml():
    return ['成龙', '甄子丹', '李连杰']


@pytest.fixture(scope="function", autouse=False, params=read_yaml())
def exe_database_sql(request):
    print(request.param)
    print("执行sql查询")
    yield request.param
    print("关闭数据库链接")


class TestCase:

    def test_01(self):
        print("测试pytest案例01")

    def test_02(self, exe_database_sql):
        print("测试pytest案例02")
        print(exe_database_sql)

4.ids:不能单独使用,必须和params一起使用,作用是对参数起别名

# ids对参数起别名
import pytest

# 使读取数据方法
def read_yaml():
    return ['成龙', '甄子丹', '李连杰']

@pytest.fixture(scope="function", autouse=False, params=read_yaml(), ids=['chenglong', 'zenzidan', 'lilianjie'])
def exe_database_sql(request):
    print("执行sql查询")
    yield request.param
    print("关闭数据库链接")

class TestCase:
    def test_01(self):
        print("测试pytest案例01")

    def test_02(self, exe_database_sql):
        print("测试pytest案例02")
        print(exe_database_sql)

5.name:作用是给fixture起别名
特别注意:一旦使用了别名,fixture名称就不能用,只能用别名name

# name对fixture起别名
import pytest

# 使读取数据方法
def read_yaml():
    return ['成龙', '甄子丹', '李连杰']

@pytest.fixture(scope="function", autouse=False, params=read_yaml(), ids=['chenglong', 'zenzidan', 'lilianjie'], name="db")
def exe_database_sql(request):
    print("执行sql查询")
    yield request.param
    print("关闭数据库链接")

class TestCase:

    def test_01(self):
        print("测试pytest案例01")

    def test_02(self, db):
        print("测试pytest案例02")
        print(db)

用例顺序按照指定方式:@pytest.mark.run(order=)
指定用例运行:@pytest.mark.任意名字,执行时添加‘-m 任意名字’

import pytest
class TestCase:
    @pytest.mark.smoke
    def test_01(self):
        print("测试pytest案例01")
  
    @pytest.mark.run(order=1)
    def test_02(self):
        print('测试pytest案例02’)
if __name__ = '__main__':
    pytest.main(['s', '-v', '-m', 'smoke'])

七、fixture结合conftest.py文件使用
1.conftest.py是专门用于存放fixture的配置文件,名称是固定的不能变
conftest.py

import pytest

# 使读取数据方法
def read_yaml():
    return ['成龙', '甄子丹', '李连杰']

@pytest.fixture(scope="function", autouse=False, params=read_yaml(), ids=['chenglong', 'zenzidan', 'lilianjie'], name="db")
def exe_database_sql(request):
    print("执行sql查询")
    yield request.param
    print("关闭数据库链接")

test_case.py

# 直接使用固件fixture的别名db
class TestCase:

    def test_01(self):
        print("测试pytest案例01")

    def test_02(self, db):
        print("测试pytest案例02")
        print(db)

2.在conftest.py文件里所有的方法在使用时不需要打包导包
3.conftest.py文件可以有多个,并且多个conftest.py文件里面的多个fixture可以被一个用例调用

八、setup、teardown、setup_class、teardown_class、fixture、conftest优先级
会话:fixture的session级别优先级最高,setup_session

类:fixture的class级别优先级最高
setup_class > setup method > setup > teardown > teardown method > teardown class
类:setup_class

函数:fixture的function级别优先级最高,函数:setup_function

九、总结pytest执行过程
1.查询当前目录下的conftest.py文件
2.查询当前目录下的pytest.ini文件,找到测试用例的位置
3.查询用例目录下的conftest.py文件
4.查询测试用例的py文件中是否有setup、teardown、setup_class、teardown_class
5.再根据pytest.ini文件的测试用例的规则去查找用例并执行

十、pytest断言
就是使用pytest自己的断言assert

1.assert flag is True
2.assert 1 == 1
3.assert 'a' in 'abc'
# pytest断言assert
class TestCase:
    def test_01(self):
        print("测试pytest案例01")
        assert 1 > 2

    def test_02(self, db):
        print("测试pytest案例02")
        print(db)
        assert 1 >= 1

十一、pytest结合allure-pytest插件生成美观的报告
1.安装allure-pytest插件
2.下载allure,下载解压之后,还需配置环境变量(把allure目录下的bin目录配置到系统变量的path路径),下载地址:https://github.com/allure-framework/allure2/releases
验证allure是否安装成功:allure --version
1).先在dos窗口验证

allure --version

2).在pycharm验证(如果验证失败,重启pycharm)
3.生成allure报告
1)生成临时的json报告,在pytest.ini文件加入以下内容:

addopts = -vs --alluredir=./temps --clean-alluredir

--alluredir=./temps 生成临时报告
--clean-alluredir 清空临时报告
2)生成正式的allure报告
run.py

import pytest
import os
import time

if __name__ == '__main__':
    pytest.main()
    time.sleep(3)
    os.system("allure generate ./temps -o ../report --clean")

3)定制化
注意:安装的allure-pytest和allure版本需相匹配

十二、pytest之parametrize()实现数据驱动
方法:@pytest.mark.parametrize(args_name,args_value)
args_name:参数名称,用于将参数值传递给函数
args_vlaue:参数值(列表和字典列表,元祖和字典元祖)有n个值用例执行n次
第一种用法:

# pytest之parametrize()实现数据驱动
import pytest

class TestCase:
    @pytest.mark.parametrize('caseinfo', ['chenglong', 'zenzidan', 'lilianjie'])
    def test_01(self, caseinfo):
        print("测试pytest案例01")
        print(caseinfo)

第二种用法:

# pytest之parametrize()实现数据驱动
import pytest

class TestCase:
    @pytest.mark.parametrize('arg1, arg2', [['chenglong', 'zenzidan'], ['lilianjie', 'xietingfeng']])
    def test_01(self, arg1, arg2):
        print("测试pytest案例01:"+str(arg1)+""+str(arg2))

十三、YAML格式测试用例读写,清楚封装
1.yaml是一种数据格式,扩展名可以使yaml,yml,支持#注释,通过缩进表示层级,区分大小写。
用途:
用于做配置文件(yam,ini)
用于编写自动化测试用例
2.数据组成
1)map对象,键值对(键和值之间有空格)
yaml_util.py

# 读取yaml,map对象
import yaml
import os

# 获取项目的根目录
def get_obj_path():
    # 当前文件的目录
    # return os.path.dirname(__file__)
    # 项目根目录
    return os.path.dirname(__file__).split('common')[0]

# 读取yaml,需要安装pip install pyyaml
def read_yaml(yamlpath):
    # with open('yaml文件的路径', mode='r', encoding='utf-8') as f: value = yaml.load(stream=f, Loader=yaml.FullLoader)
    with open(get_obj_path()+yamlpath, mode='r', encoding='utf-8') as f: value = yaml.load(stream=f, Loader=yaml.FullLoader)
    return value

# # 读取yaml文件,r为读取文件内容
    # files = open(filename, 'r', encoding='utf-8')
    # # 读取files的内容
    # data = yaml.load(files, Loader=yaml.FullLoader)
    # return data

if __name__ == '__main__':
    print(read_yaml('pytest1/get_token.yaml'))

get_token.yaml

name: 'token value'

2)数组(list)使用'-'表示列表
yaml_util1.py

# 读取yaml,list数组
import yaml
import os

# 获取项目的根目录
def get_obj_path():
    # 当前文件的目录
    # return os.path.dirname(__file__)
    # 项目根目录
    return os.path.dirname(__file__).split('common')[0]

# 读取yaml,需要安装pip install pyyaml
def read_yaml(yamlpath):
    # with open('yaml文件的路径', mode='r', encoding='utf-8') as f: value = yaml.load(stream=f, Loader=yaml.FullLoader)
    with open(get_obj_path()+yamlpath, mode='r', encoding='utf-8') as f: value = yaml.load(stream=f, Loader=yaml.FullLoader)
    return value

if __name__ == '__main__':
  print(read_yaml('pytest1/get_token1.yaml'))

get_token1.yaml

yamlist:
  - name1: '成龙'
  - name2:
      - title1: '李连杰'
      - title2: '谢霆锋'
  - name3: '甄子丹'

十四、parametrize结合yaml实现接口自动化测试

# 未封装request
import requests
import pytest
from common.common_util import CommonUtil
from common.yaml_util import read_yaml


class TestApi(CommonUtil):
    session = requests.session()

    @pytest.mark.parametrize('caseinfo', read_yaml('./pytest1/get_token2.yaml'))
    def test_01_get_token(self, caseinfo):
        print('获取统一接口鉴权码')
        print(caseinfo)
        name = caseinfo['name']
        method = caseinfo['request']['method']
        url = caseinfo['request']['url']
        data = caseinfo['request']['data']
        validate = caseinfo['validate']
        res = TestApi.session.request(method=method, url=url, params=data)
        result = res.json()
        print(result)

封装request
requests_util.py

import requests


class RequestUtil:
    session = requests.session()

    def send_request(self, method, url, data, **kwargs):
        method = str(method).lower()
        if method == "get":
            res = RequestUtil.session.request(method, url, params=data, **kwargs)
        elif method == "post":
            res = RequestUtil.session.request(method, url, json=data, **kwargs)
        return res

test_case.py

# 封装request
import pytest
from common.common_util import CommonUtil
from common.yaml_util import read_yaml
from common.requests_util import RequestUtil


class TestApi(CommonUtil):

    @pytest.mark.parametrize('caseinfo', read_yaml('./pytest1/get_token2.yaml'))
    def test_01_get_token(self, caseinfo):
        print('获取统一接口鉴权码')
        print(caseinfo)
        name = caseinfo['name']
        method = caseinfo['request']['method']
        url = caseinfo['request']['url']
        data = caseinfo['request']['data']
        validate = caseinfo['validate']
        res = RequestUtil.session.request(method=method, url=url, params=data)
        result = res.json()
        print(result)

十五、接口自动化测试框架的封装
1.yaml文件如何实现接口关联封装
2.yaml文件如何实现动态参数的处理
3.yaml文件如何实现文件上传
4.yaml文件如何实现解决断言,特别是当有参数化的时间如何断言
5.yaml文件数据量太大如何处理
6.接口自动化框架的扩展性:加密接口、签名接口、自定义的功能接口

接口自动化测试框架规则

1.必须有四个一级关键字:name,base_url,rquest,validate
2.在request一级关键字下必须包括两个二级关键字:method,url
3.传参方式:在request一级关键字下,通过二级关键字传递参数
1)如果是get请求,通过params传参
2)如果是post请求,传json格式,通过json关键字传参;传表单格式,通过data关键字传参
3)如果是文件格式,通过files关键字传参,如:

files: media: "D:\video.png"

4.如果需要做接口关联,必须使用一级关键字:extract,框架支持json提取和正则提取
提取:
1)json提取方式:

extract: access_token: access_token

2)正则表达式方式:

extract:access_token: '"access_token":"(.*?)"'

5.热加载,当yaml需要使用动态参数时,那么可以在debug_talk.py文件中写方法调用,如:

# 获取随机数的方法
def get_random_number(self, min, max):
    return random.randint(int(min), int(max))

6.框架支持两个断言方式,分别为equals和contains断言,如:
validate:

  • equals: {status_code: 205}
  • contains: {access_token}
    7.数据驱动使用csv和一级关键字parameters实现。
    1)yaml写法
    parameters:
    name_appid: appidvalue
    2)csv写法

8.日志监控,异常处理以及基础路径的设置

十六、YAMLJ简介
1.用途
1)用于做配置文件,配置全局的数据:环境变量、数据库信息、账号信息、日志格式、日志报告名称等。
2)用于写测试用例(接口自动化测试用例),用于数据驱动

2.YAML简介
yaml是一种数据格式,支持注释、换行、多行字符串、裸字符串等

3.yaml语法规则
1)大小写敏感
2)使用缩进表示层级关系
3)缩进不管空格的数量,只要层级的左边对齐就行
4)#表示注释

4.yaml和json数据结构对比
JSON:
1)Map对象:键值对(字典dict),使用{}括起来,如:

{name: 陈铭}

2)数组(列表list),使用[]括起来,如:

[{name: 陈铭}]

YAML:
1)Map对象:键值对(字典dict)

name: 陈铭
#  一行的写法
list: {name: 陈铭}

2)数组(列表list),用一组横行'-'开头

yamllist:
    - name: 陈铭
# 一行的写法
yamlist: [{name: 陈铭}]

5.yaml配置文件处理

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容