如何在自动化测试中应用装饰器、多线程优化自动化架构?
1、装饰器概念
装饰器是Python中用于修改函数或类的语法结构的工具。它以函数作为输入参数,并返回一个函数作为一个输出函数,在不改变原有函数的代码情况下,给函数增加功能或改变函数行为。
装饰器的使用方式是在函数定义的上方使用 @decorator_name 的形式将装饰器应用到函数上,这样定义的函数在被调用时会先执行装饰器函数,再执行原始函数。常见的装饰器有类装饰器、函数装饰器、属性装饰器等。
装饰器本身需要接受一个被装饰的对象作为参数,该参数通常为函数、方法、类等对象。
装饰器需要返回一个对象,该对象可以是 经过处理的原参数对象、一个包装且类似原参数的对象
1.1 最简单的装饰器
def my_decorator(func):def wrapper():print("Before the function is called.")func()print("After the function is called.")return wrapper@my_decorator
def say_hello():print("Hello!")say_hello()
如代码所示:
1、当我们调用被装饰器修饰的函数的时候,首先会把 say_hello 的函数传入 my_decorator
的 func 参数中,然后再去执行内部的 wrapper 函数(在这个函数内部就会操作很多东西,比如增删改等操作),最后返回 wrapper 函数。
2、实际上我们调用的 say helo 函数没有被执行,那这里为什么执行了呢?是因为 wrapper函数里面又调用了一次它,如果不调用,那么 say hello 函数就是一个装饰,但是通常情况下都会调用一次来达到我们的一些特殊目的。
1.2 用于修改对象和函数的装饰器
def wrap(obj):obj.name = 'python'return obj@wrap
class Bar(object):def __init__(self, name):passprint(Bar.name)@wrap
def foo():passprint(foo.name)
可以看到实际的使用过程中,warp装饰器已经成功的给Bar对象添加了name属性。除了给类对象添加属性之外,它还可以给函数对象添加属性。
1.3 用于模拟对象的装饰器-函数装饰器【重点中的重点】
用于模拟对象的装饰器--函数装饰器
上面例子中的装饰器,是直接修改了传入对象,而装饰器最常用的方式却是模拟一个传入对象。即返回一个和原对象相似的对象(即调用接口完全一样的另一个对象),并且该模拟对象是包装了原对象在内的。具体代码如下:
def outer(func):def inner():print("Hello inner")func()return inner
@outer
def foo():print("Hello foo")print(foo.__name__)
foo()
上面是一个函数装饰器,即用来修饰函数的装饰器因为它返回了一个模拟func对象的inner对象。而这里inner对象是一个函数,所以这个装饰器只能装饰函数。(因为inner对象只能模拟func这样的函数对象,不能模拟class对象)
可以看到首先打印的是 foo._name_代码,注意内容是inner而不是foo(说明其本质上是inner函数);其次打印的时候,先打印inner函数中的内容,后打印foo函数中的内容。
Python 中被装饰后的函数,函数名等函数属性会发生改变(相当于另一个函数了)所以,Python 的functools 包中提供了一个叫 wraps 的装饰器来解决该问题。它能使其保留原有函数的结构。
1.4 用于模拟对象的装饰器
def outer(obj):def inner(self):print('hello inner')obj(self)return innerclass Zoo(object):def _init_(self):pass@outerdef zoo(self):print('hello zoo')
zoo = Zoo()
print(zoo.zoo.__name__)
zoo.zoo()
可以看到类方法装饰器和函数装饰器,唯一的区别是多了一个默认的self参数,这是因为类方法本身比函数多一个参数。
2、装饰器的应用
Python 装饰器的应用比较广泛,大部分场景的公共处理逻辑都可以使用装饰器去简化。(使用上类似于 JAVA 中的注解)一般比较常见的场景比如:
- 日志记录
- 权限验证
- 单例模式
- 竞争资源管理
- Fixture
2.1 装饰器在pytest中的使用@pytest.fixture
比如 conftest 胶水文件中可以写 driver 以及写数据库连接操作等,使用装饰器+生成器假设我们有一个需要连接数据库执行查询操作的测试用例集合。
在每个测试用例执行前,我们需要先建立数据库连接,并在每个测试用例执行结束后,关闭数据库连接。这时,我们可以使用 @pytest.fixture 装饰器定义一个名为 db_conn 的fixture 函数,它返回一个数据库连接对象,然后在测试用例函数中通过参数引用该 fixture函数即可获得连接对象。
我们可以非常方便地对测试用例进行初始化和清理工作,避免几余代码,并且可以重复利用测试代码和资源。
代码示例如下:
import pytest
import psycopg2
@pytest.fixture(scope='function')
def db_conn():conn = psycopg2.connect(database="testdb",user="testuser",
password="testpass'host="localhost",port="5432")yield connconn.close()def test_query1(db_conn):cursor =db_conn.cursor()cursor.execute('SELECT * FROM table1')result = cursor.fetchall()assert len(result)>0def test_query2(db_conn):cursor=db_conn.cursor()cursor.execute('SELECT * FROM table2')result = cursor.fetchall()assert len(result)>0
2.2 pytest中的@pytest.mark
它可用于给测试用例打标签,方便统计测试结果和过滤测试用例。比如,给测试用例打上 slow、fast、smoke 等标签,可以通过 pytest 的命令行参数 pytest -m 进行选择性测试。
import pytest@pytest.mark.slow
def test_case1():pass@pytest.mark.fast
def test_case2():pass@pytest.mark.smoke
def test_case3():pass
pytest .\1.py -m smoke
如图,只有标记为smoke的被执行了
2.3 @pytest.mark.paramsmetrize在pytest中的使用(参数化)
它可用于参数化测试用例,即对同一种情况下的不同输入进行测试,可以减少重复的测试代码和测试用例数量。
import pytest
@pytest.mark.parametrize('a, b,c',[(1,2,3),(4,5,9),(7,8, 15)])
def test add(a, b, c):assert a+b==C
2.4 pytest中的@pytest.hookimpl中的使用
它可以定义 pytest 的 hook 函数,hook 函数用于在 pytest 的执行过程中被调用,以处理测试收集、运行、报告等各个环节。
import pytest
@pytest.hookimpl
def pytest runtest call(item):print(f"test function fitem.name} is about to run...")
把这个函数放到 conftest 中就可以实现功能了
每当我们跑一个用例,就会打印一些日志内容
2.5 python 的@property 装饰器的介绍与使用
python 的@property 是 python 的一种装饰器,是用来修饰方法的。
作用:
我们可以使用@property 装饰器来创建只读属性,@property 装饰器会将方法转换为相同名称的只读属性,可以与所定义的属性配合使用,这样可以防止属性被修改。
简而言之:
1.可以把方法变为属性
2.防止属性被修改
class DataSet(object):@propertydef method _with_property(self):
##含有@propertyreturn 15def method without property(self):##不含@propertyreturn 15
I= DataSet()
print(l.method with _property)
#加了@property 后,可以用调用属性的形式来调用方法,后面不需要加()
print(l.method without property())
#没有加@property,必须使用正常的调用方法的形式,即在后面加()
3、多线程编程思想
1、如果你的测试用例是 I/O 密集型的,比如页面加载、数据输入等操作,那么使用多线程可以显著提高测试执行的效率。因为在等待I/O操作完成的时间内,可以切换到其他线程执行任务,避免浪费 CPU 资源,简单来说就是频繁输入加载等操作,就很适合多线程,不过要注意电脑的配置。
2、如果你的测试用例需要在不同的浏览器或者不同的页面之间进行切换,那么使用多
线程可以避免在切换时出现页面卡顿或者响应延迟的问题。
3、如果除开这上面的操作,如果强行使用多线程开发,可能还会导致性能下降,因为需要频繁去调度 cpu,切换线程,线程间切换的开销可能会抵消掉并行处理所获得的性能提升。
使用方法:使用 pytest-xdist 插件来实现多线程并发执行测试用例(自动分配每个线程要执行哪些测试用例)
当运行测试时,pytest-xdist 插件会自动将测试用例分发给多个线程并发执行,每个线程都可以独立打开一个网页运行测试用例,从而提高整体的执行效率。
使用 pytest 单线程运行测试用例:
pytest无头模式
在无头模式中,五个用例执行快了四秒,我为每一个用例都分配了一个线程去执行,这样会更快,因为会并发的去执行这五个测试用例。cpu不断的调度实现宏观的并发,微观实际上还是并行)
在有头模式中,打开了五个浏览器一起执行 88 个测试用例,相当于五个人一起执行测试用例,速度肯定是更快的,快了30s。(cpu不断的调度实现宏观的并发,微观实际上还是并行)
用法详解:
1、pytest-n 2:使用 2 个进程并行运行测试,如果有四个用例,1,3用例给第一个线程执行,2,4用例给第二个线程执行。
2、pytest -n auto:自动检测可用的 CPU 核心数量,并使用对应数量的进程并行运行测试。这是一种常用的设置,可以充分利用计算资源。
3、pytest -n 4-dist=loadfile:使用 4个进程并行运行测试,并使用"oadfile"分发插件进行测试运行。
使用 -n 参数,pytest 可以在多个进程中同时执行测试,从而加快整体测试运行时间,特别是在拥有多个 CPU 核心或处理器的计算机上。建议在实际使用时,根据自己的系统配置和测试需求,调整-n参数的值来获得最佳的性能和效果
4、数据驱动测试,yaml数据源