pytest测试实战pdf_Pytest测试实战
Pytest測試框架是動態語言Python專用的測試框架,使用起來非常的簡單,這主要得易于它的設計,Pytest測試框架具備強大的功能,豐富的第三方插件,以及可擴展性好,可以很好的和unittest測試框架能夠結合起來在項目中使用。本文章主要介紹Pytest測試框架中參數化的詳細信息。
參數化的本質是對列表中的對象進行循環,然后把循環的對象進行一一的賦值,它的應用場景主要是基于相同的業務場景,但是需要不同的測試數據來測試從而達到最大化的覆蓋更多的業務場景和測試的覆蓋率。理解了這樣的一個思想之后,我們就以兩個數想加作為案例,來演示Pytest測試框架的參數化實際應用,另外一點需要特別說的是在Pytest測試框架中參數化使用的方式是通過裝飾器的方式來進行。剛才也說到它的本質是對列表中的對象進行循環和賦值,那么這個對象可以是列表,也可以是元祖以及和字典數據類型,見如下的實戰案例,把測試的數據分離到不同的對象中(列表,元組,字典),源碼如下:
執行后的結果信息如下:
在如上的結果信息中,可以看到真正實現測試用例的代碼是很少的,而且把參數化使用到的數據分離到不同的數據類型中。
下面結合API的測試場景來考慮,被測試的API的代碼如下:
#!/usr/bin/env python #!coding:utf-8 from flask import Flask,jsonify from flask_restful import Api,Resource,reqparseapp=Flask(__name__) api=Api(app)class LoginView(Resource):def get(self):return {'status':0,'msg':'ok','data':'this is a login page'}def post(self):parser=reqparse.RequestParser()parser.add_argument('username', type=str, required=True, help='用戶名不能為空')parser.add_argument('password',type=str,required=True,help='賬戶密碼不能為空')parser.add_argument('age',type=int,help='年齡必須為正正數')parser.add_argument('sex',type=str,help='性別只能是男或者女',choices=['女','男'])args=parser.parse_args()return jsonify(args)api.add_resource(LoginView,'/login',endpoint='login')if __name__ == '__main__':app.run(debug=True)在基于API測試維度的思想,針對該接口測試我們不考慮接口的安全性,高并發以及它的穩定性方面,單純的只是從功能層面來考慮進行測試,那么需要針對每個參數是否缺少都得需要進行驗證,就會涉及到五個測試用例的設計,我們把數據分別分離到主流的文件中,文件的格式主要為JSON,Yaml,Excel和CSV的文件,先來看分離到JSON的文件內容:
{"item":[{"request":{"url": "http://localhost:5000/login","body":{"password":"admin","sex":"男","age":18}},"response":[{"message":{"username": "用戶名不能為空"}}]},{"request":{"url": "http://localhost:5000/login","body":{"username":"wuya","sex":"男","age":18}},"response":[{"message":{"password": "賬戶密碼不能為空"}}]},{"request":{"url": "http://localhost:5000/login","body":{"username":"wuya","password":"admin","sex":"asdf","age":18}},"response":[{"message":{"sex": "性別只能是男或者女"}}]},{"request":{"url": "http://localhost:5000/login","body":{"username":"wuya","password":"admin","sex":"男","age":"rrest"}},"response":[{"message":{"age": "年齡必須為正正數"}}]},{"request":{"url": "http://localhost:5000/login","body":{"username":"wuya","password":"admin","sex":"男","age":"18"}},"response":[{"age": 18,"password": "admin","sex": "男","username": "wuya"}]}] }涉及到的測試代碼為:
#!/usr/bin/env python #!coding:utf-8 import pytest import requests import jsondef readJson():return json.load(open('login.json','r'))['item']@pytest.mark.parametrize('data',readJson()) def test_json_login(data):r=requests.post(url=data['request']['url'],json=data['request']['body'])assert r.json()==data['response'][0]if __name__ == '__main__':pytest.main(["-s","-v","test_json_login.py"])再來看分離到Yaml文件的數據:
--- #用戶名請求為空 "url": "http://localhost:5000/login" "body": '{"password":"admin","sex":"男","age":18}' "expect": '{"message": {"username": "用戶名不能為空"}}' --- #密碼參數為空 "url": "http://localhost:5000/login" "body": '{"username":"admin","sex":"男","age":18}' "expect": '{"message": {"password": "賬戶密碼不能為空"}}' --- #校驗性別參數的驗證 "url": "http://localhost:5000/login" "body": '{"username":"wuya","password":"admin","sex":"asdf","age":18}' expect: '{"message": {"sex": "性別只能是男或者女"}}' --- #校驗年齡是否是正整數 "url": "http://localhost:5000/login" "body": '{"username":"wuya","password":"admin","sex":"男","age":"rrest"}' "expect": '{"message": {"age": "年齡必須為正正數"}}' --- #登錄成功 "url": "http://localhost:5000/login" "body": '{"username":"wuya","password":"admin","sex":"男","age":"18"}' "expect": '{"age": 18,"password": "admin","sex": "男","username": "wuya"}'涉及到的測試代碼為:
#!/usr/bin/env python #!coding:utf-8 import pytest import requests import yamldef readYaml():with open('login.yaml','r') as f:return list(yaml.safe_load_all(f))@pytest.mark.parametrize('data',readYaml()) def test_login(data):r=requests.post(url=data['url'],json=json.loads(data['body']))assert r.json()==json.loads(data['expect'])分離到CSV的文件內容為:
涉及到的測試代碼為:
#!/usr/bin/env python #!coding:utf-8 import pytest import requests import csvdef readCsv():data=list()with open('login.csv','r') as f:reader=csv.reader(f)next(reader)for item in reader:data.append(item)return data@pytest.mark.parametrize('data',readCsv()) def test_csv_login(data):r=requests.post(url=data[0],json=json.loads(data[1]))assert r.json()==json.loads(data[2])最后來看分離到Excel的文件內容:
涉及到的測試代碼為:
#!/usr/bin/env python #!coding:utf-8 import pytest import requests import xlrddef readExcel():data=list()book=xlrd.open_workbook('login.xls')sheet=book.sheet_by_index(0)for item in range(1,sheet.nrows):data.append(sheet.row_values(item))return data@pytest.mark.parametrize('data',readExcel()) def test_excel_login(data):r=requests.post(url=data[0],json=json.loads(data[1]))assert r.json()==json.loads(data[2])其實我們發現套路都是一樣的,不管把數據分離到什么樣的數據格式下,都得符合它的本質思想,也就是參數化的本質是對列表中的對象進行循環賦值,把握住這樣的一個思想就可以了。整合上面的所有代碼,完整代碼為:
#!/usr/bin/env python #!coding:utf-8 import pytest import requests import json import yaml import csv import xlrddef readJson():return json.load(open('login.json','r'))['item']def readYaml():with open('login.yaml','r') as f:return list(yaml.safe_load_all(f))def readCsv():data=list()with open('login.csv','r') as f:reader=csv.reader(f)next(reader)for item in reader:data.append(item)return datadef readExcel():data=list()book=xlrd.open_workbook('login.xls')sheet=book.sheet_by_index(0)for item in range(1,sheet.nrows):data.append(sheet.row_values(item))return data@pytest.mark.parametrize('data',readJson()) def test_json_login(data):r=requests.post(url=data['request']['url'],json=data['request']['body'])assert r.json()==data['response'][0]@pytest.mark.parametrize('data',readYaml()) def test_yaml_login(data):r=requests.post(url=data['url'],json=json.loads(data['body']))assert r.json()==json.loads(data['expect'])@pytest.mark.parametrize('data',readCsv()) def test_csv_login(data):r=requests.post(url=data[0],json=json.loads(data[1]))assert r.json()==json.loads(data[2])@pytest.mark.parametrize('data',readExcel()) def test_excel_login(data):r=requests.post(url=data[0],json=json.loads(data[1]))assert r.json()==json.loads(data[2])執行后的結果信息為:
Pytest測試框架最強大的功能除了豐富的第三方插件外,還有就是它的Fixture和共享Fixture的conftest.py,下面具體來看被測試的接口代碼:
from flask import Flask,make_response,jsonify,abort,request from flask_restful import Api,Resource from flask_httpauth import HTTPBasicAuthfrom flask import Flask from flask_jwt import JWT, jwt_required, current_identity from werkzeug.security import safe_str_cmpapp=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 = idself.username = usernameself.password = passworddef __str__(self):return "User(id='%s')" % self.idusers = [User(1, 'wuya', 'asd888'),User(2, 'admin', 'admin'), ]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 userdef identity(payload):user_id = payload['identity']return userid_table.get(user_id, None)jwt = JWT(app, authenticate, identity)class Books(Resource):# decorators = [auth.login_required]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 jsonify({'status':1002,'msg': '添加書籍成功','datas':book}, 201)class Book(Resource):# decorators = [auth.login_required]# 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)我們通過token的方式,首先需要授權,授權成功后才可以針對書籍這些接口進行操作,如添加刪除以及查看所有的書籍信息,那么獲取token這部分的代碼完全可以放在conftest.py里面,具體源碼為:
#!/usr/bin/env python #!coding:utf-8 import requests import pytest@pytest.fixture() def getToken():'''獲取token''' r=requests.post(url='http://localhost:5000/auth',json={"username":"wuya","password":"asd888"})return r.json()['access_token']Fixture一點需要考慮的是初始化與清理,也就是說在一個完整的測試用例中,都必須都得有初始化與清理的部分,這樣才是一個完整的測試用例的。Fixture可以很輕松的來解決這部分,還有一點需要說的是Fixture的函數也可以和返回值整合起來,如添加書籍成功后,把數據ID返回來,下面就以查看書籍為案例,那么查看書籍前提是需要添加書籍,這樣可以查看,最后把添加的書籍刪除,這樣一個測試用例執行完成后才符合它的完整流程,具體測試代碼如下:
#!/usr/bin/env python #!coding:utf-8 import pytest import requestsdef writeBookID(bookID):with open('bookID','w') as f:f.write(str(bookID))@pytest.fixture() def getBookID():with open('bookID','r') as f:return f.read()def addBook(getToken):r=requests.post(url='http://localhost:5000/v1/api/books',json={"author": "無涯","done": False,"name": "Selenium3自動化測試實戰"},headers={'Authorization':'JWT {0}'.format(getToken)})print('添加書籍:n',r.json())writeBookID(r.json()[0]['datas']['id'])def delBook(getToken,getBookID):r=requests.delete(url='http://localhost:5000/v1/api/book/{0}'.format(getBookID),headers={'Authorization':'JWT {0}'.format(getToken)})print('刪除書籍:n',r.json())@pytest.fixture() def init(getToken,getBookID):addBook(getToken)yielddelBook(getToken,getBookID)def test_get_book(init,getToken,getBookID):r=requests.get(url='http://localhost:5000/v1/api/book/{0}'.format(getBookID),headers={'Authorization':'JWT {0}'.format(getToken)})print('查看書籍:n',r.json())在如上的代碼中可以看到,我們刻意了寫了init的Fixture函數,就是使用了它的初始化與清理的思想,當然還可以結合內置的Fixture把代碼改造為如下的部分:
#!/usr/bin/env python #!coding:utf-8 import pytest import requests import allure@allure.step def addBook(getToken):r=requests.post(url='http://localhost:5000/v1/api/books',json={"author": "無涯","done": False,"name": "Selenium3自動化測試實戰"},headers={'Authorization':'JWT {0}'.format(getToken)})print('添加書籍:n',r.json())return r.json()[0]['datas']['id']@allure.step def delBook(getToken,bookID):r=requests.delete(url='http://localhost:5000/v1/api/book/{0}'.format(bookID),headers={'Authorization':'JWT {0}'.format(getToken)})print('刪除書籍:n',r.json())def test_get_book(getToken,tmpdir):f=tmpdir.join('bookid.txt')f.write(addBook(getToken))r=requests.get(url='http://localhost:5000/v1/api/book/{0}'.format(f.read()),headers={'Authorization':'JWT {0}'.format(getToken)})delBook(getToken=getToken,bookID=f.read())print('查看書籍:n',r.json())針對Pytest測試框架的其他知識體系就在這里不詳細的說了,感謝您的閱讀和關注!
Python接口自動化測試實戰 - 網易云課堂?study.163.comPytest測試實戰 - 網易云課堂?study.163.com總結
以上是生活随笔為你收集整理的pytest测试实战pdf_Pytest测试实战的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ios 高德挪动地图获取经纬度_高德地图
- 下一篇: libsvm中svmtrain的参数和返