下方是个毛胚,没有时间细化整理,根据目录找相关知识点
记录pytest高级使用方式,从单例到组合的过程,有时间会继续整理
高效的利用fixture实例
在测试期间,pytest只激活最少个数的fixture实例;如果你拥有一个参数化的fixture,所有使用它的用例会在创建的第一个fixture实例并销毁后,才会去使用第二个实例;
下面这个例子,使用了两个参数化的fixture,其中一个是模块级别的作用域,另一个是用例级别的作用域,并且使用print方法打印出它们的setup/teardown流程:
参考:fixture
# src/chapter-4/test_minfixture.py
import pytest
@pytest.fixture(scope="module", params=["mod1", "mod2"])
def modarg(request):
param = request.param
print(" SETUP modarg", param)
yield param
print(" TEARDOWN modarg", param)
@pytest.fixture(scope="function", params=[1, 2])
def otherarg(request):
param = request.param
print(" SETUP otherarg", param)
yield param
print(" TEARDOWN otherarg", param)
def test_0(otherarg):
print(" RUN test0 with otherarg", otherarg)
def test_1(modarg):
print(" RUN test1 with modarg", modarg)
def test_2(otherarg, modarg):
print(" RUN test2 with otherarg {} and modarg {}".format(otherarg, modarg))
mod1的TEARDOWN操作完成后,才开始mod2的SETUP操作;
用例test_0独立完成测试;
用例test_1和test_2都使用到了模块级别的modarg,同时test_2也使用到了用例级别的otherarg。它们执行的顺序是,test_1先使用mod1,接着test_2使用mod1和otherarg 1/otherarg 2,然后test_1使用mod2,最后test_2使用mod2和otherarg 1/otherarg 2;也就是说test_1和test_2共用相同的modarg实例,最少化的保留fixture的实例个数;
autouse=True的fixture在其它级别作用域中的工作流程:
- autouse fixture遵循scope关键字的定义:如果其含有scope=‘session’,则不管它在哪里定义的,都将只执行一次;scope='class’表示每个测试类执行一次;
- 如果在测试模块中定义autouse fixture,那么这个测试模块所有的用例自动使用它;
- 如果在conftest.py中定义autouse fixture,那么它的相同文件夹和子文件夹中的所有测试模块中的用例都将自动使用它;
- 如果在插件中定义autouse fixture,那么所有安装这个插件的项目中的所有用例都将自动使用它;
@pytest. fixture(scope=“”, params=“” ,autouse=" “,ids=” “,name=”")
(1)scope表示的是被@pytest. fixture标记的方法的作用域。function(默认) , class , module,package/session.
(2)params :参数化(支持,列表[,元祖(),字典列表[{.0.0],字典元祖({,{.{})
(3)autouse=True :自动执使用,默认False,函数中声明了autouse=True,所以TestClass中的所有用例,可以自动调用transact而不用显式的声明或标记;更标准的做法是,将模块声明在conftest.py中,而不是使用autouse=True
(4)ids :当使用params参数化时,给每一个值设置一 个变量名。 意义不大。
(5)name :给表示的是被@pytest.fixture标记的方法取一个别名。
当取了别名之后,那么原来的名称就用不了了。
- 在模块层级覆写fixture
tests/
__init__.py
conftest.py
# content of tests/conftest.py
import pytest
@pytest.fixture
def username():
return 'username'
test_something.py
# content of tests/test_something.py
import pytest
@pytest.fixture
def username(username):
return 'overridden-' + username
def test_username(username):
assert username == 'overridden-username'
test_something_else.py
# content of tests/test_something_else.py
import pytest
@pytest.fixture
def username(username):
return 'overridden-else-' + username
def test_username(username):
assert username == 'overridden-else-username'
模块中的fixture覆盖了conftest.py中同名的fixture;
模块中的fixture可以轻松的访问conftest.py中同名的fixture
conftest.py与fixtrue
1、conftest.py文件是什么
单独存放fixtrue配置的一个文件
用处是可以多个不同的pytest测试脚本共用一套fixture配置,代码维护方便
2、conftest.py使用注意事项
conftest.py文件不能修改名称,否则pytest检测不到
conftest.py与运行的用例要在python同一个包下(ps:必须有 init.py)
conftest.py文件配置好后,pytest自动检测并生效,不需要进行import导入
3、conftest.py文件使用实例
3.1. 单个包层级
-
代码结构层次
-
代码
-
上述实例要运行,只需要运行 test_demo_02.py 文件即可,会自动检测 conftest.py 文件中的fixtrue
-
通过上述实例,可以发现 test_demo_01.py 和 test_demo_02.py 都能引用到 conftest.py 文件中的fixtrue
3.2. 多个包层级,存在顶级包和测试包
-
代码结构层次
-
顶级目录pytest_demo包下的conftest.py代码
-
conftest_demo_02包下的conftest.py代码
-
conftest_demo_02包下的test_demo_01.py代码
-
conftest_demo_02包下的test_demo_02.py代码
-
运行结果
运行conftest_demo_02包下的test_demo_02.py代码,结果如下
3、conftest.py总结
上述包下实例要运行,只需要运行 test_demo_02.py 文件即可,会自动检测到所有 conftest.py 文件中的fixtrue
上例中包含了3个 conftest.py 文件,有顶层的,有测试包下的
上例执行后查看结果会发现本层的 conftest.py 文件先生效,然后再生效上一级包下的
在testadd方法中引用了本层和顶层的的fixtrue,可以看出顶层的fixtrue对本层级包以及子包下的测试方法都能生效
注意:conftest_demo_02包下的测试模块不能引用conftest_demo_01包下 conftest.py 的fixture,因为它们没有上下的层级关系
在实际使用 conftest.py 文件中,一般会把顶层的fixtrue的scope设置为session,存放整个项目需要使用的初始化操作或数据
以上引用:http://t.zoukankan.com/ritaliu-p-13523842.html
pytest主函数
-
获取项目引用第三方库
pip freeze > reqirements. txt -
安装插件
pytest
pytest-html ( 生成html格式的自动化测试报告)
pytest-xdist 测试用例分布式执行。 多CPU分发。
pytest-ordering 用于改变测试用例的执行顺序
pytest-rerunfailures 用例失败后重跑
allure-pytest 用于生成美观的测试报告。
放到requirements. txt中,通过pip install -r requirements.txt -
使用pytest ,默认的测试用例的规则以及基础应用
1 模块名必须以test开头或者test结尾
2 测试类必须以Test开头,并且不能有init方法。
3 测试方法必须以test开头 -
pytest测试用例的运行方式
1.主函数模式1)运行所有: pytest main() 2)指定模块: pytest.main([-vs'test _login.py']) 3)指定目录: pytest main(['-vs',' ./interface_ _testcase']) 4)通过nodeid指定用例运行: nodeid由模块名,分隔符,类名,方法名,函数名组成。 执行这个包下,某一个函数 pytest.main(["-vs",'/interface_ testcase/test jinterface. py::test_ 04 _func]) 执行类下某一个方法 pytest. main(["-vs'./interface_ testcase/test _interface py::TestInterface::test_ 03 _ziliao']) 5)多线程 pytest .main ( ['-vs' ,'./t estcase','-n=2']) 6)测试用例执行失败后重跑,算本次用例有三次执行 pytest .main(['-vs', './ testcase', '- - reruns=2 ' ] )
2命令行模式
(1)运行所有: pytest
(2)指定模块: pytest -VS test login. py
(3)指定目录: pytest -VS ./interface_ testcase
(4)指定目录: pytest -VS /interface testcase/test interface. py:.test 04 func参数详解:
-s :表示输出调试信息,包括print打印的信息. -v:显示更详细的信息 -vs :这两个参数一起用 -n :支持多线程或者分布式运行测试用例。-n跟的是线程数 如: pytest -VS ./testcase/test_ login.py -n 2 --reruns NUM:失败用例重跑 pytest -vs ./testcase --reruns 2 -x :表示只要有一个用例报错,那么测试停止 pytest -vs ./testcase -x --maxfail=2 出现两个用例失败就会停止 pytest -vs ./testcase --maxfail 2 -k:根据测试用例的部分字符串指定测试用例, 如字符串包含"ao"的用例 pytest -vs ./testcas -k "ao" --html ./reportreport. html :生成htm的测试报告。
通过读取pytest. ini配置文件运行。配置文件和主函数分开使用,有一个就可以,执行时优先读取ini文件
pytest.in这个文件它是pytest单元测试框架的核心配置文件。
1.位置: -般放在项目的根目录
2编码:必须是ANSI ,可以使用notpad++修改编码格式。
3.作用:改变pytest默认的行为。
4.运行的规则;不管是主函数的模式运行,命令行模式运行,都会去读取这个配置文件。
[pytest]
addopts = -vs ##命令行的参数,用空格分隔
testpaths = …/pytestproiect ##测试用例的路径
python files = test*.py ##模块名的规则
python classes = Test* ##类名的规则
python functions = test ##方法名的规则 -
执行顺序
unittest: asclI的大小来绝对的执行的顺序 pytest:默认从上到下 改变默认的执行顺序:使用mark标记, @pytest.mark.run(order=1) @pytest.mark.run(order=2)
Hook 方法之 pytest_addoption :
引自:waitan
这个方法是我再做driver并发时所用到传参,通过运行函数获取参数,传参返回值,带入运行函数中
pytest_addoption 可以让用户注册一个自定义的命令行参数,方便用户将数据传递给 pytest;
这个 Hook 方法一般和 内置 fixture pytestconfig 配合使用,pytest_addoption 注册命令行参数,pytestconfig 通过配置对象读取参数的值;
- pytest_addoption 注册、pytestconfig 获取命令行参数 :
# conftest.py
import pytest
# 注册自定义参数 cmdopt 到配置对象
def pytest_addoption(parser):
parser.addoption("--cmdopt", action="store",
default="None",
help="将自定义命令行参数 ’--cmdopt' 添加到 pytest 配置中")
# 从配置对象获取 cmdopt 的值
@pytest.fixture(scope='session')
def cmdopt(pytestconfig):
return pytestconfig.getoption('--cmdopt')
# 然后任何 fixture 或测试用例都可以调用 cmdopt 来获得设备信息
parser.addoption() 参数说明:
- name:自定义命令行参数的名字,可以是:“foo”, “-foo” 或 “–foo”;
- action:在命令行中遇到此参数时要采取的基本操作类型;
- nargs:应该使用的命令行参数的数量;
- const:某些操作和nargs选择所需的常量值;
- default:如果参数不在命令行中,则生成的默认值。
- type:命令行参数应该转换为的类型;
- choices:参数允许值的容器;
- required:命令行选项是否可以省略(仅可选);
- help:对参数作用的简要说明;
- metavar:用法消息中参数的名称;
- dest:要添加到 parse_args() 返回的对象中的属性的名称;
示例:
- action=“” 介绍
action="store":默认,只存储参数的值,可以存储任何类型的值,此时 default 也可以是任何类型的值,而且命令行参数多次使用也只能生效一个,最后一个值覆盖之前的值;
# 注册自定义参数 cmdopt 到配置对象
def pytest_addoption(parser):
parser.addoption("--cmdopt", action="store",
default="这个是默认值...",
help="将命令行参数 ’--cmdopt' 添加到 pytest 配置中")
# 从配置对象中读取自定义参数的值
@pytest.fixture(scope="session")
def cmdopt(request):
return request.config.getoption("--cmdopt")
action="append":存储一个列表,用 append 模式 将可以同时多次使用自定义参数,并且 default 默认值必须是一个列表,pytest 会把 default 默认参数的值和多个自定义参数的值放在一个列表中:
# 将自定义参数的值打印出来
@pytest.fixture(autouse=True)
def fix_1(cmdopt):
print('\n --cmdopt的值:',cmdopt)
if __name__ == '__main__':
# 使用参数
pytest.main(['-s', '--cmdopt=98k'])
# 控制台打印参数值:
============================= test session starts =============================
test_Z.py::TestDemoA::test_A_001
--cmdopt的值: 98k
PASS
============================== 1 passed in 0.02s ==============================
action="store_const":使用 const 为命令行参数指定一个常量值,必须和 const 参数同时使用,使用这个模式后命令行参数不能赋值:
# 注册自定义参数 cmdopt 到配置对象
def pytest_addoption(parser):
parser.addoption("--cmdopt", action="append",
default=['这是默认参数'],
help="将命令行参数 ’--cmdopt' 添加到 pytest 配置中")
if __name__ == '__main__':
# 使用参数
pytest.main(['-s', '--cmdopt=98k', '--cmdopt=毛瑟小手枪'])
# 控制台打印参数值:
============================= test session starts =============================
test_Z.py::TestDemoA::test_A_001
--cmdopt的值: ['这是默认参数', '98k', '毛瑟小手枪']
PASS
============================== 1 passed in 0.02s ==============================
action="append_const":存储一个列表,使用 const 为命令行参数指定一个常量值,并将 default 默认值和 const 常量值添加到列表中,这个模式可以同时多次使用自定义参数,但是还是不能赋值,只能使用常量;
def pytest_addoption(parser):
parser.addoption("--cmdopt", action="store_const",
default='这是默认参数',
const='这个是为命令行参数指定的常量值...',
help="将命令行参数 ’--cmdopt' 添加到 pytest 配置中")
if __name__ == '__main__':
pytest.main(['-s','--cmdopt'])
# 控制台打印参数值:
============================= test session starts =============================
test_Z.py::TestDemoA::test_A_001
--cmdopt的值: 这个是为命令行参数指定的常量值...
PASS
============================== 1 passed in 0.02s ==============================
- type:type 的类型可以是 python 的基础类型,比如:int,str,float,list 等类型,如果不指定类型的话,pytest会把接受到的参数值都默认为 str 类型,所以我们有时需要指定参数的类型:
注意:在使用 type 指定类型时,也需要把 default 的类型修改为同样的类型!
def pytest_addoption(parser):
parser.addoption("--cmdopt", action="store",
default=100,
type=int,
help="将命令行参数 ’--cmdopt' 添加到 pytest 配置中")
if __name__ == '__main__':
pytest.main(['-s', f'--cmdopt=888'])
# 控制台打印参数值:
============================= test session starts =============================
--cmdopt的值: 888
--cmdopt的类型: <class 'int'>
PASS
============================== 1 passed in 0.02s ==============================
- choices:choices 可以指定几个值,自定义参数必须在这几个值中选择一个,否则会报错:
def pytest_addoption(parser):
parser.addoption("--cmdopt", action="store",
default='100',
choices= ['python', 'java', 'c++'],
help="将命令行参数 ’--cmdopt' 添加到 pytest 配置中")
if __name__ == '__main__':
pytest.main(['-s', f'--cmdopt=888'])
# 控制台打印结果:
ERROR: usage: conftest.py [options] [file_or_dir] [file_or_dir] [...]
conftest.py: error: argument --cmdopt: invalid choice: '888' (choose from 'python', 'java', 'c++')
Allure测试报告
allure-pytest
-
下载,解压,配置path路径。
https://github.com/allure-framework/allure2/releases
配置路径D:\Tools\allure-2.17.3\bin
验证: allure --version
问题: dos可以验证但是pycharm验证失败,怎么办,重启pycharm.| -
加入命令性成json格式的临时报告 --alluredir ./temp
-
生成allure报告
os.system(‘allure generate ./temp -o ./repost --clean’) 基础语法 json临时报告 输出到 指定文件 清空原有报告
os. system(allure generate ./temp -o ./report --clean’)
allure generate—命令.固定的
./temp -----临时的json格式报告的路径
-o ---- 输出output
./report ----- 生成的allure报告的路径
–clean ---- 清空/report路径原来的报告
理解上述测试报告 ,下面是我常用语法报告
import pytest
import sys, os
import shutil
dir_path = os.path.dirname(os.path.dirname(__file__))
sys.path.append(dir_path)
Basepath = os.path.split(os.path.dirname(os.path.abspath(__file__)))[0]
file_name = Basepath + "\\case"
if __name__ == "__main__":
filename = os.path.dirname(os.path.dirname(__file__))
copy_file = os.path.join(filename, 'config', 'environment.properties')
paste_file = os.path.join(filename, 'result', 'environment.properties')
test_dir = os.path.dirname(__file__)
shutil.copy(copy_file, paste_file) # 复制生成环境变量
pytest.main(['-vs', '--disable-warnings', '-q', file_name, '--alluredir', '../result/', "--clean-alluredir"])
os.system('allure generate ../result/ -o ../report/ --clean')
os.system('allure open -h 127.0.0.1 -p 8881 ../report/')