自动化测试框架pytest系列之基础概念介绍(一)-CSDN博客
自动化测试框架pytest系列之21个命令行参数介绍(二)-CSDN博客
接上两篇文章继续 :
学过unittest的都知道 ,unittest有四个函数 ,分别是 :setUp() 、 tearDown 、setUpClass ,tearDownClass 它们的作用就是在用例运行前和运行后所做的操作 ,往往是在运行前做准备工作 ,运行后做恢复 工作 。
而在pytest中,不仅支持了这四个函数 ,而且又进行了扩展 ,分别为:模块级别、类级别、函数级别、方法级别、方法细化级别,分别如下:
而在pytest中,不仅支持了这四个函数 ,而且又进行了扩展 ,分别为:模块级别、类级别、函数级别、方法级别、方法细化级别,分别如下:
支持方法 | 功能描述 |
---|---|
setup_module() | 在每个模块之前执行 |
teardown_module() | 在每个模块之后执行 |
setup_class() | 在每个类之前执行,即在一个测试类只运行一次setup_class |
teardown_class() | 在每个类之后执行,即在一个测试类只运行一次teardown_class |
setup_function() | 在每个函数之前执行。 |
teardown_function() | 在每个函数之后执行。 |
setup_method() | 在每个方法之前执行 |
teardown_method() | 在每个方法之后执行 |
setup() | 在每个方法之前执行 |
teardown() | 在每个方法之后执行 |
首先要明确的是,这些函数确实很重要 ,举个例子你就知道 ,比如你要做web自动化测试 ,通常的操作是:
打开浏览器
运行一条测试用例
关闭浏览器
重复上面的3个步骤 ,只到所有测试用例运行完毕 。
好了 ,现在的问题是:这个打开浏览器的操作 ,它本身不属于测试用例 ,但是又是一个必须要做的操作 ,怎么办 ?
你就可以将打开浏览器操作放在setup_method()或者setup函数 ,同样关闭浏览器放在teardown_method()或teardown()中。
所以,在编写测试用例中你几乎都会用到它们 。但是在pytest中,这些函数的实现只是为了习惯unittest用户的使用 ,或者是为了兼容unittest框架的那些函数 。如果你不了解unittest中的setup和teardown ,建议你先看看 。如何搭建接口自动化框架系列之unittest测试框架详解(二) - 知乎 (zhihu.com)
但是你如果用过pytest的fixture的话,你几乎就不想用它们了 ,为什么呢 ? 因为它们有10个函数 ,不好记。而如果使用fixture只需要编写一个函数就可以搞定以上所有的需求 。所以 ,在这里建议你使用fixture就可以了 ,它的强大超出你的想象 。
在pytest中 ,fixture是核心的功能 ,通过装饰器所使用的 ,而在pytest中使用的装饰器有很多 ,这个我们后来单独分一个板块再说 。
在这里 ,我们先问自己一个问题 ,为什么这个fixture非常强大 ?这的要从执行测试用例说起 ,因为执行测试用例其实就是如下的3个步骤 :
做初始化操作
执行测试用例
进行清除操作
例如,我要编写一条测试用例 ,一般会在这条用例中编写三个方法 ,分别是:
初始化函数:setUp()
参数化的测试用例 :test_case()
清除函数:tearDown()
那如果是10条测试用例呢 ? 那就是30个方法 ,同样编写1000条测试用例 ,就是3000个方法 。虽然每个方法都大同小异 ,但是你使用其他测试框架是无法把他们聚合到一起的 ,这就为我们编写脚本效率是有大大折扣的 。
但是如果你使用pytest的fixture就可以通过一个函数搞定 ,在这个函数中既可以实现setUp()和tearDown() ,也可以通过参数化将测试数据返回给测试用例。这是什么意思呢 ?我们还是拿上面例子来说明 ,如果你是编写3000个方法 ,而使用fixture的话,只需要编写一个fixture函数可以搞定了 。是不是极大的提升了开发效率呢 ? 这就是fixture的强大之处。这种实现在我的自动化高级课程中就有说明。
虽然我们说了这个fixture的强大 ,如果你没有真正把这个强大功能利用出来 ,那你就浪费了这个功能 。废话少说,接下来我们来介绍fixture这个功能 。
先说这个功能的主要用途 ,就两点 :
可以进行参数化
可以解决初始化和清除的操作,在上一小节中介绍的那10个函数 ,都可以通过这个fixture来实现 。
函数格式 :
fixture(scope,autouse,params,ids,name):
? ?scope :在什么层级下运行 ,它的值只有 :session ,package ,module ,class ,function(默认值)
? ?autouse : 代表的是否自动执行 ,若此参数不加或者设置为False的话,代表不会自动运行 ,设置为True自动执行,无需调用
? ?params : 进行的数据参数化 ,参数化的数据就是通过此参数传入到测试用例中。
? ?ids : 它是给生成的结果的fixture进行重命名 ,主要是为了好理解 ,前提是必须要有参数params
? ?name : 对fixture的函数名起别名, 或者叫重命名 ,没啥大用处 。
使用时只需要将fixture通过装饰器标记到对应的函数上就可以了 。
fixture这个函数中,其中前3个参数是有用的,后面两个无所谓了,可用可不用 。接下来我们介绍它的每个参数使用 :
参数名 :autouse ,它只有两个值,分别是 :
True : 如果等于此值 ,此fixture将被自动调用 ,而且都是在测试用例前执行 。
False : 该值是此参数的默认值 ,如果等于此值 ,此fixture将不会自动调用,需要主动调用。
接下来我们编写一个fixture函数 ,将autouse设置为True, 里面只打印一句代码 ,然后又编写了两条测试用例 。
?
@pytest.fixture(autouse=True)
def fixture_demo():
? ?print("每次运行用例前都执行下这个函数")
?
?
# case1 : 输入正确的用户名和正确的密码进行登录
def test_login_success():
? ?print("1")
? ?expect_result = 0
? ?actual_result = login('admin','123456').get('code')
? ?assert expect_result == actual_result
?
?
# case2 : 输入正确的用户名和错误的密码进行登录
def test_password_is_wrong():
? ?print("2")
? ?expect_reesult = 3
? ?actual_result = login('admin','1234567').get('code')
? ?assert expect_reesult == actual_result
运行结果 :
通过上面的结果我们看到 ,fixture_demo并没有被主动调用 ,就是因为autouse=Ture ,所以它会被自动调用。
以上我们介绍的是autouse=True的情况 ,那如果它的值是False呢 ? 该如何调用呢 。
首先需要明确一点,如果autouse=False ,这是它的默认值 ,也就是说如果是默认值的话就可以省略 。我们将上面的代码修改下 :
@pytest.fixture(autouse=False) # 其实这里的autouse可以不传,因为False就是它的默认值
def fixture_demo():
return "hello pytest"
# case1 : 输入正确的用户名和正确的密码进行登录
def test_login_success():
print("1")
expect_result = 0
actual_result = login('admin','123456').get('code')
assert expect_result == actual_result
# case2 : 输入正确的用户名和错误的密码进行登录
def test_password_is_wrong(fixture_demo):
print("2")
print("fixture_demo:{}".format(fixture_demo))
expect_reesult = 3
actual_result = login('admin','1234567').get('code')
assert expect_reesult == actual_result
注意 :我把fixture函数编写为有返回值了 ,同时在test_password_is_wrong方法中传入了这个fixture函数 。相当于把fixture_demo作为测试方法的参数了 。接下来我们看运行结果:
总结:就是将fixture函数的返回值,直接传给测试用例了 ,测试用例可以接受到fixture的返回值 。
当然 ,你也可以同时编写多个fixture函数 ,同时将这些fixture函数传入到一个用例或者多个用例 ,这都没有问题 。
参数名 :scope ,它有5个值,分别是 :
session :如果等于此值 ,那么这个fixture在整个项目下只运行一次 ,这个特别适合于登录 ,登录在项目中只需要登录一次。
package :如果设置为此值 ,那么这个fixture在这个包下只运行一次 。
module : 如果设置为此值 ,那么这个fixture在这个文件中只运行一次 ,文件中可能既有函数又有类 。
class : 如果设置为此值 ,那么这个fixture在这个类中只运行一次
function:它是这个函数的默认值,如果为此值,那么这个fixture在每个测试函数前运行一次 ,
以下先看一个示例, 这就是scope等于session级别的 。
接下来我们再看一个scope=function级别的,这是它的默认设置 ,所以可以不设置 ,代码依旧这段代码。 ?
@pytest.fixture(autouse=True)
def fixture_demo():
print("每次运行用例前都执行下这个函数")
# case1 : 输入正确的用户名和正确的密码进行登录
def test_login_success():
print("1")
expect_result = 0
actual_result = login('admin','123456').get('code')
assert expect_result == actual_result
# case2 : 输入正确的用户名和错误的密码进行登录
def test_password_is_wrong():
print("2")
expect_reesult = 3
actual_result = login('admin','1234567').get('code')
assert expect_reesult == actual_result
?运行结果 :
说明 :scope=function时 ,它会每次在执行测试用例前都会自动先执行一次fixture_demo函数 ,因为这个测试用例是一个函数级别的 。总结为 :当scope=function时 ,它会每次在执行测试用例前执行一次 。
接下来我们再看一种scope=class的情况,具体看代码 ,
@pytest.fixture(scope='class',autouse=True)
def fixture_demo():
print("在每个类前面只运行一次")
class TestLogin():
# case1 : 输入正确的用户名和正确的密码进行登录
def test_login_success(self):
print("1")
expect_result = 0
actual_result = login('admin','123456').get('code')
assert expect_result == actual_result
# case2 : 输入正确的用户名和错误的密码进行登录
def test_password_is_wrong(self):
print("2")
expect_reesult = 3
actual_result = login('admin','1234567').get('code')
assert expect_reesult == actual_result
?运行结果 :
因为这里面只有一个类 ,所以只运行了一次 。
在上面我们介绍过 ,fixture可以搞定所有的seup和teardown的各种情况 ,上面我们介绍了10个函数 ,而fixture这一个函数就可以搞定 。让我们再次回顾下setup和teardown是啥意思 ,编写一个用例就知道了 。
编写一个函数 :setup() ,主要用来做用例执行前的初始化工作
编写一个函数 :teardown() , 主要用例做用例执行后的恢复操作 。
编写一个测试用例 :test_case() , 所要运行的测试用例 。
那么,它的运行顺序将是 :setup() -> testcase() ->teardown() .
如果使用fixture的话 ,这里就需要用到Python的一个关键字 :yield , 这个关键字非常重要 。不解释,直接上代码 。
@pytest.fixture(autouse=True)
def fixture_demo():
print("每次运行用例前都执行下这个函数")
yield
print("每次运行用例后再此执行下这个函数")
"""
说明 : 针对以上的fixture, 只是在中间加了个yield ,下面又加了一行代码 。
"""
# case1 : 输入正确的用户名和正确的密码进行登录
def test_login_success():
print("1")
expect_result = 0
actual_result = login('admin','123456').get('code')
assert expect_result == actual_result
# case2 : 输入正确的用户名和错误的密码进行登录
def test_password_is_wrong():
print("2")
expect_reesult = 3
actual_result = login('admin','1234567').get('code')
assert expect_reesult == actual_result
直接看运行结果 :
我们再回来看它的运行流程 : ?
你看 ,通过fixture和yield就完美的解决了setup和teardown的问题 ,再加上scope值的控制 ,它就可以解决在不同层次上的初始化和清除操作 。比如你想实现个setup_class和teardown_class() . 你只需要在fixture将scope的值设置为class就可以了 。
看到这里 ,你是否会觉得开发pytest的人真是个天才 。
接下来,我们介绍fixture中最牛的功能,数据参数化 。
在介绍这个参数化之前 ,再回头看看前面的测试用例 ,编写的几条测试用例中 ,你是否发现它们有着高度的相似点 。比如:同样都有预期结果、实际结果以及两者的断言 。所不同的只是测试数据 ,这就为我们做数据参数化提供了条件 。
让我们再回顾下什么是数据参数化 :就是操作步骤相同 ,数据不同 ,那就可以通过编写一个用例 ,传入不同的数据就可以搞定 。
而fixture中的params就可以做数据参数化 ,让我们先来了解下这个参数 :
参数名 :params , 它的值主要接受的是列表 ,而列表中的值存放的就是测试数据 。先看个例子 :
import pytest
@pytest.fixture(params=[1, 2, 3])
def fixture_demo(request): # 传入参数request 系统参数
return request.param # 取列表中获取单个值,默认的取值方式
def test_number(fixture_demo):
print("------->fixture_demo")
assert fixture_demo != 3 # 断言fixture_demo不等于3
if __name__ == '__main__':
pytest.main("-q test05_fixture_params.py")
#执行结果:可以发现结果运行了三次
============================= test session starts =============================
collecting ... collected 3 items
test05_fixture_params.py::test_number[1] PASSED [ 33%]------->fixture_demo
test05_fixture_params.py::test_number[2] PASSED [ 66%]------->fixture_demo
test05_fixture_params.py::test_number[3] FAILED [100%]------->fixture_demo
test05_fixture_params.py:8 (test_number[3])
3 != 3
Expected :3
Actual :3
以上运行的结果是什么意思呢 ?列表中传入3个数 ,它就运行了3次 ;那如果传8个数呢 ,肯定也就会运行8次 。这里面有几个关键参数需要说明 :
request : 在fixture_demo中传入的参数名,这是fixture函数中的系统参数 ,你想要接收params列表里的值,这个参数必须传 ,也必须这样写,这是规则。
request param : 在fixture_demo中的返回值,这也是固定格式 ,必须这样写 ,用来返回params列表里的值 ,每次只返回列表里的一个值 。这也是为什么在测试用例中只调用了一次fixture_demo函数 ,而被运行了三次的原因 。
在这里要特别说明return 和 yeild的区别 。可以看到,在fixture_demo函数目前使用的返回语句关键字是return .但它是可以替换为yield的 ,唯一的不同 ,yield的运行流程较长,具体如下:
先运行yield之前的代码
接着返到测试用例中 ,将yeild后面的返回值 返回给测试用例 ,然后运行测试用例 。
接着再运行yeild下面的代码
如果是用return语句 ,它相当于只走了上面的第2步 ,不会有第1步和第3步 。
接下来,我们我们通过fixture来实现登录的测试用例 ,具体如下。
import pytest
from package_pytest.login import login
cases = [(0,'admin','123456'),(3,'admin','1234567'),(2,'admin',''),(1,'','123456')]
@pytest.fixture(params=cases)
def fixture_demo(request):
print("===初始化方法===")
yield request.param # request.param :代表将params接受到的数据返回给测试用例中。
print("===清除方法===")
# case :运行登录测试用例
def test_login(fixture_demo):
# fixture_demo : 每次循环进来以后,都给到一组数据 ,而这组数据其实就是一个元组
print(fixture_demo)
expect_result = fixture_demo[0]
username = fixture_demo[1]
password = fixture_demo[2]
actual_result = login(username,password).get('code')
assert expect_result == actual_result
运行结果 :
从上面我们可以看到它的运行逻辑为 :
将cases里的第一组数据传入到params参数中 。
然后fixture_demo通过request.param接受到这组数据 。
然后通过yield 将这组数据再返回到测试用例中,
测试用例通过参数名为:fixture_demo接受到这组数据 ,然后测试用例再进一步处理 。
接下来再返回到cases中获取第二组数据 ,然后依次循环以上的四个步骤 。
所以 ,当你列表中的数据是元组 ,那么在测试用例就以元组的形式去处理 ,就比如上面的这个案例就是保存的元组 。而如果列表的值是字符串,你在列表中就以字符串的方式进行处理 ,如果列表里的值是字典 ,那么在测试用例就以字典的形式来处理 。
而使用参数化进行测试用例实现时,在列表中存放的主要还是字典 。
参数名 :ids ,它是给生成的结fixture进行重命名 ,因为它之前生成的模式是按照fixture_demo[index] 进行命名的,其中这个index就是每次循环的数字 ,第一次为0 。如果这个不太好理解 ,就可以使用ids进行重命名 ,但是前提必须要有参数化:params。
?具体代码为 :
import pytest
from package_pytest.login import login
cases = [(0,'admin','123456'),(3,'admin','1234567'),(2,'admin',''),(1,'','123456')]
ids = ['正确的用户名和正确的密码登录','正确的用户名和错误的密码登录','空的密码登录','空的用户名登录']
@pytest.fixture(params=cases,ids=ids)
def fixture_demo(request):
print("===初始化方法===")
yield request.param # request.param :代表将params接受到的数据返回给测试用例中。
print("===清除方法===")
# case1 : 输入正确的用户名和正确的密码进行登录
def test_login(fixture_demo):
# fixture_demo : 每次循环进来以后,都给到一组数据 ,而这组数据其实就是一个元组
print(fixture_demo)
expect_result = fixture_demo[0]
username = fixture_demo[1]
password = fixture_demo[2]
actual_result = login(username,password).get('code')
assert expect_result == actual_result
运行结果 :
参数名 :name ,它是给fixture函数重名名的,或者叫起别名 ,主要是为了调用时方便使用 ,一旦起别名,在测试用例中就必须的使用别名 。
import pytest
from package_pytest.login import login
cases = [(0,'admin','123456'),(3,'admin','1234567'),(2,'admin',''),(1,'','123456')]
@pytest.fixture(params=cases,name='fd')
def fixture_demo(request):
print("===========初始化方法===============")
yield request.param # request.param :代表将params接受到的数据返回给测试用例中。
print("============清除方法================")
# case: 进行登录参数化
def test_login_success(fd):
# fixture_demo : 每次循环进来以后,都给到一组数据 ,而这组数据其实就是一个元组
print("1")
expect_result = fd[0]
username = fd[1]
password = fd[2]
actual_result = login(username,password).get('code')
assert expect_result == actual_result
可以看到,给fixture_demo起了个别名为fd ,那么在测试用例中传入的fixture名字就必须是fd .
以上就是fixture装饰器的5个参数的介绍 ,其中scope 、autouse 、params这个三个是最主要的 ,把这三个参数用好即可 。