pytest简介

  1. pytest是一个非常成熟的python的单元框架,比unittest更灵活,容易上手。

  2. pytest可以和seleniumrequestsappium结合实现web自动化,接口自动化,app自动化

  3. pytest可以实现测试用例的跳过以及reruns失败用例重试。

  4. pytest可以和allure生成非常美观的测试报告。

  5. pytest可以和Jenkins持续集成。

  6. pytest有很多非常强大的插件,并且这些插件能够实现很多实用的操作。

单元测试框架

  1. 单元测试框架是指在软件开发中,针对软件的最小单位(函数,方法)进行正确性的检查测试

  2. 单元测试所需要的框架:

    java:junit和testing

    python:unittest和pytest

  3. 单元测试框架主要使用:

    测试发现:从多个文件里面找到我们的测试用例

    测试执行:按照一定的顺序和规则去执行,并生成结果

    测试判断:通过断言判断预期结果和实际结果的差异

    测试报告:统计测试进度,耗时,通过率,生成测试报告

单元测试框架和自动化的关系

  1. 提高测试效率,降低维护成本

  2. 减少人工干预。提高测试的准确性,增加代码的重用性

  3. 核心思想是让不懂代码的人也能够通过这个框架去实现自动化测试

  4. 单元测试框架和自动化测试框架的关系

    单元测试框架:只是自动化测试框架的组成部分之一

    python设计模式:只是自动化测试框架的组成部分之一

    数据驱动……

    关键字驱动

    全局配置文件的封装

    日志监控

    selenium,request二次封装

    断言

    报告邮件

    更多……

需要安装的模块

1
2
3
4
5
6
pytest
pytest-html
pytest-xdist
pytest-ordering
pytest-rerunfailures
allure-pytest

输入指令安装

这里无意中看到了可以集体安装的方法,如果你卸载了python或者重置电脑后,安装还需要安装很多的模块安装包,一次次输入太过于麻烦,如果你这里保存了一个txt文本,在里面保存了你安装过的模块,后续安装可以直接输入指令安装你安装过的模块,这里需要你自行保存,虽然感觉很麻烦,但你如果去安装一大堆模块后,电脑重置后安装更麻烦,这里可以保存你的模块安装记录,然后使用指令一口气安装岂不是更好。

1
2
3
4
# r 后面是你的txt文件
pip install - r requirements.txt
# 检查安装的版本号
pytest --version
模块介绍
pytestpython的单元框架
pytest-html生成html格式的自动化测试报告
pytest-xdist测试用例分布式执行,多CPU分发
pytest-ordering用于改变测试用例的执行顺序
pytest-rerunfailures用例失败后重跑
allure-pytest用于生成美观的测试报告

使用pytest

  1. 模块名必须使用以test_开头或者以_test结尾
  2. 测试类必须以Test开头,并且不能有init方法
  3. 测试方法必须以test开头

pytest测试用例的运行方式

  1. 主函数模式

    • 运行所有:pytest.main()

    • 运行指定模块:pytest.main(‘-s’,’test_login.py’)

    • 指定目录:pytest -vs ./interface_testcase

    • 通过nodeid指定运行:nodeid由模块名,分隔符,类名,方法名,函数名组成。
      pytest.main([‘-vs’,’./interface_testcase/test_interface.py::test_04_func’])
      pytest.main([‘-vs’,’./interface_testcase/test_interface.py::TestInterface::test_03_zhiliao’])

  2. 命令行模式

    • 运行所有:pytest

    • 运行指定模块:pytest -vs test_login.py

    • 指定目录:pytest -vs ./interface_testcase

    • 通过nodeid指定运行:pytest -vs ./interface_testcase/test_interface.py::test_04_func

  3. 参数详解:

参数详解
-s表示输出调试信息,包括print打印信息。
-v显示更详细的信息。
-vs两个参数可以一起使用。
-n支持多线程或者分布式运行测试用例。如:pytest -vs ./test_login.py -n 2
–renuns NUM失败用例重跑。
-x表示只需要有一个用例报错。那么测试停止。
–maxfall=2出现两个用例失败就停止。
–html ./report/report.html生成测试报告

使用pytest.ini执行(重要)

pytest.ini这个文件它是pytest单元测试框架的核心配置文件。

  1. 位置:一般都是放在根目录文件夹下。
  2. 编码:必须是ANSI码,可以使用notpad++来修改编码格式。
  3. 作用:改变pytest默认行为。
  4. 运行的规则:不管是主函数的模式运行,命令行模式运行,都会去读取这个配置文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[pytest]
