2023-04-18 16:47阅读: 1480评论: 0推荐: 0

pytest 的前置与后置处理详谈

pytest 的前置与后置处理

Pytest贴心的提供了类似setup、teardown的方法,并且还超过四个,一共有十种

  • 模块级别:setup_module、teardown_module
  • 函数级别:setup_function、teardown_function,不在类中的方法
  • 类级别:setup_class、teardown_class
  • 方法级别:setup_method、teardown_method
  • 方法细化级别:setup、teardown

setup和teardown的详细使用

代码示例

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pytest
def setup_module():
print("=====整个.py模块开始前只执行一次:打开浏览器=====")
def teardown_module():
print("=====整个.py模块结束后只执行一次:关闭浏览器=====")
def setup_function():
print("===每个函数级别用例开始前都执行setup_function===")
def teardown_function():
print("===每个函数级别用例结束后都执行teardown_function====")
def test_one():
print("one")
def test_two():
print("two")
class TestCase():
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_three(self):
print("three")
def test_four(self):
print("four")
if __name__ == '__main__':
pytest.main(["-q", "-s", "-ra", "test_learn02.py"])
 
 

执行结果

=====整个.py模块开始前只执行一次:打开浏览器=====
===每个函数级别用例开始前都执行setup_function===
PASSED [ 25%]one
===每个函数级别用例结束后都执行teardown_function====
test_learn02.py::test_two ===每个函数级别用例开始前都执行setup_function===
PASSED [ 50%]two
===每个函数级别用例结束后都执行teardown_function====
test_learn02.py::TestCase::test_three
test_learn02.py::TestCase::test_four
============================== 4 passed in 0.03s ==============================
Process finished with exit code 0
====整个测试类开始前只执行一次setup_class====
==类里面每个用例执行前都会执行setup_method==
=类里面每个用例执行前都会执行setup=
PASSED [ 75%]three
=类里面每个用例结束后都会执行teardown=
==类里面每个用例结束后都会执行teardown_method==
==类里面每个用例执行前都会执行setup_method==
=类里面每个用例执行前都会执行setup=
PASSED [100%]four
=类里面每个用例结束后都会执行teardown=
==类里面每个用例结束后都会执行teardown_method==
====整个测试类结束后只执行一次teardown_class====
=====整个.py模块结束后只执行一次:关闭浏览器=====
 
 
  • 模块级别:模块级别 的初始化、清除 分别 在整个模块的测试用例 执行前后执行,并且 只会执行1次 。
  • 类级别:类级别的初始化、清除 分别 在整个类的测试用例 执行前后执行,并且只会执行1次
  • 方法级别: 方法级别 的初始化、清除 分别 在类的 每个测试方法 执行前后执行,并且 每个用例分别执行1次

以上:这种都是针对整个脚本全局生效的

fixture的详细使用

fixture的优势

  • 命名方式灵活,不局限于 setup 和teardown 这几个命名
  • conftest.py 配置里可以实现数据共享,不需要 import 就能自动找到fixture
  • scope="module" 可以实现多个.py 跨文件共享前置
  • scope="session" 以实现多个.py 跨文件使用一个 session 来完成多个用例

fixture参数列表

@pytest.fixture(scope="function", params=None, autouse=False, ids=None, name=None)
def test():
print("fixture初始化的参数列表")
 
 

参数列表

  • scope:可以理解成fixture的作用域,默认:function,还有class、module、package、session四个【常用】
  • autouse:默认:False,需要用例手动调用该fixture;如果是True,所有作用域内的测试用例都会自动调用该fixture
  • name:默认:装饰器的名称,同一模块的fixture相互调用建议写个不同的name

注意

session的作用域:是整个测试会话,即开始执行pytest到结束测试

测试用例调用fixture的方式

  1. 将fixture名称作为测试用例函数的输入参数
  2. 测试用例加上装饰器:@pytest.mark.usefixtures(fixture_name)
  3. fixture设置autouse=True

方式1:将fixture名称作为测试用例函数的输入参数

