pytest测试实战pdf_Pytest测试实战
Pytest測(cè)試框架是動(dòng)態(tài)語言Python專用的測(cè)試框架,使用起來非常的簡(jiǎn)單,這主要得易于它的設(shè)計(jì),Pytest測(cè)試框架具備強(qiáng)大的功能,豐富的第三方插件,以及可擴(kuò)展性好,可以很好的和unittest測(cè)試框架能夠結(jié)合起來在項(xiàng)目中使用。本文章主要介紹Pytest測(cè)試框架中參數(shù)化的詳細(xì)信息。
參數(shù)化的本質(zhì)是對(duì)列表中的對(duì)象進(jìn)行循環(huán),然后把循環(huán)的對(duì)象進(jìn)行一一的賦值,它的應(yīng)用場(chǎng)景主要是基于相同的業(yè)務(wù)場(chǎng)景,但是需要不同的測(cè)試數(shù)據(jù)來測(cè)試從而達(dá)到最大化的覆蓋更多的業(yè)務(wù)場(chǎng)景和測(cè)試的覆蓋率。理解了這樣的一個(gè)思想之后,我們就以兩個(gè)數(shù)想加作為案例,來演示Pytest測(cè)試框架的參數(shù)化實(shí)際應(yīng)用,另外一點(diǎn)需要特別說的是在Pytest測(cè)試框架中參數(shù)化使用的方式是通過裝飾器的方式來進(jìn)行。剛才也說到它的本質(zhì)是對(duì)列表中的對(duì)象進(jìn)行循環(huán)和賦值,那么這個(gè)對(duì)象可以是列表,也可以是元祖以及和字典數(shù)據(jù)類型,見如下的實(shí)戰(zhàn)案例,把測(cè)試的數(shù)據(jù)分離到不同的對(duì)象中(列表,元組,字典),源碼如下:
執(zhí)行后的結(jié)果信息如下:
在如上的結(jié)果信息中,可以看到真正實(shí)現(xiàn)測(cè)試用例的代碼是很少的,而且把參數(shù)化使用到的數(shù)據(jù)分離到不同的數(shù)據(jù)類型中。
下面結(jié)合API的測(cè)試場(chǎng)景來考慮,被測(cè)試的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='年齡必須為正正數(shù)')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測(cè)試維度的思想,針對(duì)該接口測(cè)試我們不考慮接口的安全性,高并發(fā)以及它的穩(wěn)定性方面,單純的只是從功能層面來考慮進(jìn)行測(cè)試,那么需要針對(duì)每個(gè)參數(shù)是否缺少都得需要進(jìn)行驗(yàn)證,就會(huì)涉及到五個(gè)測(cè)試用例的設(shè)計(jì),我們把數(shù)據(jù)分別分離到主流的文件中,文件的格式主要為JSON,Yaml,Excel和CSV的文件,先來看分離到JSON的文件內(nèi)容:
{"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": "年齡必須為正正數(shù)"}}]},{"request":{"url": "http://localhost:5000/login","body":{"username":"wuya","password":"admin","sex":"男","age":"18"}},"response":[{"age": 18,"password": "admin","sex": "男","username": "wuya"}]}] }涉及到的測(cè)試代碼為:
#!/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"])再來看分離到Y(jié)aml文件的數(shù)據(jù):
--- #用戶名請(qǐng)求為空 "url": "http://localhost:5000/login" "body": '{"password":"admin","sex":"男","age":18}' "expect": '{"message": {"username": "用戶名不能為空"}}' --- #密碼參數(shù)為空 "url": "http://localhost:5000/login" "body": '{"username":"admin","sex":"男","age":18}' "expect": '{"message": {"password": "賬戶密碼不能為空"}}' --- #校驗(yàn)性別參數(shù)的驗(yàn)證 "url": "http://localhost:5000/login" "body": '{"username":"wuya","password":"admin","sex":"asdf","age":18}' expect: '{"message": {"sex": "性別只能是男或者女"}}' --- #校驗(yàn)?zāi)挲g是否是正整數(shù) "url": "http://localhost:5000/login" "body": '{"username":"wuya","password":"admin","sex":"男","age":"rrest"}' "expect": '{"message": {"age": "年齡必須為正正數(shù)"}}' --- #登錄成功 "url": "http://localhost:5000/login" "body": '{"username":"wuya","password":"admin","sex":"男","age":"18"}' "expect": '{"age": 18,"password": "admin","sex": "男","username": "wuya"}'涉及到的測(cè)試代碼為:
#!/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的文件內(nèi)容為:
涉及到的測(cè)試代碼為:
#!/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的文件內(nèi)容:
涉及到的測(cè)試代碼為:
#!/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])其實(shí)我們發(fā)現(xiàn)套路都是一樣的,不管把數(shù)據(jù)分離到什么樣的數(shù)據(jù)格式下,都得符合它的本質(zhì)思想,也就是參數(shù)化的本質(zhì)是對(duì)列表中的對(duì)象進(jìn)行循環(huán)賦值,把握住這樣的一個(gè)思想就可以了。整合上面的所有代碼,完整代碼為:
#!/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])執(zhí)行后的結(jié)果信息為:
Pytest測(cè)試框架最強(qiáng)大的功能除了豐富的第三方插件外,還有就是它的Fixture和共享Fixture的conftest.py,下面具體來看被測(cè)試的接口代碼:
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':"請(qǐng)認(rèn)證"}),403)books=[{'id':1,'author':'wuya','name':'Python接口自動(dòng)化測(cè)試實(shí)戰(zhàn)','done':True},{'id':2,'author':'無涯','name':'Selenium3自動(dòng)化測(cè)試實(shí)戰(zhàn)','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':'請(qǐng)求參數(shù)不是JSON的數(shù)據(jù),請(qǐng)檢查,謝謝!'})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': '請(qǐng)求參數(shù)不是JSON的數(shù)據(jù),請(qǐng)檢查,謝謝!'})elif 'author' not in request.json:return jsonify({'status': 1004, 'msg': '請(qǐng)求參數(shù)author不能為空'})elif 'name' not in request.json:return jsonify({'status': 1005, 'msg': '請(qǐng)求參數(shù)name不能為空'})elif 'done' not in request.json:return jsonify({'status': 1006, 'msg': '請(qǐng)求參數(shù)done不能為空'})elif type(request.json['done'])!=bool:return jsonify({'status': 1007, 'msg': '請(qǐng)求參數(shù)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的方式,首先需要授權(quán),授權(quán)成功后才可以針對(duì)書籍這些接口進(jìn)行操作,如添加刪除以及查看所有的書籍信息,那么獲取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一點(diǎn)需要考慮的是初始化與清理,也就是說在一個(gè)完整的測(cè)試用例中,都必須都得有初始化與清理的部分,這樣才是一個(gè)完整的測(cè)試用例的。Fixture可以很輕松的來解決這部分,還有一點(diǎn)需要說的是Fixture的函數(shù)也可以和返回值整合起來,如添加書籍成功后,把數(shù)據(jù)ID返回來,下面就以查看書籍為案例,那么查看書籍前提是需要添加書籍,這樣可以查看,最后把添加的書籍刪除,這樣一個(gè)測(cè)試用例執(zhí)行完成后才符合它的完整流程,具體測(cè)試代碼如下:
#!/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自動(dòng)化測(cè)試實(shí)戰(zhàn)"},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函數(shù),就是使用了它的初始化與清理的思想,當(dāng)然還可以結(jié)合內(nèi)置的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自動(dòng)化測(cè)試實(shí)戰(zhàn)"},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())針對(duì)Pytest測(cè)試框架的其他知識(shí)體系就在這里不詳細(xì)的說了,感謝您的閱讀和關(guān)注!
Python接口自動(dòng)化測(cè)試實(shí)戰(zhàn) - 網(wǎng)易云課堂?study.163.comPytest測(cè)試實(shí)戰(zhàn) - 網(wǎng)易云課堂?study.163.com總結(jié)
以上是生活随笔為你收集整理的pytest测试实战pdf_Pytest测试实战的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ios 高德挪动地图获取经纬度_高德地图
- 下一篇: libsvm中svmtrain的参数和返