在单元测试的组件中,主要分为测试用例,测试固件,测试套件,测试执行以及测试报告,看过我书的同学对这些应该很清晰。测试固件也是不难理解,也就是在测试用例执行前需要做的动作和测试执行后需要做的事情。比如在UI的自动化测试中,我们更加关注的是对页面的操作,而不是关心打开浏览器和关闭浏览器,在数据库的操作中,更加关注的是对MySQL的基本操作,而不怎么关心连接数据库和数据库断开连接这部分。所以打开浏览器和关闭浏览器,连接数据库和关闭数据库部分,可以让测试固件去干,测试用例的层面更加关心测试用例的执行结果以及断言结果。在pytest的测试框架中,测试固件有各种形式的表现,比如除了刚才说的初始化与清理外,还有它强大的参数化的部分。下面还是通过具体的案例来说明这部分的应用。
fixture是在测试函数运行前后,由pytest执行的外壳函数。首先来看fixture的函数返回值,也就是返回数值。先看如下的案例代码:
#!/usr/bin/python3
#coding:utf-8
import pytest
@pytest.fixture()
def login():
return 'safghj43567dsafg'
def test_info_setting(login):
'''查看用户个人设置'''
assert login=='safghj43567dsafg'
在如上的函数中,可以看到login的函数返回了token的值,但是它的装饰器是@pytest.fixtuer,在测试函数中,传入login的,也就是函数的形式函数也可以是函数,然后在测试函数中进行断言验证,执行的结果会显示通过,见执行的结果信息:
============================= test session starts ==============================
platform darwin -- Python 3.7.4, pytest-4.0.2, py-1.8.0, pluggy-0.12.0 -- /usr/local/bin/python3.7
cachedir: .pytest_cache
rootdir: /Applications/code/stack/study/xunit/fixtureTest, inifile:
plugins: allure-adaptor-1.7.10
collecting ... collected 1 item
test_001.py::test_info_setting PASSED [100%]
=========================== 1 passed in 0.01 seconds ===========================
Process finished with exit code 0
我们有必要进行断言的验证,打断点来调试程序执行的顺序,在开始之前,先写一个函数的形式参数是函数的案例代码,见如下:
#!/usr/bin/python3
#coding:utf-8
def f1():
print('hello')
def f2(f1):
return f1
f2(f1())
在如上函数执行后,可以看到输出的结果是f1函数的结果,这就是函数的形式参数是函数的案例应用,当然下来是装饰器,关于装饰器我就不详细的介绍了,在博客的其他文章有专门介绍装饰器的文章。现在来看上面的测试函数执行的顺序,见如下:
继续来解释装饰器@pytest.fixture(),它是声明一个函数是fixture,如果测试函数的参数列表中包含了fixture名,那么pytest执行的时候,就会检测到,并且在测试函数运行之前执行该fixture,fixture可以完成任务,也可以返回数据给测试函数。
接着来看另外一个场景,也就是数据的初始化和清理,我们还是依据还是的案例来实战,结合fixture来实战,假设要测试一个接口的查询,前提是我添加数据,这就是初始化干的事,查询完后,要删除数据,这就是清理,来看在fixture的案例应用,先来看被测试的代码,也就是flask
写的api,见如下:
#!/usr/bin/env python
# -*-coding:utf-8 -*-
from flask import Flask,request,jsonify,abort,make_response
from flask_restful import Resource,Api
app=Flask(__name__)
api=Api(app=app)
books=[
{
'id':1,
'author':'无涯',
'name':'Python自动化测试实战',
"done":True
}
]
class BooksApi(Resource):
def get(self):
return jsonify(books)
def post(self):
if not request.json or not 'author' in request.json:
abort(400)
book = {
'id': books[-1]['id'] + 1,
'author': request.json.get('author'),
'name': request.json.get('name'),
'done': False
}
books.append(book)
return jsonify({'status':1002,'msg':'添加成功','datas':book},201)
class BookApi(Resource):
def get(self,book_id):
book=list(filter(lambda t:t['id']==book_id,books))
if len(book)==0:
abort(400)
else:
return jsonify(book)
def delete(self,book_id):
book = list(filter(lambda t: t['id'] == book_id, books))
if len(book)==0:
abort(404)
books.remove(book[0])
return jsonify({'status':1001,'msg':'删除成功'})
api.add_resource(BooksApi,'/v1/api/books',endpoint='/v1/api/books')
api.add_resource(BookApi,'/v1/api/book/<int:book_id>')
if __name__ == '__main__':
app.run(debug=True)
下来结合pytest的fixture来测试刚才说的业务,实现的测试代码如下:
#!/usr/bin/python3
#coding:utf-8
import pytest
import requests
def addBook():
'''添加书籍'''
dict1={"author":"无涯","name":"无涯课堂","done":True}
r=requests.post(url='http://127.0.0.1:5000/v1/api/books',json=dict1)
with open('bookID','w') as f:
f.write(str(r.json()[0]['datas']['id']))
def getBookID():
with open('bookID', 'r') as f:
return f.read()
def delBook():
'''删除书籍'''
r=requests.delete(url='http://127.0.0.1:5000/v1/api/book/{0}'.format(getBookID()))
@pytest.fixture()
def api():
addBook()
yield
delBook()
def test_query_book(api):
r=requests.get(url='http://127.0.0.1:5000/v1/api/book/{0}'.format(getBookID()))
assert r.json()[0]['id']==int(getBookID())