前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Pytest测试实战|Fixture详解

Pytest测试实战|Fixture详解

作者头像
无涯WuYa
发布2024-04-23 16:37:47
740
发布2024-04-23 16:37:47
举报

Pytest测试实战

The pytest framework makes it easy to write small, readable tests, and can scale to support complex functional testing for applications and libraries. 这段话很好地阐述了Pytest的设计思想与强大的特性。之前详细地阐述了Pytest测试框架搜索规则Pytest测试框架执行方式Pytest测试框架参数化,本文章主要详细地阐述下Pytest测试框架中Fixture特性。

Fixture测试固件特性

在Pytest测试框架中Fixture最核心的特点测试固件的特性(所谓测试固件就就是执行测试用例中初始化与清理的部分),Fixture函数测试固件主要是通过yield来进行体现的。在实际的测试实战中,这样的测试场景其实经常出现,比如查询某条数据,那么它的前置动作是添加数据,后置动作是清理数据,而前置动作与后置动作部分,就是测试固件最直接也是最典型的体现。见如下的测试代码。

代码语言:javascript
复制
#! /usr/bin/env python
# -*- coding:utf-8 -*-
# author:无涯

import  pytest 

@pytest.fixture()
def init():
  print('初始化')
  yield 
  print('清理')

在如上代码中定义了Fixture的函数init,这部分详细的解读具体就是:在Fixture函数init中,yield部分主要为测试步骤与测试验证部分,也就是TestCase真正被执行的逻辑部分,yield前面的代码主要是为了执行yield部分初始化的代码,yield后面的代码主要是yield代码执行后的清理操作,持续完善下代码,增加测试函数,完善后的代码如下。

代码语言:javascript
复制
#! /usr/bin/env python
# -*- coding:utf-8 -*-
# author:无涯

import  pytest

@pytest.fixture()
def init():
  print('初始化')
  yield
  print('清理')


def test_case_001(init):
  print('测试步骤与测试验证')

在测试函数test_case_001中形式参数增加了Fixture函数init,其实本质上它是Fixture函数的对象,这样执行测试函数test_case_001后,如上截图中可以清晰的看到第一步执行的是初始化,第二部分执行的也就是测试步骤与测试验证部分,最后是清理的部分。理解了Fixture函数测试固件的特性,下面结合一个书籍管理的微服务详细的演示下它在API自动化测试中的案例应用实战,书籍微服务案例代码如下:

代码语言:javascript
复制
from flask import  Flask,make_response,jsonify,abort,request
from flask_restful import  Api,Resource
from flask_httpauth import  HTTPBasicAuth

from flask import Flask
from flask_jwt import JWT, jwt_required, current_identity
from werkzeug.security import safe_str_cmp

app=Flask(__name__)
app.debug = True
app.config['SECRET_KEY'] = 'super-secret'
api=Api(app=app)
auth=HTTPBasicAuth()

@auth.get_password
def get_password(name):
   if name=='admin':
      return 'admin'
@auth.error_handler
def authoorized():
   return make_response(jsonify({'msg':"请认证"}),403)

books=[
   {'id':1,'author':'wuya','name':'Python接口自动化测试实战','done':True},
   {'id':2,'author':'无涯','name':'Selenium3自动化测试实战','done':False}
]


class User(object):
   def __init__(self, id, username, password):
      self.id = id
      self.username = username
      self.password = password

   def __str__(self):
      return "User(id='%s')" % self.id

users = [
   User(1, 'wuya', 'asd888'),
   User(2, 'stage', 'asd888'),
   User(3,'line','asd888')
]

username_table = {u.username: u for u in users}
userid_table = {u.id: u for u in users}

def authenticate(username, password):
   user = username_table.get(username, None)
   if user and safe_str_cmp(user.password.encode('utf-8'), password.encode('utf-8')):
      return user

def identity(payload):
   user_id = payload['identity']
   return userid_table.get(user_id, None)

jwt = JWT(app, authenticate, identity)

class Books(Resource):
   decorators=[jwt_required()]

   def get(self):
      return jsonify({'status':0,'msg':'ok','datas':books})

   def post(self):
      if not request.json:
         return jsonify({'status':1001,'msg':'请求参数不是JSON的数据,请检查,谢谢!'})
      else:
         book = {
            'id': books[-1]['id'] + 1,
            'author': request.json.get('author'),
            'name': request.json.get('name'),
            'done': True
         }
         books.append(book)
         return {'status':1002,'msg': '添加书籍成功','datas':book}
         # return jsonify({'status':1002,'msg': '添加书籍成功','datas':book}, 201)


