Python pytest单元测试框架常见使用方法

摘要1:https://blog.csdn.net/yxxxiao/article/details/94602614?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param#%E4%BA%8C%E3%80%81%E6%96%AD%E8%A8%80

摘要2:https://blog.csdn.net/yxxxiao/article/details/94591174#%E5%9B%9B%E3%80%81pytest%20%E7%94%A8%E4%BE%8B%E8%A7%84%E5%88%99

摘要3:https://www.jianshu.com/p/a754e3d47671

摘要4:fixture: https://blog.csdn.net/totorobig/article/details/111823208

 

一、pytest 断言

断言是判断实际结果与预期结果的重要方法。pytest除了支持正常情况的断言,还支持异常断言。

1、正常断言

正常的断言在上一篇博客中已经有所体现,pytest使用最基本的python中的assert语句进行断言,下面我们再举一个例子

# content of test_assert1.py
def f():
    return 3
def test_function():
    assert f() == 4

 执行上面测试:

$ py.test test_assert1.py
=========================== test session starts ============================
platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1
rootdir: /tmp/doc-exec-87, inifile:
collected 1 items
test_assert1.py F
================================= FAILURES =================================
______________________________ test_function _______________________________
def test_function():
> assert f() == 4
E assert 3 == 4
E + where 3 = f()
test_assert1.py:5: AssertionError
========================= 1 failed in 0.01 seconds =========================

我们发现,该测试失败了。而且pytest帮我们打印出来了中间表达式f()的结果,这样我们就可以非常清晰的知道该测试为什么失败。但是,如果你像下面这样写assert语句,则我们得不到assert表达式中的子表达是的中间结果:

assert a % 2 == 0, "value was odd, should be even"

所以,我们在写assert语句的时候,子表达式最好是一个函数签名,这样我们就可以得到该函数的计算结果,以便我们知道测试为什么失败。我们将上面的语句改成下面这样就可以了:

value = a % 2
assert value == 0, "value was odd, should be even"

2、异常断言

有些时候我们会对某些异常写断言语句,例如我们断言1除以0,将产生一个ZeroDivisionError类型的异常。针对这样的断言,pytest给我们提供了pytest.raise方法:

有的时候,我们可能需要在测试中用到产生的异常中的某些信息,比如异常的类型type,异常的值value等等。下面我们修改下上面的测试:

import pytest
def test_recursion_depth():
    with pytest.raises(ZeroDivisionError) as excinfo:
	    1/0
    assert excinfo.type == 'RuntimeError'

这个测试中,我们使用了测试的异常类型:excinfo.type。执行这个测试:

 
C:\Users\liu.chunming\Desktop>py.test idlist.py
============================= test session starts =============================
platform win32 -- Python 2.7.10 -- py-1.4.28 -- pytest-2.7.1
rootdir: C:\Users\liu.chunming\Desktop, inifile:
plugins: capturelog, instafail, pythonpath
collected 1 items
 
idlist.py F
 
================================== FAILURES ===================================
____________________________ test_recursion_depth _____________________________
 
    def test_recursion_depth():
        with pytest.raises(ZeroDivisionError) as excinfo:
                1/0
>       assert excinfo.type == 'RuntimeError'
E    assert <type 'exceptions.ZeroDivisionError'> == 'RuntimeError'
E     +  where <type 'exceptions.ZeroDivisionError'> = <ExceptionInfo ZeroDivisi
onError tblen=1>.type
 
idlist.py:5: AssertionError
========================== 1 failed in 0.05 seconds ==========================

因为该测试断言产生的异常类型是RuntimeError,而实际上产生的异常类型是ZeroDivisionError,所以测试失败了。在测试结果中,可以看到assert子表达式excinfo.type的值。

二、pytest fixture

pytest支持以xUnit格式型的测试模型(setup/teardown),但还与python自带的unittest还是有一点差别,如下

  • 模块形式----使用setup_module/teardown_module  
  • 函数形式----使用setup_function/teardown_function
  • 类形式----使用setup_class/teardown_class
  • 方法形式---使用setup_method/teardown_method

注意:

pytest也可以直接运行unittest模式的测试用例

如果你在pytest模式中使用setupClass()函数是不行的,不会识别,但如果用例类继承之unittest.Testcase,还是可以识别的

1、fixture scope的范围参数

