pytest
一、简介
1.1、 认识pytest
pytest是一个非常成熟的全功能的python测试框架,主要有以下几个特点:
- 简单灵活,容易上手
- 支持参数化
- 能够支持简单的单元测试和复杂的功能测试,还可以用来做
- pytest+selenium (webUI自动化测试)
- pytest+appium(移动端自动化测试)
- pytest+request(接口自动化测试)
作用:管理测试用例、执行测试用例、判断测试结果、生成测试报告
1.2、插件介绍
- pytest具有很多第三方插件,并且可以自定义扩展,比较好用的如
- pytest-selenium(集成selenium)、
- pytest-html(完美html测试报告生成)、
- pytest-rerunfailures(失败case重复执行)、
- pytest-xdist(多CPU分发,多线程运行)
- pytest-ordering(改变用例的执行顺序的插件)
- allure-pytest(生成美观自定义的allure报告)
二、安装
2.1、pip命令安装pytest
- 在命令行中运行以下命令:
pip install -U pytest
- 检查是否安装了正确的版本:
pytest --version
pytest 6.2.1
2.2、pip 命令安装插件
我们在项目下创建一个 requirements.txt 文件,然后把需要安装的插件写入这个文件里面
pytest
pytest-xdist
pytest-ordering
pytest-rerunfailures
allure-pytest
pyyaml
requests
在控制面版 Terminal 输入:pip install -r requirements.txt (只适用虚拟环境)
三、用例规则
官方文档:标准的测试发现规则
级别 | 默认定义 |
包级 | test开头 |
模块 | test_*.py 或 _test 结尾 |
类级 | Test开头 且不含init方法 |
函数或方法级 | 以test_ 开头 |
自定义
这种规则不是一成不变的,如果因为某种需要,可以通过pytest.ini配置文件来修改命名规则, 在测试系统的顶层目录创建pytest.ini文件,在pytest.ini文件中写入如下配置:
[pytest]
# 改变规则 用例路径,.代表根目录
testpaths = ./stu
# 更改测试 文件命名规则
python_files = a*
# 更改测试类命名规则
python_classes = b*
# 更改测试函数命名规则
python_functions = c*
四、执行
4.1、命令行方式
在pycharm Teminnal 面板 直接输入pytest,就会执行 所有符合规则的用例
4.2、通过主函数main()
我们可以在项目的根目录下创建一个run文件,然后导入pytest包,写入main() 函数
import pytest
if __name__ == "__main__":
pytest.main()
# main里也可以加参数
# pytest.main(["-vs"])
# 指定文件执行
pytest.main(["-vs",'setup_teatdown/test_set_teat.py::Test000::test_001'])
里面也可以加参数
4.3、通过全局配置文件pytest.ini文件执行
注:文件一定在项目根目录下,名称不可变,编码格式为ANSI ,无论命令行还是main函数运行,都会加载这个配置文件
[pytest]
# 设置一些默认参数
addopts = -vs -m 'smoke'
# 指定执行用例路径,.代表根目录
testpaths = ./stu
;# 更改测试 文件命名规则
;python_files = a*
;
;# 更改测试类命名规则
;python_classes = b*
;# 更改测试函数命名规则
;python_functions = c*
# 分组 标记
markers =
smoke:冒烟测试用例
sit:系统测试
注:Pycharm 里面默认运行框架 为Unittests,我们把它改为pytest
步骤:File -->Tools -->Python Integrated Tools -->Default test runner: pytest
有可能需要重启一下才生效,pytest 兼容 unittest
参数说明
参数 | 说明 | 实例 |
-s | 输出调试信息 | |
-v | 输出详细信息 | pytest -vs |
-n | 多线程运行(依赖pytest.xdis插件) | pytest -vs -n=5 |
-x | 遇见用例失败 则停止测试 | pytest -vs -x |
-k | 运行测试用例名称中包含某个字符串的测试用例 | pytest -vs -k "ttt" (运行包含ttt的测试用例,里面可以加逻辑运算) |
-m | 执行有标记的用例 | -m "website":执行有website标记的test方法 |
--html | 生成html的测试报告(依赖pytest-html插件) | python -vs --html 报告路径(如:./reports/result.html) |
--reruns | 重新运行失败的用例,并指定运行最大次数 | pytest --reruns 3 test00.py |
--reruns-delay | 用例失败重新运行时间间隔 | pytest --reruns 3 --reruns-delay 1 test00.py |
--maxfail | 最多出现几个失败终止 | pytest -vs --maxfail=2 |
五、前后执行组件(setup/teardown)
为了测试准备,和测试结束要做的一些事情,
测试准备:初始化代码,创建浏览器对象,创建日志对象,创建数据库连接 ,接口请求对象创建准备
测试结束:关闭所有对象
模块级(setup_module / teardown_module)模块始末,全局的(优先最高)
函数级(setup_function / teardown_function)只对函数用例生效(不在类中)
类级(setup_class / teardown_calss)只在类中前后运行一次 (在类中)
方法级(setup_method / teardown_methond)开始于方法始末(在类中)
类里面的(setup /teardown)运行在调用方法的前后
5.1、模块级:
setup_module / teardown_module
模块始末(当前文件),全局的(优先最高)
如创建一个 test_setup_teardown.py文件
#-----------模块文件----------
def setup_module():
print("模块前运前")
def teardown_module():
print("模块后运行")
def test_000():
print("第000条用例")
5.2、函数级:
setup_function / teardown_function
只对函数用例生效(不在类中)
def setup_function():
print("函数之前运行的代码")
def teardown_function():
print("函数之后运行的代码")
def test_000():
print("第000条用例")
def test_001():
print("第001条用例")
5.3、类级:
setup_class / teardown_class
只在类中前后运行一次 (定义在类中)
class Test000():
def setup_class(self):
print("在使用类前面执行")
def teardown_class(self):
print("在使用类后面执行")
def test_000(self):
print("第000条用例")
def test_001(self):
print("第001条用例")
5.4、方法级:
setup_method / teardown_method
开始于方法始末(在类中,和setup、teardown 功能一样,优先级稍微高一点)
class Test000():
def setup_method(self):
print("类中的每一个方法前")
def teardown_method(self):
print("类中的每一个方法后")
def test_000(self):
print("第000条用例")
def test_001(self):
print("第001条用例")
5.5、类里面的:
setup /teardown
运行在调用方法的前后
class Test000():
def setup(self):
print("方法前------")
def teardown(self):
print("方法后---------")
def test_000(self):
print("第000条用例")
def test_001(self):
print("第001条用例")
def ttt(self):
print("一个方法")
六、fixture (固定组件)
应用场景:可以部分用例实现,非常灵活,自定义
fixture的作用范围
fixture 里面有个scope参数可以控制fixture的作用范围:session>module>class>function
-function:每一个函数或方法都会调用
-class:每一个类调用一次,一个类中可以有多个方法
-module:每一个.py文件调用一次,该文件内又有多个function和class
-session:是多个文件调用一次,可以跨.py文件调用,每个.py文件就是module
fixture源码详解
fixture(scope='function',params=None,autouse=False,ids=None,name=None)
scope:有四个级别参数"function"(默认),"class","module","session"
params:一个可选的参数列表,它将导致多个参数调用fixture功能和所有测试使用它。
autouse:如果True,则为所有测试激活fixture func可以看到它(自动为用例使用)。如果为False则显示需要参考来激活fixture
ids:每个字符串id的列表,每个字符串对应于params这样他们就是测试ID的一部分。如果没有提供ID它们将从params自动生成
name:fixture的名称。这默认为装饰函数的名称。如果fixture在定义它的统一模块中使用,夹具的功能名称将被请求夹具的功能arg遮蔽,解决这个问题的一种方法时将装饰函数命令"fixture_<fixturename>"然后使用"@pytest.fixture(name='<fixturename>')"。
import pytest
class Fruit:
def __init__(self, name):
self.name = name
def __eq__(self, other):
return self.name == other.name
@pytest.fixture
def my_fruit():
return Fruit("apple")
@pytest.fixture
def fruit_basket(my_fruit):
return [Fruit("banana"), my_fruit]
def test_my_fruit_in_basket(my_fruit, fruit_basket):
assert my_fruit in fruit_basket
七、用例的跳过
无条件跳过
@pytest.mark.skip(reason="无理由跳过")
有条件跳过
@pytest.mark.skipif(count<5,reason="有理由跳过")
断言
unittest提供了assertEqual、assertIn、assertTrue、assertFalse。
pytest直接使用python 自带的 assert 表达式。
实 例 | 描 述 |
assert a == b assert a != b |
断言是否相等 |
assert a assert False |
断言真假 |
assert "a" in "abc" assert "a" not in "abc" |
断言是否包含 |
assert a is None assert a in not None |
断言是否包含 |
assert a is None assert a in not None |
断言是否为空 |
assert isinstance(a,int) | 断言数据类型 |
def test_zero_division(): with pytest.raises(ZeroDivisionError): 1/0 |
断言异常 |
参数化
软件测试中,输入相应值,检查期望值,是常见测试方法。
在自动化测试中,一个测试用例对应一个测试点,通常一组测试数据无法完全覆盖测试范围,所以,需要参数化来传递多组数据。
pytest的测试用例参数化使用如下装饰器即可完成。
方法:
parametrize(argnames, argvalues, indirect=False, ids=None, scope=None)
常用参数:
argnames:参数名,以逗号分隔的字符串
argvalues:参数值列表,若有多个参数,一组参数以元组形式存在,包含多组参数的所有参数
当参数为一个时格式:[value]
当参数个数大于一个时,格式为:[(param_value1,param_value2.....),(param_value1,param_value2.....)]
使用方法:
@pytest.mark.parametrize(argnames,argvalues)
️ 参数值为N个,测试方法就会运行N次
import pytest
from selenium import webdriver
from time import sleep
from selenium.webdriver.common.action_chains import ActionChains
# 一个登录案例
@pytest.mark.parametrize(
"user,password",
[('huang123456','****'),
("hduser8000","****")
]
)
def test_login(user,password):
driver = webdriver.Chrome("D:\\selenium_driver\\chromedriver.exe")
driver.implicitly_wait(10)
driver.get("http://106.14.225.213/")
driver.maximize_window()
driver.find_element_by_xpath('//*[@id="main-navbar"]/div[3]/div[2]/a').click()
driver.find_element_by_name("username").send_keys(user)
driver.find_element_by_name("password").send_keys(password)
driver.find_element_by_xpath('//*[@id="app"]/div/div[3]/div/form/button').click()
sleep(3)
tt=driver.find_element_by_xpath('//*[@id="app"]/div/div[3]/div/div/div[2]/div[1]/div[2]/div/div[2]/button/span').text
print(tt)
assert "发布文章" == tt
above = driver.find_elements_by_xpath('//*[@class="vnb__menu-options__option__link"]')[1]
ActionChains(driver).move_to_element(above).perform()
driver.find_element_by_xpath('//*[@aria-label="退出登录"]').click()
sleep(5)
driver.quit()
if __name__ == '__main__':
pytest.main(['-sv','test_par01.py'])
测试报告
生成junitXML 测试报告 : 这种格式的结果文件可以被Jenkins或其他CI工具解析
pytest 测试文件 --junitxml=path
生成在线测试报告
pytest 测试文件 --pastebin=all
参考: