Loading

pytest基于类的用例方法与fixture组织

fixture介绍

fixture是pytest的核心功能,也是亮点功能,熟练掌握fixture的使用方法,pytest用起来才会得心应手!

 

fixture的目的是提供一个固定基线,在该基线上测试可以可靠地和重复地执行。fixture提供了区别于传统单元测试(setup/teardown)有显著改进:

  • 有独立的命名,并通过声明它们从测试函数、模块、类或整个项目中的使用来激活。
  • 按模块化的方式实现,每个fixture都可以互相调用。
  • fixture的范围从简单的单元扩展到复杂的功能测试,允许根据配置和组件选项对fixture和测试用例进行参数化,或者跨函数function、类class、模块module或整个测试会话session范围。

fixture用途

1.做测试前后的初始化设置,如测试数据准备,链接数据库,打开浏览器等这些操作都可以使用fixture来实现

2.测试用例的前置条件可以使用fixture实现

3.支持经典的xunit fixture ,像unittest使用的setup和teardown

4.fixture可以实现unittest不能实现的功能,比如unittest中的测试用例和测试用例之间是无法传递参数和数据的,但是fixture却可以解决这个问题

fixture实例

入门运用fixture

创建一个fixtureclass文件,并创建test_something.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-


import pytest

class TestSomething:

    # 我们希望运行test用例前运行“准备工作”,并在用例结束后运行“清理工作”,这时候用到fixture里的yield
    @pytest.fixture()  # autouse=True 自动使用
    def setUp(self):
        print("准备工作...")
        yield
        print("清理工作...")

    # 每个用例以参数的形式把setUp传递进去,当运行用例时就能达到预期效果
    def test_method_A(self,setUp):
        print("测试类 > 用例方法A")

    def test_method_B(self,setUp):
        print("测试类 > 用例方法B")

    def test_method_C(self,setUp):
        print("测试类 > 用例方法C")

    def test_method_D(self,setUp):
        print("测试类 > 用例方法D")

if __name__ == '__main__':
    pytest.main(['-v','-s','test_somthing.py'])
collecting ... collected 4 items

test_somthing.py::TestSomething::test_method_A 准备工作...
测试类 > 用例方法A
PASSED清理工作...

test_somthing.py::TestSomething::test_method_B 准备工作...
测试类 > 用例方法B
PASSED清理工作...

test_somthing.py::TestSomething::test_method_C 准备工作...
测试类 > 用例方法C
PASSED清理工作...

test_somthing.py::TestSomething::test_method_D 准备工作...
测试类 > 用例方法D
PASSED清理工作...


============================== 4 passed in 0.04s ==============================

如果我们不希望每个case都传入setUp,那就要用到autouse=True

如下:

#!/usr/bin/env python
# -*- coding:utf-8 -*-


import pytest

class TestSomething:

    # 我们希望运行test用例前运行“准备工作”,并在用例结束后运行“清理工作”,这时候用到fixture里的yield
    @pytest.fixture(autouse=True)  # autouse=True 自动使用
    def setUp(self):
        print("准备工作...")
        yield
        print("清理工作...")

    def test_method_A(self):
        print("测试类 > 用例方法A")

    def test_method_B(self):
        print("测试类 > 用例方法B")

    def test_method_C(self):
        print("测试类 > 用例方法C")

    def test_method_D(self):
        print("测试类 > 用例方法D")

if __name__ == '__main__':
    pytest.main(['-v','-s','test_somthing.py'])
collecting ... collected 4 items

test_somthing.py::TestSomething::test_method_A 准备工作...
测试类 > 用例方法A
PASSED清理工作...

test_somthing.py::TestSomething::test_method_B 准备工作...
测试类 > 用例方法B
PASSED清理工作...

test_somthing.py::TestSomething::test_method_C 准备工作...
测试类 > 用例方法C
PASSED清理工作...

test_somthing.py::TestSomething::test_method_D 准备工作...
测试类 > 用例方法D
PASSED清理工作...


============================== 4 passed in 0.05s ==============================

进阶运用fixture

 如果我们不想在class类里写fixture装饰器,这里用到第二种方法

通过conftest.py文件作为pytest的配置文件

conftest.py配置需要注意以下点:

  • conftest.py配置脚本名称是固定的,不能改名称
  • conftest.py与运行的用例要在同一个pakage下,并且有__init__.py文件
  • 不需要import导入 conftest.py,pytest用例会自动查找

conftest.py配置说明

  • conftest.py文件名字是固定的,不可以做任何修改
  • 不需要import导入conftest.py,pytest用例会自动识别该文件,若conftest.py文件放在根目录下,那么conftest.py作用于整个目录,全局调用
  • 在不同的测试子目录也可以放conftest.py,其作用范围只在该层级以及以下目录生效
  • 所有目录内的测试文件运行前都会先执行该目录下所包含的conftest.py文件
  • conftest.py文件不能被其他文件导入

conftest.py与fixture结合

conftest文件实际应用中需要结合fixture来使用,如下

  • conftest中fixture的scope参数为session时,那么整个测试在执行前会只执行一次该fixture
  • conftest中fixture的scope参数为module时,那么每一个测试文件执行前都会执行一次conftest文件中的fixture
  • conftest中fixture的scope参数为class时,那么每一个测试文件中的测试类执行前都会执行一次conftest文件中的fixture
  • conftest中fixture的scope参数为function时,那么所有文件的测试用例执行前都会执行一次conftest文件中的fixture