class Book(Resource):
   decorators = [jwt_required()]

   def get(self,book_id):
      book = list(filter(lambda t: t['id'] == book_id, books))
      if len(book) == 0:
         return jsonify({'status': 1003, 'msg': '很抱歉,您查询的书的信息不存在'})
      else:
         return jsonify({'status': 0, 'msg': 'ok', 'datas': book})

   def put(self,book_id):
      book = list(filter(lambda t: t['id'] == book_id, books))
      if len(book) == 0:
         return jsonify({'status': 1003, 'msg': '很抱歉,您查询的书的信息不存在'})
      elif not request.json:
         return jsonify({'status': 1001, 'msg': '请求参数不是JSON的数据,请检查,谢谢!'})
      elif 'author' not in request.json:
         return jsonify({'status': 1004, 'msg': '请求参数author不能为空'})
      elif 'name' not in request.json:
         return jsonify({'status': 1005, 'msg': '请求参数name不能为空'})
      elif 'done' not in request.json:
         return jsonify({'status': 1006, 'msg': '请求参数done不能为空'})
      elif type(request.json['done'])!=bool:
         return jsonify({'status': 1007, 'msg': '请求参数done为bool类型'})
      else:
         book[0]['author'] = request.json.get('author', book[0]['author'])
         book[0]['name'] = request.json.get('name', book[0]['name'])
         book[0]['done'] = request.json.get('done', book[0]['done'])
         return jsonify({'status': 1008, 'msg': '更新书的信息成功', 'datas': book})

   def delete(self,book_id):
      book = list(filter(lambda t: t['id'] == book_id, books))
      if len(book) == 0:
         return jsonify({'status': 1003, 'msg': '很抱歉,您查询的书的信息不存在'})
      else:
         books.remove(book[0])
         return jsonify({'status': 1009, 'msg': '删除书籍成功'})

api.add_resource(Books,'/v1/api/books')
api.add_resource(Book,'/v1/api/book/<int:book_id>')

if __name__ == '__main__':
   app.run(debug=True,host='0.0.0.0',port='5000')

需要验证的测试场景为查询书籍,初始化是添加书籍,清理是删除书籍,编写的代码具体如下。

代码语言:javascript
复制
#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:无涯

import pytest
import requests


@pytest.fixture()
def token():
   r=requests.post(
      url='http://localhost:5000/auth',
      json={'username':'wuya','password':'asd888'})
   return r.json()['access_token']

@pytest.fixture()
def headers(token):
   return {'Authorization':'jwt {0}'.format(token)}

def test_all_books(headers):
   r=requests.get(
      url='http://localhost:5000/v1/api/books',
      headers=headers)
   print(r.json())

def writeBook(content):
   with open('bookID','w') as f:
      f.write(content)

def addBook(headers):
   r=requests.post(
      url='http://localhost:5000/v1/api/books',
      json={"name":"接?测试","author":"?涯课堂","done":True},
      headers=headers)
   print('添加书籍:\n',r.json())
   writeBook(str(r.json()['datas']['id']))

def bookID():
   with open('bookID') as f:
      return f.read()

def delBook(headers):
   r=requests.delete(
      url='http://localhost:5000/v1/api/book/{0}'.format(bookID()),
      headers=headers)
   print('删除书籍:\n',r.json())

@pytest.fixture()
def apiInit(headers):
   addBook(headers)
   yield
   delBook(headers)

def test_query_book(apiInit,headers):
   r=requests.get(
      url='http://127.0.0.1:5000/v1/api/book/{0}'.format(bookID()),
      headers=headers)
   print('获取添加的书籍信息:\n',r.json())
   assert r.json()['datas'][0]['id']==int(bookID())

在如上代码中定义的Fixture函数名称为apiInit,初始化代码为添加书籍的方法addBook,清理代码是删除书籍delBook方法,这样在每次执行测试函数test_query_book的时候第一步是添加书籍,第二步骤是查询书籍并且验证查询的结果信息,最后一步是清理添加的数据,这样的目的是为了保持编写的测试用例的独立性。其实本质上而言,只要深刻理解了测试固件的特性,再结合上面的案例,就很好理解Fixture函数测试固件的特性。

Fixture返回值特性

