pytest
用例设计原则
文件名以test_.py文件和test.py
以test_开头的函数
以Test开头的类
以test_开头的方法
所有的包pakege必须要有__init_.py文件
用例按照你写的顺序去执行,不像unittest会按照数字和字母的顺序去执行,而pytest只是按照写的先后去执行
# test_ceshi.py
class Testclass:
def test_b(self):
a = 'hello'
assert 'e' in a
def test_a(self):
a = 'word'
assert 'a' in a
运行方法:
1.直接cmd窗口,pytest 文件名
2.打开test_ceshi.py所在的文件夹,cmd窗口输入:pytest(或者输入py.test也可以)
正常pytest 或 py.test 运行时,会运行文件夹下所有符合标准的文件和文件当中的case,如果只想运行文件夹下某一文件中的case,可以在后边加上 文件名
pytest test_ceshi.py
如果只想运行某一个case,则可以
pytest test_ceshi.py::Testclass::test_a
-x 遇到错误时停止测试,一旦遇到错误时,那么就会停止运行,下边的所有case都将不在运行
pytest -x test_class.py
–maxfail=num 当用例错误个数达到指定数量时,停止测试, num为设置的数量,当错误数达到设置的数量时停止测试
pytest --maxfail=1
Pycharm以pytest方式运行,需要改该工程设置默认的运行器:file->Setting->Tools->Python Integrated Tools->项目名称->Default test runner->选择py.test
setup与teardown
学过unittest的都知道里面用前置和后置setup和teardown非常好用,在每次用例开始前和结束后都去执行一次。当然还有更高级一点的setupClass和teardownClass,需配合@classmethod装饰器一起使用
pytest中也有类似的语法:
模块级(setup_module/teardown_module)开始于模块始末,全局的
函数级(setup_function/teardown_function)只对函数用例生效(不在类中)
类级(setup_class/teardown_class)只在类中前后运行一次(在类中)
方法级(setup_method/teardown_method)开始于方法始末(在类中)
类里面的(setup/teardown)运行在调用方法的前后
例:
import pytest
def setup_module():
print('所有用例执行前运行')
def teardown_module():
print('所有用例执行后运行')
def setup_function():
print('每条用例执行前都会运行')
def teardown_function():
print('每条用例执行后都会运行')
def test_1():
print('正在执行用例>1')
def test_2():
print('正在执行用例>2')
if __name__ == '__main__':
pytest.main('-q test_classs.py')
运行结果
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.7.1, py-1.5.4, pluggy-0.7.1
rootdir: D:\PyCharmcode\untitled\pytestceshi, inifile:
plugins: metadata-1.7.0, html-1.19.0collected 2 items
test_classs.py 所有用例执行前运行
每条用例执行前都会运行
.正在执行用例>1
每条用例执行后都会运行
每条用例执行前都会运行
.正在执行用例>2
每条用例执行后都会运行
所有用例执行后运行
[100%]
========================== 2 passed in 0.05 seconds ===========================
从上述例子中我们可以看出:
setup_module是所有用例开始前只执行一次,teardown_module是所有用例结束后只执行一次
setup_function/teardown_function 每个用例开始和结束调用一次
例:
import pytest
class Testclass:
def setup_class(self):
print('setup_class')
def teardown_class(self):
print('teardown_class')
def setup_method(self):
print('setup_method')
def teardown_method(self):
print('teardown_method')
def setup(self):
print('setup')
def teardown(self):
print('teardown')
def test_1(self):
print('用例 》 1')
def test_2(self):
print('用例 》 2')
if __name__ == '__main__':
pytest.main('-q test_classs.py')
运行结果:
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.7.1, py-1.5.4, pluggy-0.7.1
rootdir: D:\PyCharmcode\untitled\pytestceshi, inifile:
plugins: metadata-1.7.0, html-1.19.0collected 2 items
test_classs.py setup_class
setup_method
setup
.用例 》 1
teardown
teardown_method
setup_method
setup
.用例 》 2
teardown
teardown_method
teardown_class
[100%]
========================== 2 passed in 0.05 seconds ===========================
从结果看出,运行的优先级:setup_class》setup_method》setup 》用例》teardown》teardown_method》teardown_class
例:
import pytest
def setup_module():
print('所有用例执行前运行')
def teardown_module():
print('所有用例执行后运行')
def setup_function():
print('每条用例执行前都会运行')
def teardown_function():
print('每条用例执行后都会运行')
def test_1():
print('正在执行用例>1')
def test_2():
print('正在执行用例>2')
class Testclass:
def setup_class(self):
print('setup_class')
def teardown_class(self):
print('teardown_class')
def setup_method(self):
print('setup_method')
def teardown_method(self):
print('teardown_method')
def setup(self):
print('setup')
def teardown(self):
print('teardown')
def test_1(self):
print('用例 》 1')
def test_2(self):
print('用例 》 2')
if __name__ == '__main__':
pytest.main('-q test_classs.py')
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.7.1, py-1.5.4, pluggy-0.7.1
rootdir: D:\PyCharmcode\untitled\pytestceshi, inifile:
plugins: metadata-1.7.0, html-1.19.0collected 4 items
test_classs.py 所有用例执行前运行
每条用例执行前都会运行
.正在执行用例>1
每条用例执行后都会运行
每条用例执行前都会运行
.正在执行用例>2
每条用例执行后都会运行
setup_class
setup_method
setup
.用例 》 1
teardown
teardown_method
setup_method
setup
.用例 》 2
teardown
teardown_method
teardown_class
所有用例执行后运行
[100%]
========================== 4 passed in 0.07 seconds ===========================
从运行结果看出,setup_module/teardown_module的优先级是最大的,然后函数里面用到的setup_function/teardown_function与类里面的setup_class/teardown_class互不干涉
fixture方法相较setup/teardown优势
fixture相对于setup和teardown来说应该有以下几点优势:
命名方式灵活,不局限于setup和teardown这几个命名
conftest.py 配置里可以实现数据共享,不需要import就能自动找到一些配置
scope=“module” 可以实现多个.py跨文件共享前置
scope=“session” 以实现多个.py跨文件使用一个session来完成多个用例
import pytest
@pytest.fixture()
def login(request):
print('请登录~~~~~~~~~')
def test_1():
print('用例 》 1')
def test_2(login):
print('用例 》 2')
def test_3(login):
print('用例 》 3')
if __name__ == '__main__':
pytest.main(['-q', 'test_class.py'])
test_class.py .用例 》 1
请登录~~~~~~~~~
.用例 》 2
请登录~~~~~~~~~
.用例 》 3
@pytest.fixture() 不带参数的时候 默认是 : scope=”function”
相当于setup、teardown,只要用例调用了,就会走login方法,当 fixture参数scope=”module” 的时候,那么它只会在第一个调用的时候才会走login方法,而后续的不会运行, 如下图所示:
import pytest
@pytest.fixture(scope='module')
def login(request):
print('请登录~~~~~~~~~')
def test_1(login):
print('用例 》 1')
def test_2(login):
print('用例 》 2')
def test_3(login):
print('用例 》 3')
if __name__ == '__main__':
pytest.main(['-q', 'test_class.py'])
test_class.py 请登录~~~~~~~~~
.用例 》 1
.用例 》 2
.用例 》 3
上述所述相当于setup,下面我们来说一下fixture中的teardown
yield,我们可以通过yied 来唤醒执行teardown,如下图:
import pytest
@pytest.fixture(scope='module')
def login(request):
print('请登录~~~~~~~~~')
yield
print('关闭状态~~~~~~~')
def test_1(login):
print('用例 》 1')
def test_2(login):
print('用例 》 2')
def test_3(login):
print('用例 》 3')
if __name__ == '__main__':
pytest.main(['-q', 'test_class.py'])
test_class.py 请登录~~~~~~~~~
.用例 》 1
.用例 》 2
.用例 》 3
关闭状态~~~~~~~
addfinalizer终结函数
import pytest
@pytest.fixture(scope='module')
def login(request):
print('请登录~~~~~~~~~')
def end():
print('关闭~~~~~~')
request.addfinalizer(end)
def test_1(login):
print('用例 》 1')
def test_2(login):
print('用例 》 2')
def test_3(login):
print('用例 》 3')
if __name__ == '__main__':
pytest.main(['-q', 'test_class.py'])
test_class.py 请登录~~~~~~~~~
.用例 》 1
.用例 》 2
.用例 》 3
关闭~~~~~~
yield和addfinalizer方法都是在测试完成后呼叫相应的代码。但是addfinalizer不同的是:
他可以注册多个终结函数。
这些终结方法总是会被执行,无论在之前的setup code有没有抛出错误。这个方法对于正确关闭所有的fixture创建的资源非常便利,即使其一在创建或获取时失败
参数化parametrize
import pytest
@pytest.mark.parametrize('input, result',[('5-0',5),("2+3",5),])
def test_01(input, result):
assert eval(input) == result
@pytest.mark.parametrize('x',[1,2,3,])
@pytest.mark.parametrize('y',[4,5,6,])
def test_02(x,y):
print('x => %s,y => %s' % (x,y))
test_para.py ...x => 1,y => 4
.x => 2,y => 4
.x => 3,y => 4
.x => 1,y => 5
.x => 2,y => 5
.x => 3,y => 5
.x => 1,y => 6
.x => 2,y => 6
.x => 3,y => 6
通过编写的小案例发现:
parametrize参数化,第一种写法是对应的,第二种写法会将所有组合都跑一遍
自定义标记mark
# test_para.py
import pytest
@pytest.mark.web
@pytest.mark.parametrize('input, result',[('5-0',5),("2+3",5),])
def test_01(input, result):
assert eval(input) == result
print('断言通过')
@pytest.mark.parametrize('x',[1,2,3,])
@pytest.mark.parametrize('y',[4,5,6,])
def test_02(x,y):
print('x => %s,y => %s' % (x,y))
# test_class.py
import pytest
import time
@pytest.mark.web
def test_1(driver,start):
print('搜索python')
driver.find_element_by_id('kw').send_keys('python')
driver.find_element_by_id('su').click()
time.sleep(5)
def test_2(driver,start):
print('搜索地图')
driver.find_element_by_id('kw').send_keys('地图')
driver.find_element_by_id('su').click()
time.sleep(5)
上述几个case我们标记 web,然后我们在文件目录中运行命令行, > pytest -m web
pytest就回自动运行 test_para.py/test_01和test_class/test_1 两条用例,别的case都会跳过不运行
如果相同时运行多个标记case,那么标记名之间用 or 相连, > pytest -m “web or webtest”
这时会运行所有标记为 web和webtest 的case
mark标记也可以用在类上,cmd运行类上的标记时,会运行类下的所有case
函数传参
import pytest
login_data = [('admin', '1111'), ('admin', '')]
def login(user, pwd):
print('用户名:', user)
print('密码:', pwd)
if pwd:
return True
else:
return False
@pytest.mark.parametrize('user, pwd', login_data)
def test_01(user, pwd):
a = login(user, pwd)
assert a == True, '密码为空'
if __name__ == '__main__':
pytest.main()
chuancan.py .用户名: admin
密码: 1111
F用户名: admin
密码:
AssertionError: 密码为空
True != False
Expected :False
Actual :True
<Click to see difference>
user = 'admin', pwd = ''
@pytest.mark.parametrize('user, pwd', login_data)
def test_01(user, pwd):
a = login(user, pwd)
> assert a == True, '密码为空'
E AssertionError: 密码为空
E assert False == True
chuancan.py:18: AssertionError
[100%]
================================== FAILURES ===================================
_______________________________ test_01[admin-] _______________________________
user = 'admin', pwd = ''
@pytest.mark.parametrize('user, pwd', login_data)
def test_01(user, pwd):
a = login(user, pwd)
> assert a == True, '密码为空'
E AssertionError: 密码为空
E assert False == True
chuancan.py:18: AssertionError
---------------------------- Captured stdout call -----------------------------
用户名: admin
密码:
===================== 1 failed, 1 passed in 0.19 seconds ======================
函数传参就没什么可说的了,通过上述实例我们可以看出,两个case互不影响,即使有一个失败了,另一个也会正常运行,这里值得一提的是,我们可以自定义一个异常的名字,只需要在断言后直接加上就可以了
request参数
# conftest.py
import pytest
@pytest.fixture(scope='module')
def login(request):
user = request.param['user']
psw = request.param['psw']
print('用户名:', user)
print('密码:', psw)
if psw:
return True
else:
return False
# test_para.py
import pytest
log = [{'user': 'admin', 'psw': '11111'}, {'user': 'asd', 'psw': ''}]
@pytest.mark.parametrize('login', log, indirect=True)
def test_03(login):
a = login
print('login的返回值为:%s'%a)
assert a
@pytest.mark.parametrize('login', log)
def test_04(login):
print('参数:', login)
print(login['user'])
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.7.1, py-1.5.4, pluggy-0.7.1
rootdir: D:\PyCharmcode\untitled\pytestceshi, inifile:
plugins: metadata-1.7.0, html-1.19.0collected 4 items
test_para.py 用户名: admin
密码: 11111
.login的返回值为:True
用户名: asd
密码:
Flogin的返回值为:False
test_para.py:15 (test_03[login1])
login = False
@pytest.mark.parametrize('login', log, indirect=True)
def test_03(login):
a = login
print('login的返回值为:%s'%a)
> assert a
E assert False
test_para.py:20: AssertionError
.参数: {'user': 'admin', 'psw': '11111'}
admin
.参数: {'user': 'asd', 'psw': ''}
asd
[100%]
================================== FAILURES ===================================
_______________________________ test_03[login1] _______________________________
login = False
@pytest.mark.parametrize('login', log, indirect=True)
def test_03(login):
a = login
print('login的返回值为:%s'%a)
> assert a
E assert False
test_para.py:20: AssertionError
---------------------------- Captured stdout setup ----------------------------
用户名: asd
密码:
---------------------------- Captured stdout call -----------------------------
login的返回值为:False
===================== 1 failed, 3 passed in 0.20 seconds ======================
如果想把登录操作放到前置操作里,也就是用到@pytest.fixture装饰器,传参就用默认的request参数: user = request.param 这一步是接收传入的参数,如果传多个参数的话,可以把多个参数用一个字典去存储,这样最终还是只传一个参数不同的参数再从字典里面取对应key值就行,如: user = request.param[“user”]
注:添加indirect=True
参数是为了把login当成一个函数去执行,而不是一个参数