conftest应用场景

  • 测试中需共用到的token
  • 测试中需共用到的测试用例数据
  • 测试中需共用到的配置信息
  • 结合 yield 语句,进行运行前环境的初始化和运行结束后环境的清理工作,yield前面的语句相当于unitest中的setup动作,yield后面的语句相当于unitest中的teardown动作,不管测试结果如何,yield后面的语句都会被执行。
  • 当fixture超出范围时(即fixture返回值后,仍有后续操作),通过使用yield语句而不是return,来将值返回(因为return后,说明该函数/方法已结束,return后续的代码不会被执行),如下:
@pytest.fixture(scope="module")
def smtpConnection():
    smtp_connection = smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
    yield smtp_connection  # 返回 fixture 值smtp_connection
    print("teardown smtp")
    smtp_connection.close()

无论测试的异常状态如何,print和close()语句将在模块中的最后一个测试完成执行时执行。

  • 可以使用with语句无缝地使用yield语法(with语句会自动释放资源)
@pytest.fixture(scope="module")
def smtpConnection():
    with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection: 
        yield smtp_connection  # 返回smtp_connection对象值

测试结束后, 连接将关闭,因为当with语句结束时,smtp_connection对象会自动关闭。

应用场景

多个用例调用一个登陆功能,如果有多个.py的文件都需要调用这个登陆功能的话,那就不能把登陆写到用例里面去了。此时应该要有一个配置文件,单独管理一些预置的操作场景,pytest里面默认读取conftest.py里面的配置

conftest.py代码

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import pytest

@pytest.fixture(scope="class")
def oneTimeSetUp():
    print("类前准备工作...")
    yield
    print("类后清理工作...")

@pytest.fixture(autouse=True)
def setUp():
    print("准备工作...")
    yield
    print("清理工作...")

test_something.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-


import pytest

@pytest.mark.usefixtures("oneTimeSetUp") # 用mark指定oneTimeSetUp运用在该类下
class TestSomething:

    def test_method_A(self):
        print("测试类 > 用例方法A")

    def test_method_B(self):
        print("测试类 > 用例方法B")

    def test_method_C(self):
        print("测试类 > 用例方法C")

    def test_method_D(self):
        print("测试类 > 用例方法D")

if __name__ == '__main__':
    pytest.main(['-v','-s','test_somthing.py'])
collecting ... collected 4 items

test_somthing.py::TestSomething::test_method_A 类前准备工作...
准备工作...
测试类 > 用例方法A
PASSED清理工作...

test_somthing.py::TestSomething::test_method_B 准备工作...
测试类 > 用例方法B
PASSED清理工作...

test_somthing.py::TestSomething::test_method_C 准备工作...
测试类 > 用例方法C
PASSED清理工作...

test_somthing.py::TestSomething::test_method_D 准备工作...
测试类 > 用例方法D
PASSED清理工作...
类后清理工作...


============================== 4 passed in 0.05s ==============================

fixture传参

返回多个数据

如果用例需要用到多个fixture的返回数据,fixture也可以return一个元组、list或字典,然后从里面取出对应数据。

import pytest

@pytest.fixture()
def user():
    print("获取用户名")
    a = "hjt"
    b = "123456"
    return (a, b)


def test_1(user):
    u = user[0]
    p = user[1]
    print("测试账号:%s, 密码:%s" % (u, p))
    assert u == "hjt"

用例传多个fixture参数

@pytest.fixture()
def user():
    print("获取用户名")
    a = "hjt"
    return a

@pytest.fixture()
def psw():
    print("获取密码")
    b = "123456"
    return b

def test_1(user, psw):
    '''传多个fixture'''
    print("测试账号:%s, 密码:%s" % (user, psw))
    assert user == "hjt"

和parametrize组合使用

# 测试账号数据
test_user = ["admin1", "admin2"]
test_psw = ["11111", "22222"]


@pytest.fixture(scope="module")
def input_user(request):
    user = request.param
    print("登录账户:%s" % user)
    return user


@pytest.fixture(scope="module")
def input_psw(request):
    psw = request.param
    print("登录密码:%s" % psw)
    return psw


@pytest.mark.parametrize("input_user", test_user, indirect=True)
@pytest.mark.parametrize("input_psw", test_psw, indirect=True)
# 1) 当 indirect=False 时,argnames 参数被当成普通变量;
# 2) 当 indirect=True 时,parametrize 中的 argnames 参数被当成函数执行,且 argvalues 值作为 argnames函数中的参数传参
def test_login(input_user, input_psw):
    """登录用例"""
    a = input_user
    b = input_psw
    print("测试数据a-> %s, b-> %s" % (a, b))
    assert b

fixture之间互相调用

@pytest.fixture()
def first():
    print("获取用户名")
    a = "hjt"
    return a

@pytest.fixture()
def sencond(first):
    '''psw调用user fixture'''
    a = first
    b = "123456"
    return (a, b)

def test_1(sencond):
    '''用例传fixture'''
    print("测试账号:%s, 密码:%s" % (sencond[0], sencond[1]))
    assert sencond[0] == "hjt"

 

posted @ 2021-04-24 12:47  Binzichen  阅读(1403)  评论(0编辑  收藏  举报