一、安装
pip install pytest
验证安装是否成功
pytest --version
二、使用
1、基本要求:
1、所有的单测文件名都需要满足test_*.py格式或*_test.py格式。
2、在单测文件中,测试类以Test开头,并且不能带有 init 方法(注意:定义class时,需要以T开头,不然pytest是不会去运行该class的)
3、在单测类中,可以包含一个或多个test_开头的函数。
在执行pytest命令时,会自动从当前目录及子目录中寻找符合上述约束的测试函数来执行。
2、使用方法:
1、文件结构框架
- Test_App
- - test_abc.py
- - conftest.py # 配置文件,可不写
- - pytest.ini # 配置文件,可不写
2、主运行函数
import pytest
def test_01():
print("this is {} testcase".format(__name__))
if __name__=='__main__':
pytest.main()
3、类命名规则:
class Test_ABC:
3、断言的五种方式:
1、assert xx:判断 xx 为真
2、assert not xx:判断 xx 不为真
3、assert a in b:判断 b 包含 a
4、assert a == b:判断 a 等于 b
5、assert a !=b:判断 a 不等于 b
4、测试用例的执行参数:
1、参数以列表的形式给出
例如:
参数 | 描述 | 案例 |
-v | 输出调试信息。如:打印信息 | pytest.main ([‘-v’,‘testcase/test_one.py’,‘testcase/test_two.py’]) |
-s | 输出更详细的信息,如:文件名、用例名 | pytest.main ([‘-vs’,‘testcase/test_one.py’,‘testcase/test_two.py’]) |
-n | 多线程或分布式运行测试用例 | |
-x | 只要有一个用例执行失败,就停止执行测试 | pytest.main([‘-vsx’,‘testcase/test_one.py’]) |
– maxfail | 出现N个测试用例失败,就停止测试 | pytest.main([‘-vs’,‘-x=2’,‘testcase/test_one.py’] |
–html=report.html | 生成测试报告 | pytest.main ([‘-vs’,‘–html=./report.html’,‘testcase/test_one.py’]) |
-m | 通过标记表达式执行 | |
-k | 根据测试用例的部分字符串指定测试用例,可以使用and,or | |
--count=n | 执行用例n次 | pytest.main ([‘-vs’,‘--count = 2’,‘testcase/test_two.py’]) |
5、Pytest Exit Code 含义清单
Exit code 0 所有用例执行完毕,全部通过
Exit code 1 所有用例执行完毕,存在Failed的测试用例
Exit code 2 用户中断了测试的执行
Exit code 3 测试执行过程发生了内部错误
Exit code 4 pytest 命令行使用错误
Exit code 5 未采集到可用测试用例文件
6、控制条件
1、在第n个用例失败后,结束测试执行
pytest -x # 第01次失败,就停止测试
pytest --maxfail=2 # 出现2个失败就终止测试
2、执行指定文件路径/文件
# 执行指定文件路径
pytest ./testcase/
# 或者
r'Testcases'
# 执行指定文件
pytest ./testcase/test_one.py
# 或者
r'Testcases/test_one.py'
3、通过关键字过滤执行
运行包含特定关键字的用例
pytest -k "关键字"
排除包含特定关键字的测试
pytest -k "not 关键字"
组合执行多个关键字的用例(or / and)
pytest -k "关键字1 or 关键字2"
pytest -k "关键字1 and 关键字2"
利用表达式
# 运行包含关键字1并且不包含关键字2的
pytest -k "关键字1 and not 关键字2"
匹配测试类和方法
# 运行类名
pytest -k "类名"
测试失败重试
用例执行失败后,会立即开始重试此用例NUM次,再执行下一条用例
# 需要先安装pytest-rerunfailures
# pip install -U pytest-rerunfailures
pytest test_se.py --reruns NUM
# NUM 表示失败后的重试次数
增加失败之后重试延时(reruns_delay=2 延时2秒)
pytest.main( [‘-vs’,‘–reruns=5’,‘–reruns_delay=10’,‘./testcase/test_debug.py’,‘–report=_report.html’])
4、通过修饰跳过执行
@pytest.mark.skip可以加在函数上,类上,类方法上
@pytest.mark.skip(reason="不执行该用例!!因为没写好!!")
5、pytest.skip()函数
在测试用例执行期间强制跳过不再执行剩余内容
def test_function():
n = 1
while True:
print(f"这是我第{n}条用例")
n += 1
if n == 5:
pytest.skip("我跑五次了不跑了")
7、生成测试报告:
安装pytest-html插件(可生成xml和html的报告)
pip install pytest-html
两种应用方式:
(pytest --html=用户路径/report.html)
1、在pytest.ini中使用
addopts = -s --html=./report.html
# 或者
addopts = -s --html=./report.xml
2、在main.py主函数
pytest.main(["-v", "-s",r'Testcases/test_log.py','--log-cli-level=INFO','--html=report.html','--self-contained-html', f'--alluredir=./{allure_result}'])
node id 指定测试用例:
nodeid由模块文件名、分隔符、类名、方法名、参数构成
pytest test_mod.py::TestClass::test_method
三、高阶用法:
Fixture:定义共享的fixture,供测试用例使用。
钩子函数:实现pytest的钩子函数,以便在测试运行的不同阶段执行特定代码。
插件:可以在 conftest.py 中定义插件,使其自动应用于所有测试。
1、详解conftest.py文件
存放fixture的配置文件(类似于初始化)
不需要执行import 就能自动找到fixture
在多个测试文件中,使用同一个fixture
conftest.py文件名是固定的,不可以修改(放在测试目录下)
conftest.py与运行用例在同一个包下,并且该包中有__init__.py文件
@pytest.fixture()装饰器来装饰一个函数,那么被装饰的这个函数就是一个fixture
Fixture的参数如下:
@pytest.fixture(scope = "function",params=None,autouse=False,ids=None,name=None)
fixture的作用范围(scope)共有4种:function,class,module,session(默认function)
范围依次递增的:即function < class < module < session
1、当scope=“function”时,只要有测试函数显示调用fixture时,都会执行一次fixture。但是如果测试函数没有调用fixture,那么就不会执行fixture
import pytest
@pytest.fixture()
def login():
print("Login")
return "hello world"
class TestDemo:
#调用login,则会调用一次fixture
def test_case1(self,login):
print("case1")
#不调用login,则不会调用fixture
def test_case2(self):
print("case2")
#调用login,则第二次调用fixture,加上case1,一共调用了两次
def test_case3(self,login):
print("case3")
assert login == "hello world"
2、当scope=“class”时,有两种场景:
1.测试类下所有的测试方法都调用了fixture,那么fixture只执行一次,时机为测试类中所有测试方执行前。
2.测试类下只有部分测试方法调用了fixture,那么fixture只执行一次,时机为在测试类中第一个调用fixture的测试方法前执行
例1:
import pytest
@pytest.fixture(scope="class")
def login():
print("Scope='class'")
return "hello world"
class TestDemo:
def test_case1(self,login):
print("case1")
assert login == "hello world"
def test_case2(self,login):
print("case2")
def test_case3(self,login):
print("case3")
assert login != "hello world"
例2:
import pytest
@pytest.fixture(scope="class")
def login():
print("Scope='class'")
return "hello world"
class TestDemo:
def test_case1(self):
print("case1")
def test_case2(self,login):
print("case2")
def test_case3(self,login):
print("case3")
assert login == "hello world"
3、当scope=“module”时,则范围会对当前整个.py文件生效
会在第一个调用fixture的测试方法前调用一次然后生效,后面即使再有 其他测试方法调用fixture,也不会再调用
import pytest
@pytest.fixture(scope="module")
def login():
print("Scope='module'")
return "hello world"
def test_case(login):
print("test_case")
class TestDemo:
def test_case1(self,login):
print("case1")
def test_case2(self,login):
print("case2")
def test_case3(self,login):
print("case3")
assert login == "hello world"
4、当scope=“session”时,则范围会对所有的.py文件生效
conftest.py文件范围应用在这里
多个测试文件只调用一次
例:
# conftest.py
import pytest
@pytest.fixture(scope="session")
def login():
print("Scope='session'")
return "hello world"
# test_1.py
import pytest
def test_case1(login):
print("case1")
assert login == "hello world"
def test_case2(login):
print("case2")
def test_case3(login):
print("case3")
# test_2.py
import pytest
def test_case(login):
print("test_case")
class TestDemo:
def test_case1(self,login):
print("case1")
def test_case2(self,login):
print("case2")
def test_case3(self,login):
print("case3")
assert login == "hello world"
2、Fixture使用方式
(fixture修饰器来标记固定的函数,在其他函数,模块,类或整个工程调用它时会被激活并优先执行,通常会被用于完成预置处理和重复操作。)
# 方法
fixture(scope="function", params=None, autouse=False, ids=None, name=None)
# 参数说明
scope:被标记方法的作用域
"function"(default):作用于每个测试方法,每个test都运行一次
"class":作用于整个类,每个class的所有test只运行一次
"module":作用于整个模块,每个module的所有test只运行一次
"session":作用于整个session(慎用),每个session只运行一次
params:(list类型)提供参数数据,供调用标记方法的函数使用
autouse:是否自动运行,默认为False不运行,设置为True自动运行
params参数示例:
import pytest
@pytest.fixture(params=[1, 2, 3])
def need_data(request): # 传入参数request 系统封装参数
return request.param # 取列表中单个值,默认的取值方式
class Test_ABC:
def test_a(self,need_data):
print("------->test_a")
assert need_data != 3 # 断言need_data不等于3
if __name__ == '__main__':
pytest.main("-s test_abc.py")
执行结果:
# 可以发现结果运行了三次
test_abc.py
1
------->test_a
.
2
------->test_a
.
3
------->test_a
F
1.作为参数传入
import pytest
@pytest.fixture()
def login():
print("登录")
return "Account"
@pytest.fixture()
def logout():
print("退出")
return "Exit"
class TestDemo:
def test_case1(self,login):
print("我传入了Login Fixture")
assert login == "Account"
def test_case2(self):
print("我没传入任何 Fixture")
def test_case3(self,login,logout):
print("我传入了两个Fixture")
assert login == "Account"
assert logout == "Exit"
if __name__ == '__main__':
pytest.main(['-s','test_2.py'])
2.fixture相互调用(也相当于作为参数传入)
多层fixture调用时,先执行最后一层fixture,然后再依次向前执行。不会自动return 上一层函数返回的值。
import pytest
@pytest.fixture()
def account():
print("Account")
return "hello world"
#调用上面的account fixture
@pytest.fixture()
def login(account):
print("Login")
class TestDemo:
def test_case1(self,login):
print("调用了login,返回值为{}".format(login))
def test_case2(self,account):
print("调用了account,返回值为{}".format(account))
3.作为conftest.py文件传入 -- (在多个测试文件中,使用同一个fixture)
例1:
#conftest.py
import pytest
@pytest.fixture()
def paas_token():
print("开始加载")
print("加载完成")
# test_db.py
def test_query(paas_token):
print("--> finish <--")
例2:
@pytest.fixture()
def fixtureFunc():
return "Hello world"
class TestDemo:
def test_case1(self,fixtureFunc):
print(fixtureFunc)
assert fixtureFunc == "Hello world"
3、pytest.ini文件
pytest的配置文件通常放在测试目录下,名称为pytest.ini,命令行运行时会使用该配置文件中的配置
#配置pytest命令行运行参数
[pytest]
addopts = -s ... # 空格分隔,可添加多个命令行参数 -所有参数均为插件包的参数配置测试搜索的路径
testpaths = ./scripts # 当前目录下的scripts文件夹 -可自定义
#配置测试搜索的文件名称
python_files = test*.py
#当前目录下的scripts文件夹下,以test开头,以.py结尾的所有文件 -可自定义
配置测试搜索的测试类名
python_classes = Test_*
#当前目录下的scripts文件夹下,以test开头,以.py结尾的所有文件中,以Test开头的类 -可自定义
配置测试搜索的测试函数名
python_functions = test_*
#当前目录下的scripts文件夹下,以test开头,以.py结尾的所有文件中,以Test开头的类内,以test_开头的方法 -可自定义
例:
[pytest]
# 命令行参数
addopts = -s
# 搜索文件名
python_files = test_*.py
# 搜索的类名
python_classes = Test_*
# 搜索的函数名
python_functions = test_*
4、内置函数(setup和teardown函数)
1.setup和teardown主要分为:模块级,类级,功能级,函数级。
2.存在于测试类内部
例1:
运行于测试方法的始末,即:运行一次测试函数会运行一次setup和teardown
# test_abc.py
import pytest
class Test_ABC:
# 函数级开始
def setup(self):
print("------->setup_method")
# 函数级结束
def teardown(self):
print("------->teardown_method")
def test_a(self):
print("------->test_a")
assert 1
def test_b(self):
print("------->test_b")
if __name__ == '__main__':
pytest.main("-s test_abc.py")
例2:
运行于测试类的始末,即:在一个测试内只运行一次setup_class和teardown_class,不关心测试类内有多少个测试函数。
import pytest
class Test_ABC:
# 测试类级开始
def setup_class(self):
print("------->setup_class")
# 测试类级结束
def teardown_class(self):
print("------->teardown_class")
def test_a(self):
print("------->test_a")
assert 1
def test_b(self):
print("------->test_b")
if __name__ == '__main__':
pytest.main("-s test_abc.py")
5、调整用例执行顺序
安装
pip install pytest-ordering
@pytest.mark.run(order=n) -- 第n个执行
@pytest.mark.last -- 最后一个执行
import pytest
@pytest.fixture(scope="class")
def login():
print("Scope='class'")
return "hello world"
class TestDemo:
# 修饰最后一个执行
@pytest.mark.last
def test_case1(self):
print("case1")
# 修饰第n个执行
@pytest.mark.run(order=n)
def test_case2(self,login):
print("case2")
def test_case3(self,login):
print("case3")
assert login == "hello world"