pytest学习总结1

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当成一个函数去执行,而不是一个参数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值