import pytest
# 调用方式一
@pytest.fixture
def login():
print("输入账号,密码先登录")
def test_s1(login):
print("用例 1:登录之后其它动作 111")
def test_s2(): # 不传 login
print("用例 2:不需要登录,操作 222")
 
 

输出:

============================= test session starts =============================
collecting ... collected 2 items
teat_learn03.py::test_s1 输入账号,密码先登录
PASSED [ 50%]用例 1:登录之后其它动作 111
teat_learn03.py::test_s2 PASSED [100%]用例 2:不需要登录,操作 222
============================== 2 passed in 0.02s ==============================
Process finished with exit code 0
 
 

方式2:测试用例加上装饰器:@pytest.mark.usefixtures(fixture_name)

import pytest
# 调用方式一
@pytest.fixture
def login():
print("输入账号,密码先登录")
# 调用方式二
@pytest.fixture
def login2():
print("please输入账号,密码先登录")
@pytest.mark.usefixtures("login2", "login")
def test_s11():
print("用例 11:登录之后其它动作 111")
 
 

输出:

============================= test session starts =============================
collecting ... collected 1 item
teat_learn03.py::test_s11 please输入账号,密码先登录
输入账号,密码先登录
PASSED [100%]用例 11:登录之后其它动作 111
============================== 1 passed in 0.01s ==============================
Process finished with exit code 0
 
 

注意:

  • 在类声明上面加 @pytest.mark.usefixtures() ,代表这个类里面所有测试用例都会调用该fixture
  • 可以叠加多个 @pytest.mark.usefixtures() ,先执行的放底层,后执行的放上层
  • 可以传多个fixture参数,先执行的放前面,后执行的放后面
  • 如果fixture有返回值,用 @pytest.mark.usefixtures() 是无法获取到返回值的,必须用传参的方式(方式一)

方式3:fixture设置autouse=True

import pytest
# 调用方式三
@pytest.fixture(autouse=True)
def login3():
print("====所有作用域内的测试用例都会自动调用该fixture===")
def test_s1(login3):
print("用例 1:登录之后其它动作 111")
def test_s2(): # 不传 login
print("用例 2:不需要登录,操作 222")
# 不是test开头,加了装饰器也不会执行fixture
@pytest.mark.usefixtures("login3")
def loginss():
print(123)
 
 

输出:

====所有作用域内的测试用例都会自动调用该fixture===
PASSED [ 50%]用例 1:登录之后其它动作 111
teat_learn03.py::test_s2 ====所有作用域内的测试用例都会自动调用该fixture===
PASSED [100%]用例 2:不需要登录,操作 222
============================== 2 passed in 0.02s ==============================
 
 

fixture的实例化顺序

  • 较高 scope 范围的fixture(session)在较低 scope 范围的fixture( function 、 class )之前实例化【session > package > module > class > function】
  • 具有相同作用域的fixture遵循测试函数中声明的顺序,并遵循fixture之间的依赖关系【在fixture_A里面依赖的fixture_B优先实例化,然后到fixture_A实例化】
  • 自动使用(autouse=True)的fixture将在显式使用(传参或装饰器)的fixture之前实例化
import pytest
order = []
@pytest.fixture(scope="session")
def s1():
order.append("s1")
@pytest.fixture(scope="module")
def m1():
order.append("m1")
@pytest.fixture
def f1(f3, a1):
# 先实例化f3, 再实例化a1, 最后实例化f1
order.append("f1")
assert f3 == 123
@pytest.fixture
def f3():
order.append("f3")
a = 123
yield a
@pytest.fixture
def a1():
order.append("a1")
@pytest.fixture
def f2():
order.append("f2")
def test_order(f1, m1, f2, s1):
# m1、s1在f1后,但因为scope范围大,所以会优先实例化
assert order == ["s1", "m1", "f3", "a1", "f1", "f2"]
 
 

执行结果:断言成功

关于fixture的注意点

添加了 @pytest.fixture ,如果fixture还想依赖其他fixture,需要用函数传参的方式,不能用 @pytest.mark.usefixtures() 的方式,否则会不生效

@pytest.fixture(scope="session")
def open():
print("===打开浏览器===")
@pytest.fixture
# @pytest.mark.usefixtures("open") 不可取!!!不生效!!!
def login(open):
# 方法级别前置操作setup
print(f"输入账号,密码先登录{open}")
 
 