Fixture函数另外一个特性是返回值的特性,这个特性可以很好的应用在API测试中关于授权认证的部分。在API测试中首先需要获取到TOKEN,然后再下个请求中带上登录成功后返回的TOKEN,那么结合Fixture返回值的特性,可以很轻松的来解决这部分。详细的实战代码见如下。

代码语言:javascript
复制
#! /usr/bin/env python
# -*- coding:utf-8 -*-
# author:无涯

import  pytest
import  requests

@pytest.fixture()
def login():
  r=requests.post(
    url='http://0.0.0.0:8000/login/auth/',
    json={"username":"13484545195","password":"asd888"},
    headers={'Content-Type':"application/json"})
  return r.json()['token']

def test_index(login):
  r=requests.get(
    url='http://0.0.0.0:8000/interface/index',
    headers={'Authorization':'JWT {login}'.format(login=login)})
  assert r.status_code==200

在测试函数test_index中形式参数login其实就是Fixture函数login的对象,那么Fixture函数返回值的内容其实就是该函数对象的值,见debug模式下显示的信息。

其实要深刻的理解这部分,首先还是需要理解在Python中一切皆对象这个设计思想,这个设计思想中的对象它可以是一个变量,或者是一个函数,或者是一个类。在这个案例中,对象login它就是一个函数,是Fixture函数login的对象。

Fixture的重命名

在Pytest测试框架中也可以对Fixture函数进行重命名,˙这样在调用的时候直接使用重命名后的名称。Fixture函数中重命名的关键字是name,实现的案例代码如下所示。

代码语言:javascript
复制
#! /usr/bin/env python
# -*- coding:utf-8 -*-
# author:无涯

import  pytest
import  requests

@pytest.fixture(name='token')
def login():
  r=requests.post(
    url='http://0.0.0.0:8000/login/auth/',
    json={"username":"13484545195","password":"asd888"},
    headers={'Content-Type':"application/json"})
  return r.json()['token']

def test_index(token):
  r=requests.get(
    url='http://0.0.0.0:8000/interface/index',
    headers={'Authorization':'JWT {login}'.format(login=token)})
  assert r.status_code==200

if __name__ == '__main__':
  pytest.main(["-s","-v","test_fixture.py"])

指定Fixture作用范围

Fixture中包含一个scope的关键字可以指定Fixture函数的作用范围,主要用于控制Fixture函数执行前置与执行后置的频率,作用范围分别是function、class、module、session,如果编写的Fixture函数没指定scope默认是function,下面针对不同作用范围详细的阐述下,具体如下。

  • function:函数级别的Fixture在每个测试函数只运行一次。
  • class:类级别的scope不管测试类中有多少个测试方法,都可以共享这个Fixture并且每个测试类只执行一次。
  • module:每个模块只需要执行一次,不管这个模块里面有多少个测试类与测试函数。
  • session:会话级别的Fixture每次会话只需要执行一次,在一个Pytest会话中所有的测试函数(测试方法)都共享这个。

那么涉及到一个问题,在企业级里面使用的时候按那个scope范围来使用了,我的建议是按默认的作用范围来使用就可以了。

Fixture的参数化

使用Fixture也可以实现参数化,在Fixture中使用param关键字获取到需要参数化的数据,Fixture参数化案例代码如下。

代码语言:javascript
复制
#! /usr/bin/env python
# -*- coding:utf-8 -*-
# author:无涯

import  pytest

def add(a,b):
  return a+b

def data():
  return [
  {"x":1,"y":4,"result":5},
  {"x":"wuya","y":"Share","result":"wuyaShare"},
  {"x":1.0,"y":4.0,"result":5.0},
  {"x":1,"y":4.0,"result":5.0},
  {"x":[1,2,3],"y":[4,5,6],"result":[1,2,3,4,5,6]}
]

@pytest.fixture(params=data())
def getData(request):
  return request.param

def test_add_fixture_params(getData):
  assert add(a=getData['x'],b=getData['y'])==getData['result']

如上所示可以看到在getData函数中通过param关键字获取到参数化的数据,然后在测试函数中getData可以依次获取里面的数据,如上代码执行后的结果信息如下所示。

在Pytest测试框架中Fixture函数具备很强大的特性,上面分别从传递测试数据、测试固件、重命名、参数化等方面进行了详细的阐述。掌握这些特性能够解决在企业中自动化测试的事宜。感谢您的阅读。

本文参与?腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-04-16,如有侵权请联系?cloudcommunity@tencent.com 删除

本文分享自 Python自动化测试 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
腾讯云服务器利旧
云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com