之前使用@pytest.fixture(scope='module')来定义框架,scope的参数有以下几种

  •  function   每一个用例都执行
  • class        每个类执行
  • module     每个模块执行(函数形式的用例)
  • session     每个session只运行一次,在自动化测试时,登录步骤可以使用该session

2、调用fixture的三种方法

2.1函数或类里面方法直接传fixture的函数参数名称

from __future__ import print_function
import pytest
 
@pytest.fixture(scope='module')
def resource_a_setup(request):
    print('\nresources_a_setup()')
    def resource_a_teardown():
        print('\nresources_a_teardown()')
    request.addfinalizer(resource_a_teardown)
 
def test_1(resource_a_setup):
    print('test_1()')
 
def test_2():
    print('\ntest_2()')
 
def test_3(resource_a_setup):
    print('\ntest_3()')
使用-s -v运行查看详情如下

2.2使用装饰器@pytest.mark.usefixtures()修饰需要运行的用例

import pytest
# test_fixture1.py
 
 
@pytest.fixture()
def test1():
    print('\n开始执行function')
 
 
@pytest.mark.usefixtures('test1')
def test_a():
    print('---用例a执行---')
 
 
@pytest.mark.usefixtures('test1')
class TestCase:
 
    def test_b(self):
        print('---用例b执行---')
 
    def test_c(self):
        print('---用例c执行---')
 
 
if __name__ == '__main__':
    pytest.main(['-s', 'test_fixture1.py'])
 
输出结果:
platform win32 -- Python 3.7.0, pytest-4.0.2, py-1.7.0, pluggy-0.8.0
rootdir: C:\Program Files\PycharmProjects\exercise, inifile:collected 3 items
 
test_fixture1.py 
开始执行function
.---用例a执行---
 
开始执行function
.---用例b执行---
 
开始执行function
.---用例c执行---
                                                     [100%]
 
========================== 3 passed in 0.06 seconds ===========================
Process finished with exit code 0

2.3叠加usefixtures

如果一个方法或者一个class用例想要同时调用多个fixture,可以使用@pytest.mark.usefixture()进行叠加。注意叠加顺序,先执行的放底层,后执行的放上层

import pytest
# test_fixture1.py
 
 
@pytest.fixture()
def test1():
    print('\n开始执行function1')
 
 
@pytest.fixture()
def test2():
    print('\n开始执行function2')
 
 
@pytest.mark.usefixtures('test1')
@pytest.mark.usefixtures('test2')
def test_a():
    print('---用例a执行---')
 
 
@pytest.mark.usefixtures('test2')
@pytest.mark.usefixtures('test1')
class TestCase:
 
    def test_b(self):
        print('---用例b执行---')
 
    def test_c(self):
        print('---用例c执行---')
 
 
if __name__ == '__main__':
    pytest.main(['-s', 'test_fixture1.py'])
 
 
输出结果:
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-4.0.2, py-1.7.0, pluggy-0.8.0
rootdir: C:\Program Files\PycharmProjects\exercise, inifile:collected 3 items
 
test_fixture1.py 
开始执行function2
 
开始执行function1
.---用例a执行---
 
开始执行function1
 
开始执行function2
.---用例b执行---
 
开始执行function1
 
开始执行function2
.---用例c执行---
                                                     [100%]
 
========================== 3 passed in 0.03 seconds ===========================
Process finished with exit code 0

3.usefixtures与传fixture区别

 如果fixture有返回值,那么usefixture就无法获取到返回值,这个是装饰器usefixture与用例直接传fixture参数的区别。

当fixture需要用到return出来的参数时,只能讲参数名称直接当参数传入,不需要用到return出来的参数时,两种方式都可以。

4.fixture自动使用autouse=True

当用例很多的时候,每次都传这个参数,会很麻烦。fixture里面有个参数autouse,默认是False没开启的,可以设置为True开启自动使用fixture功能,这样用例就不用每次都去传参了

autouse设置为True,自动调用fixture功能

import pytest
# test_fixture1.py
 
 
@pytest.fixture(scope='module', autouse=True)
def test1():
    print('\n开始执行module')
 
 
@pytest.fixture(scope='class', autouse=True)
def test2():
    print('\n开始执行class')
 
 
@pytest.fixture(scope='function', autouse=True)
def test3():
    print('\n开始执行function')
 
 
def test_a():
    print('---用例a执行---')
 
 
def test_d():
    print('---用例d执行---')
 
 
class TestCase:
 
    def test_b(self):
        print('---用例b执行---')
 
    def test_c(self):
        print('---用例c执行---')
 
 