fixture的后置teardown

用fixture实现teardown并不是一个独立的函数,而是用 yield 关键字来开启teardown操作(yield 之前是前置,之后是后置)

import pytest
@pytest.fixture(scope="session")
def open():
# 会话前置操作setup
print("===打开浏览器===")
test = "测试变量是否返回"
yield test
# 会话后置操作teardown
print("==关闭浏览器==")
@pytest.fixture
def login(open):
# 方法级别前置操作setup
print(f"输入账号,密码先登录---{open}")
name = "==我是账号=="
pwd = "==我是密码=="
age = "==我是年龄=="
# 返回变量
yield name, pwd, age
# 方法级别后置操作teardown
print("登录成功")
def test_s2(login):
print("==用例2==")
print(login)
 
 

输出:

============================= test session starts =============================
collecting ... collected 1 item
test_05.py::test_s2 ===打开浏览器===
输入账号,密码先登录---测试变量是否返回
PASSED [100%]==用例2==
('==我是账号==', '==我是密码==', '==我是年龄==')
登录成功
==关闭浏览器==
============================== 1 passed in 0.01s ==============================
 
 

yield注意事项

  • 如果yield前面的代码,即setup部分已经抛出异常了,则不会执行yield后面的teardown内容
  • 如果测试用例抛出异常,yield后面的teardown内容还是会正常执行

addfinalizer

在用法上,addfinalizer跟yield是不同的,需要你去注册作为终结器使用的函数。

mport pytest
@pytest.fixture()
def demo_fixture(request):
print("\n这个fixture在每个case前执行一次")
def demo_finalizer():
print("\n在每个case完成后执行的teardown")
# 注册demo_finalizer为终结函数
request.addfinalizer(demo_finalizer)
def test_01(demo_fixture):
print("\n===执行了case: test_01===")
def test_02(demo_fixture):
print("\n===执行了case: test_02===")
def test_03(demo_fixture):
print("\n===执行了case: test_03===")
 
 

输出:

============================= test session starts =============================
collecting ... collected 3 items
test_06.py::test_01
这个fixture在每个case前执行一次
PASSED [ 33%]
===执行了case: test_01===
在每个case完成后执行的teardown
test_06.py::test_02
这个fixture在每个case前执行一次
PASSED [ 66%]
===执行了case: test_02===
在每个case完成后执行的teardown
test_06.py::test_03
这个fixture在每个case前执行一次
PASSED [100%]
===执行了case: test_03===
在每个case完成后执行的teardown
============================== 3 passed in 0.04s ==============================
Process finished with exit code 0
 
 

yield与addfinalizer的区别

1. addfinalizer可以注册多个终结函数。
import pytest
import pytest
@pytest.fixture()
def demo_fixture(request):
print("\n这个fixture在每个case前执行一次")
def demo_finalizer():
print("\n在每个case完成后执行的teardown")
def demo_finalizer2():
print("\n在每个case完成后执行的teardown2")
def demo_finalizer3():
print("\n在每个case完成后执行的teardown3")
# 注册demo_finalizer为终结函数
request.addfinalizer(demo_finalizer)
request.addfinalizer(demo_finalizer2)
request.addfinalizer(demo_finalizer3)
def test_01(demo_fixture):
print("\n===执行了case: test_01===")
def test_02(demo_fixture):
print("\n===执行了case: test_02===")
def test_03(demo_fixture):
print("\n===执行了case: test_03===")
 
 

输出:

============================= test session starts =============================
collecting ... collected 3 items
test_06.py::test_01
这个fixture在每个case前执行一次
PASSED [ 33%]
===执行了case: test_01===
在每个case完成后执行的teardown3
在每个case完成后执行的teardown2
在每个case完成后执行的teardown
test_06.py::test_02
这个fixture在每个case前执行一次
PASSED [ 66%]
===执行了case: test_02===
在每个case完成后执行的teardown3
在每个case完成后执行的teardown2
在每个case完成后执行的teardown
test_06.py::test_03
这个fixture在每个case前执行一次
PASSED [100%]
===执行了case: test_03===
在每个case完成后执行的teardown3
在每个case完成后执行的teardown2
在每个case完成后执行的teardown
============================== 3 passed in 0.04s ==============================
Process finished with exit code 0
 
 