# 命令行参数,用空格分隔,--html生成测试报告
addopts = -vs --html ./report/report.html
# 测试用例文件夹路径,可自己配置
testpaths = ./interface_testcase
# 配置测试搜索的模块文件名称
pyhon_files = test_*.py
# 配置测试搜索的测试类名
python_classes = Test*
# 配置测试搜索的测试函数名
python_functions = test

markers =
smoke: 冒烟用例
usermanage: 用户管理模块
productmanage: 商品管理模块

pytest改变默认执行顺序

  1. unittest ascll的大小来绝对的顺序
  2. pytest 默认从上到下
  3. 改变默认的执行顺序:使用mark标记
    @pytest.mark.run(order=3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import pytest

class TestLogin:
def test_01(self):
print("测试1")

def test_02(self):
print("测试2")

@pytest.mark.run(order=1)
def test_03(self):
print("测试3")
@pytest.mark.run(order=3)
def test_4(self):
print("测试4")

if __name__ == '__main__':
pytest.main(["-s","test_login.py"])

如何分组执行(冒烟,分模块执行,接口和web执行)

cmd执行命令或者直接在py文件中执行

1
2
3
4
5
6
# smoke:冒烟用例,分布在各个模块里面
pytest -vs -m "smoke"
pytest -vs -m "smoke or usermanage"

@pytest.mark.smoke
@pytest.mark.usermanage

pytest跳过测试用例

  1. 无条件跳过

    1
    @pytest.mark.skip(reasson="测试1")
  2. 有条件跳过

    1
    @pytest.mark.skipif(age>=18,reasson='已成年')

实现部分使用

装饰器

使用@pytest.fixture()装饰器来实现部分用例的前后置

@pytest.fixture(scope="",params="",autouse="",ids="",name="")用例解释:

参数解释
scope表示的是被@pytest.fixture标记的方法的作用域,function(默认),class,module,package/session
params参数化(支持,列表[],元组(),字典[{},{},{}],字典元组({},{},{})
autouse=Ture自动执行,默认False
ids当使用params参数化时,给每一个值设置一个变量名,意义不大
name给表示的是被@pytest.fixture标记的方法取一个别名(当取了别名后,那么原来的名称就用不了了)

params

参数使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import pytest

@pytest.fixture(scope="module",params=['夏明','甄子丹','夏梦'],ids=['xm','zzd','xiaomeng'])
def my_fixture(request):

print('\n这是前置的方法')
yield request.param # return和yield都表示返回的意思,但是return的后面不能有代码,yield返回后后面可以接代码
print('\n这是后置的方法')
# return request.param

class Testdemo01:
def test_01_baili(self):
print('\n测试百里')

def test_02_xingyao(self,my_fixture):
print('\n测试星瑶')
print('----------'+ str(my_fixture))

params=['夏明','甄子丹','夏梦']这里params是参数名,有s
request.param这里是属性名,是没有s的。

实现全局使用

用conftest.py和@pytest.fixture来实现全局的前置应用(比如:项目的全局登录,模块的全局处理)

  1. conftest.py文件单独存放的一个夹具配置文件,名称不能更改。
  2. 用处可以在不同的py文件中使用一个fixture函数。
  3. 原则上conftest.py需要和运行的用例放到统一层,并且不需要做任何import导入的操作。

总结:

setup/teardown,setup_class/teardown_class 它是作用于所有的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import pytest

class TestMashang:
# 这个在所以用例之前只执行一次
def setup_calss(self):
print('\n在每个类执行前的初始化的工作,比如:创建日志对象,创建数据库的连接,创建接口的请求对象')

# 在每个用例只执行一次。
def setup(self):
print('\n在执行测试用例之前初始化的代码:打开浏览器,加载网页')

def test_01_ceshi(self):
print('\n测试----01')

def test_02_ceshi(self):
print('\n测试----02')

def teardown(self):
print('\n在测试用例之后的扫尾的代码:关闭浏览器')

def teardown_calss(self):
print('\n在每个类执行后的扫尾的工作,比如:销毁日志对象,销毁数据库的连接,销毁接口的请求对象。')

if __name__ == '__main__':
pytest.main(["-vs"])

@pytest.fixture() 它的作用是既可以部分也可以全局前后置。
conftest.py@pytest.fixture()结合使用,作用于全局的前后置。

parametrize()基本用法

@pytest.mark.parametrize(args_name,args_value)

args_name: 参数名
args_value:参数值(列表,元组,字典列表,字典元组),有多个值用例就会执行多少次

  • 第一种方式:
1
2
3
4
5
6
7
8
9
10
import pytest

class TestApi:

@pytest.mark.parametrize("args",['百里','星瑶','依然'])
def test_01_xingyao(self,args):
print(args)

if __name__ == '__main__':
pytest.main(['test_api.py','-vs'])
  • 第二种方式(跟unittest的ddt里面的@unpack解包的一样):
1
2
3
4
5
6
7
8
9
10
import pytest

class TestApi:

@pytest.mark.parametrize('name,age',[['星瑶','18'],['星微','16']])
def test_01_xingyao(self,name,age):
print(name,age)

if __name__ == '__main__':
pytest.main(['test_api.py','-s'])

yaml详解

程序员必备网站推荐:

yaml简介:

yaml是一种数据格式,支持注释,多行字符串,裸字符串(整形,字符串)。

  1. 用于全局的配置文件:ini/yaml
  2. 用于写测试用例(接口测试用例)
  3. 语法规则:
    • 区分大小写
    • 使用缩进表示层级,不能使用tab键,只能用空格(和python类似)
    • 缩进是没有数量的,只需要前面是对齐的就行
    • 注释是#

数组组成

Map对象,键值对 键:(空格)值

多行写法:
1
2
3
4
5
6
7
8
9
-
msxy:
name: 百里
age: 18

-
msjy:
- name: 星瑶
- age: 20
一行写法:
1
2
-
msxy: {name: 星瑶,age: 18}

读取yaml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import yaml

class YamlUtil:
def __init__(self,yaml_file):
"""
通过init方法把yaml文件传入到这个类
:param yaml_file:
"""
self.yaml_file = yaml_file

def read_yaml(self):
"""
读取yaml,对yaml反序列化,就是把我们的yaml格式转换成dict格式
:return:
"""
with open(self.yaml_file,encoding='UTF-8') as f:
value = yaml.load(f,Loader=yaml.FullLoader)
print(value)

if __name__ == '__main__':
YamlUtil('test_api.yaml').read_yaml()

yaml自动化

介绍:
  1. 断言的封装。
  2. allure报告的定制。
  3. 关键字驱动和数据驱动结合自动化测试。
  4. python的反射:
    • 正常:先初始化对象,在调方法。
    • 反射:通过对象得到类对象。然后通过类对象调用方法
  5. Jenkins在持续集成和allure报告基础集成,并且根据自动化报告的错误率发送电子邮件

把这几个文件放在一个目录下

创建test_api.py文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import pytest
import requests
from testcase.yaml_util import YamlUtil
import os

class TestApi:

@pytest.mark.parametrize("args",YamlUtil(os.getcwd() + './testcase/test_api.yaml').read_yaml())
def test_01_xingyao(self,args):
# print(args)
url = args['request']['url']
# url = 'https://api.weixin.qq.com/cgi-bin/token'
params = args['request']['params']
# params = {
# 'great_type': 'client_credential',
# 'appid': 'wx6b11b3efd1cdc290',
# 'secret': '106a9c6157c4db5f6029918738f9529d'
# }
res = requests.get(url,params=params)
print(res.text)

再创建yaml_util.py文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import yaml

class YamlUtil:
def __init__(self,yaml_file):
"""
通过init方法把yaml文件传入到这个类
:param yaml_file:
"""
self.yaml_file = yaml_file

def read_yaml(self):
"""
读取yaml,对yaml反序列化,就是把我们的yaml格式转换成dict格式
:return:
"""
with open(self.yaml_file,encoding='UTF-8') as f:
value = yaml.load(f,Loader=yaml.FullLoader)
return value

创建test_yaml.yaml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
-
name: 获得token鉴权码的接口
request:
url: https://api.weixin.qq.com/cgi-bin/token
method: get
headers:
Content-Type: application/json
params:
great_type: client_credential
appid: wx6b11b3efd1cdc290
secret: 106a9c6157c4db5f6029918738f9529d
validate:
- eq: {expires_in: 7200}

这里我用了全局执行文件,创建pytest.ini文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[pytest]

; addopts = -s --alluredir ./temp

addopts = -vs

testpaths = ./testcase

python_files = test_*.py

python_classes = Test*

python_functions = test

markers =
smoke:冒烟用例
usermanage:用户管理模块
productmanage:商品管理模块

需要一个可以在控制端执行的文件(也可以不需要创建这个文件,可以丢在另外两个文件下执行)。

1
2
3
4
5
6
import pytest
import os

if __name__ == '__main__':
pytest.main()
# os.system('allure generate ./temp -o ./report --clean')