if __name__ == '__main__':
    pytest.main(['-s', 'test_fixture1.py'])
 
 
输出结果:
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-4.0.2, py-1.7.0, pluggy-0.8.0
rootdir: C:\Program Files\PycharmProjects\exercise, inifile:collected 4 items
 
test_fixture1.py 
开始执行module
 
开始执行class
开始执行function
.---用例a执行---
开始执行class
开始执行function
.---用例d执行---
开始执行class
开始执行function
.---用例b执行---
开始执行function
.---用例c执行---

三、conftest.py的作用范围

一个工程下可以建多个conftest.py的文件,一般在工程根目录下设置的conftest文件起到全局作用。在不同子目录下也可以放conftest.py的文件,作用范围只能在改层级以及以下目录生效。

目录结构:

1. conftest在不同的层级间的作用域不一样

# conftest层级展示/conftest.py
 
import pytest
 
 
@pytest.fixture(scope='session', autouse=True)
def login():
    print('----准备登录----')
 
 
 
 
 
# conftest层级展示/sougou_login/conftest
import pytest
 
 
@pytest.fixture(scope='session', autouse=True)
def bai_du():
    print('-----登录百度页面-----')
 
 
 
 
 
 
 
# conftest层级展示/sougou_login/login_website
import pytest
 
 
class TestCase:
    def test_login(self):
        print('hhh,成功登录百度')
 
 
if __name__ == '__main__':
    pytest.main(['-s', 'login_website.py'])
 
 
 
输出结果:
 
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-4.0.2, py-1.7.0, pluggy-0.8.0
rootdir: C:\Program Files\PycharmProjects\conftest层级演示\sougou_login, inifile:collected 1 item
 
login_website.py ----准备登录----
-----登录百度页面-----
.hhh,成功登录百度
                                                       [100%]
 
========================== 1 passed in 0.03 seconds ===========================
Process finished with exit code 0

2. conftest是不能跨模块调用的(这里没有使用模块调用)

# conftest层级演示/log/contfest.py
import pytest
 
 
@pytest.fixture(scope='function', autouse=True)
def log_web():
    print('打印页面日志成功')
 
 
 
 
# conftest层级演示/log/log_website.py
import pytest
 
 
def test_web():
    print('hhh,成功一次打印日志')
 
 
def test_web1():
    print('hhh,成功两次打印日志')
 
 
if __name__ == '__main__':
    pytest.main(['-s', 'log_website.py'])
 
 
输出结果:
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-4.0.2, py-1.7.0, pluggy-0.8.0
rootdir: C:\Program Files\PycharmProjects\conftest层级演示\log, inifile:
collected 2 items
 
log_website.py ----准备登录----
打印页面日志成功
hhh,成功一次打印日志
.打印页面日志成功
hhh,成功两次打印日志
.
 
========================== 2 passed in 0.02 seconds ===========================

四、pytest 命令行传参

命令行参数是根据命令行选项将不同的值传递给测试函数,比如平常在cmd执行"pytest --html=report.html",这里面的”--html=report.html“就是从命令行传入的参数对应的参数名称是html,参数值是report.html,

1. 总结:

1.conftest.py文件名字是固定的,不可以做任何修改

2.文件和用例文件在同一个目录下,那么conftest.py作用于整个目录

3.conftest.py文件所在目录必须存在__init__.py文件

4.conftest.py文件不能被其他文件导入

5.所有同目录测试文件运行前都会执行conftest.py文件

2. 步骤:

2.1 首先需要在contetest.py添加命令行选项,命令行传入参数”—cmdopt“, 用例如果需要用到从命令行传入的参数,就调用cmdopt函数:

# content of conftest.py
import pytest
 
def pytest_addoption(parser):
    parser.addoption(
        "--cmdopt", action="store", default="type1", help="my option: type1 or type2"
    )
 
@pytest.fixture
def cmdopt(request):
    return request.config.getoption("--cmdopt")

2.2 测试用例编写案例

# content of test_sample.py
import pytest
def test_answer(cmdopt):
    if cmdopt == "type1":
        print("first")
    elif cmdopt == "type2":
        print("second")
    assert 0  # to see what was printed
 
if __name__ == "__main__":
    pytest.main(["-s", "test_case1.py"])

2.3 运行结果:

3. 带参数启动

如果不带参数执行,那么传默认的default=”type1”,接下来在命令行带上参数去执行

$ pytest -s test_sample.py --cmdopt=type2

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值