可以看到,注册的3个函数都被执行了,但是要注意的是执行顺序,与注册的顺序相反

2. 当setUp的代码执行错误,addfinalizer依旧会执行

conftest.py的详细讲解

可以理解成一个专门存放fixture的配置文件,如果多个测试用例文件(test_*.py)的所有用例都需要用登录功能来作为前置操作,那就不能把登录功能写到某个用例文件中去了,conftest.py的出现,就是为了解决上述问题,单独管理一些全局的fixture。

conftest.py配置fixture注意事项

  • pytest会默认读取conftest.py里面的所有fixture
  • conftest.py 文件名称是固定的,不能改动
  • conftest.py只对同一个package下的所有测试用例生效
  • 不同目录可以有自己的conftest.py,一个项目中可以有多个conftest.py
  • 测试用例文件中不需要手动import conftest.py,pytest会自动查找

conftest.py使用举例

conftest.py文件(scope=“session”)

import pytest
@pytest.fixture(scope="session")
def login():
print("输入账号密码")
yield
print("清理数据完成")
 
 

case文件:

import pytest
class TestLogin1():
def test_1(self, login):
print("用例1")
def test_2(self):
print("用例2")
def test_3(self, login):
print("用例3")
if __name__ == '__main__':
pytest.main()
 
 

输出:

输入账号密码
PASSED [ 33%]用例1
PASSED [ 66%]用例2
PASSED [100%]用例3
清理数据完成
 
 

可以看出,conftest.py内的fixture方法的作用范围是session,调用时,整个.py文件只会调用一次

conftest.py注意事项

  • conftest.py的作用域与Python变量作用域相同
  • 内层包内conftest.py不允许被其它包的测试类或方法使用,相当于本地变量
  • 外层conftest.py可被内层测试类和方法使用,相当于全局变量

总结

以上就是勇哥今天为各位小伙伴准备的内容,如果你想了解更多关于Python自动化测试的知识和技巧,欢迎关注:

我的公众号:百态测试

博客(奈非天的主页 - 博客园 (cnblogs.com)

我会不定期地分享更多的精彩内容。感谢你的阅读和支持!

posted @   测试玩家勇哥  阅读(1480)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
🔑
  1. 1 DAN DAN 心魅かれてく(Cinema Version) FIELD OF VIEW
  2. 2 ロマンティックあげるよ 橋本潮
  3. 3 INNOCENT PRIMEVAL BREAKER GAME VERSION REOL
  4. 4 METAMORPHOSIS INTERWORLD
ロマンティックあげるよ - 橋本潮
00:00 / 00:00
An audio error has occurred, player will skip forward in 2 seconds.

作词 : 吉田健美

作曲 : 池毅

おいでファンタジー

好きさミステリー

君の若さ隠さないで

不思議したくて冒険したくて

誰もみんなウズウズしてる

大人のフリしてあきらめちゃ

奇跡の謎など解けないよ

もっとワイルドに

もっとたくましく生きてごらん

ロマンチックあげるよ

ロマンチックあげるよ

ホントの勇気みせてくれたら

ロマンチックあげるよ

ロマンチックあげるよ

トキメク胸に

キラキラ光った夢をあげるよ

いつかワンダフル

きっとビューティフル

人のジャングル迷いこんで

スリルしたくて幸せしたくて

何故かみんなソワソワしてる

思ったとおりに叫ばなきゃ

願いは空まで届かない

もっとセクシーに

もっと美しく生きてごらん

ロマンチックあげるよ

ロマンチックあげるよ

ホントの涙見せてくれたら

ロマンチックあげるよ

ロマンチックあげるよ

淋しい心やさしく包んで

愛をあげるよ

ロマンチックあげるよ

ロマンチックあげるよ

ホントの勇気みせてくれたら

ロマンチックあげるよ

ロマンチックあげるよ

トキメク胸に

キラキラ光った夢をあげるよ