更多功能
har to case
对于不熟悉 Requests 库的人来说,通过Seldom来写接口测试用例还是会有一点难度。于是,seldom 提供了har
文件转 case
的命令。
首先,打开fiddler 工具进行抓包,选中某一个请求。
然后,选择菜单栏:file
-> Export Sessions
-> Selected Sessions...
选择导出的文件格式。
点击next
保存为demo.har
文件。
最后,通过seldom -h2c
转为demo.py
脚本文件。
> seldom -h2c demo.har
2021-06-14 18:05:50 [INFO] Start to generate testcase.
2021-06-14 18:05:50 [INFO] created file: ...\demo.py
demo.py
文件。
import seldom
class TestRequest(seldom.TestCase):
def start(self):
self.url = "http://httpbin.org/post"
def test_case(self):
headers = {"User-Agent": "python-requests/2.25.0", "Accept-Encoding": "gzip, deflate", "Accept": "application/json", "Connection": "keep-alive", "Host": "httpbin.org", "Content-Length": "36", "Origin": "http://httpbin.org", "Content-Type": "application/json", "Cookie": "lang=zh"}
cookies = {"lang": "zh"}
self.post(self.url, json={"key1": "value1", "key2": "value2"}, headers=headers, cookies=cookies)
self.assertStatusCode(200)
if __name__ == '__main__':
seldom.main()
swagger to case
seldom 3.6 版本支持。
seldom 提供了swagger
转 case
的命令。 使用 seldom -s2c
命令。
> seldom -s2c swagger.json
2024-03-04 00:02:22 | INFO | core.py | Start to generate testcase.
2024-03-04 00:02:22 | INFO | core.py | created file: ...\swagger.py
将swagger文档转为 seldom 自动化测试用例。
import seldom
class TestRequest(seldom.TestCase):
def test_pet_petId_uploadImage_api_post(self):
url = f"https://petstore.swagger.io/pet/{petId}/uploadImage"
params = {}
headers = {}
headers["Content-Type"] = "multipart/form-data"
data = {"additionalMetadata": additionalMetadata, "file": file}
r = self.post(url, headers=headers, params=params, data=data)
print(r.status_code)
def test_pet_api_post(self):
url = f"https://petstore.swagger.io/pet"
params = {}
headers = {}
headers["Content-Type"] = "application/json"
data = {}
r = self.post(url, headers=headers, params=params, data=data)
print(r.status_code)
需要注意的是,转换的seldom自动化测试用例有一些变量
,需要用户根据实际情况进行定义。
请求转 cURL
seldom 支持将请求转成cCURL
命令, 你可以方便的通过cURL
命令执行,或者导入到其他接口工具,例如,postman 支持cURL
命令导入。
# test_http.py
import seldom
class TestRequest(seldom.TestCase):
"""
http api test demo
doc: https://requests.readthedocs.io/en/master/
"""
def test_get_curl(self):
"""
test get curl
"""
self.get('http://httpbin.org/get', params={'key': 'value'})
curl = self.curl()
print(curl)
self.post('http://httpbin.org/post', data={'key': 'value'})
curl = self.curl()
print(curl)
# or
r = self.delete('http://httpbin.org/delete', params={'key': 'value'})
curl = self.curl(r.request)
print(curl)
r = self.put('http://httpbin.org/put', json={'key': 'value'}, headers={"token": "123"})
curl = self.curl(r.request)
print(curl)
if __name__ == '__main__':
seldom.main(debug=True)
- 日志结果
> python test_http.py
...
curl -X GET 'Content-Type: application/json' -H 'token: 123' -d '{"key": "value"}' http://httpbin.org/get
curl -X POST 'Content-Type: application/x-www-form-urlencoded' -H -d key=value http://httpbin.org/post
curl -X DELETE 'http://httpbin.org/delete?key=value'
curl -X PUT -H 'Content-Type: application/json' -H 'token: 123' -d '{"key": "value"}' http://httpbin.org/put
接口数据依赖
在场景测试中,我们需要利用上一个接口的数据,调用下一个接口。
- 简单的接口依赖
import seldom
class TestRespData(seldom.TestCase):
def test_data_dependency(self):
"""
Test for interface data dependencies
"""
headers = {"X-Account-Fullname": "bugmaster"}
self.get("/get", headers=headers)
self.assertStatusCode(200)
username = self.response["headers"]["X-Account-Fullname"]
self.post("/post", data={'username': username})
self.assertStatusCode(200)
seldom提供了self.response
用于记录上个接口返回的结果,直接拿来用即可。
- 封装接口依赖
- 创建公共模块
# common.py
from seldom.request import check_response
from seldom.request import HttpRequest
class Common(HttpRequest):
@check_response(
describe="获取登录用户名",
status_code=200,
ret="headers.Account",
check={"headers.Host": "httpbin.org"},
debug=True
)
def get_login_user(self):
"""
调用接口获得用户名
"""
headers = {"Account": "bugmaster"}
r = self.get("http://httpbin.org/get", headers=headers)
return r
if __name__ == '__main__':
c = Common()
c.get_login_user()
- 运行日志
2023-02-14 23:51:48 request.py | DEBUG | Execute get_login_user - args: (<__main__.Common object at 0x0000023263075100>,)
2023-02-14 23:51:48 request.py | DEBUG | Execute get_login_user - kwargs: {}
2023-02-14 23:51:48 request.py | INFO | -------------- Request -----------------[🚀]
2023-02-14 23:51:48 request.py | INFO | [method]: GET [url]: http://httpbin.org/get
2023-02-14 23:51:48 request.py | DEBUG | [headers]:
{
"Account": "bugmaster"
}
2023-02-14 23:51:49 request.py | INFO | -------------- Response ----------------[🛬️]
2023-02-14 23:51:49 request.py | INFO | successful with status 200
2023-02-14 23:51:49 request.py | DEBUG | [type]: json [time]: 0.601097
2023-02-14 23:51:49 request.py | DEBUG | [response]:
{
"args": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Account": "bugmaster",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.28.1",
"X-Amzn-Trace-Id": "Root=1-63ebae14-1e629b132c21f68e23ffeb33"
},
"origin": "173.248.248.88",
"url": "http://httpbin.org/get"
}
2023-02-14 23:51:49 request.py | DEBUG | Execute get_login_user - response:
{'args': {}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Account': 'bugmaster', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.28.1', 'X-Amzn-Trace-Id': 'Root=1-63ebae14-1e629b132c21f68e23ffeb33'}, 'origin': '173.248.248.88', 'url': 'http://httpbin.org/get'}
2023-02-14 23:51:49 request.py | INFO | Execute get_login_user - 获取登录用户名 success!
@check_response
专门用于处理封装的方法。
参数说明:
describe
: 封装方法描述。status_code
: 判断接口返回的 HTTP 状态码,默认200
。ret
: 提取接口返回的字段,参考jmespath
提取规则。check
: 检查接口返回的字段。参考jmespath
提取规则。debug
: 开启debug
,打印更多信息。
- 引用公共模块
import seldom
from common import Common
class TestRequest(seldom.TestCase):
def start(self):
self.c = Common()
def test_case(self):
# 调用 get_login_user() 获取
user = self.c.get_login_user()
self.post("http://httpbin.org/post", data={'username': user})
self.assertStatusCode(200)
if __name__ == '__main__':
seldom.main(debug=True)
Session使用
在实际测试过程中,大部分接口需要登录,Session
是一种非常简单记录登录状态的方式。
import seldom
class TestCase(seldom.TestCase):
def start(self):
self.s = self.Session()
self.s.get('/cookies/set/sessioncookie/123456789')
def test_get_cookie1(self):
self.s.get('/cookies')
def test_get_cookie2(self):
self.s.get('/cookies')
if __name__ == '__main__':
seldom.main(debug=True, base_url="https://httpbin.org")
用法非常简单,你只需要在每个接口之前调用一次登录
, self.s
对象就记录下了登录状态,通过self.s
再去调用其他接口就不需要登录。
提取接口返回数据
当接口返回的数据比较复杂时,我们需要有更方便方式去提取数据,seldom提供 jmespath
、jsonpath
来简化数据提取。
- 接口返回数据
{
"args": {
"hobby": [
"basketball",
"swim"
],
"name": "tom"
},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.25.0",
"X-Amzn-Trace-Id": "Root=1-62851614-1ca9fdb276238c60406c118f"
},
"origin": "113.87.15.99",
"url": "http://httpbin.org/get?name=tom&hobby=basketball&hobby=swim"
}
- 常规提取
import seldom
class TestAPI(seldom.TestCase):
def test_extract_responses(self):
"""
提取 response 数据
"""
payload = {"hobby": ["basketball", "swim"], "name": "tom", "age": "18"}
self.get("http://httpbin.org/get", params=payload)
# response
response1 = self.response["args"]["name"]
response2 = self.response["args"]["hobby"]
response3 = self.response["args"]["hobby"][0]
print(f"response1 --> {response1}")
print(f"response2 --> {response2}")
print(f"response3 --> {response3}")
# jmespath
jmespath1 = self.jmespath("args.name")
jmespath2 = self.jmespath("args.hobby")
jmespath3 = self.jmespath("args.hobby[0]")
jmespath4 = self.jmespath("hobby[0]", response=self.response["args"])
print(f"\njmespath1 --> {jmespath1}")
print(f"jmespath2 --> {jmespath2}")
print(f"jmespath3 --> {jmespath3}")
print(f"jmespath4 --> {jmespath4}")
# jsonpath
jsonpath1 = self.jsonpath("$..name")
jsonpath2 = self.jsonpath("$..hobby")
jsonpath3 = self.jsonpath("$..hobby[0]")
jsonpath4 = self.jsonpath("$..hobby[0]", index=0)
jsonpath5 = self.jsonpath("$..hobby[0]", index=0, response=self.response["args"])
print(f"\njsonpath1 --> {jsonpath1}")
print(f"jsonpath2 --> {jsonpath2}")
print(f"jsonpath3 --> {jsonpath3}")
print(f"jsonpath4 --> {jsonpath4}")
print(f"jsonpath5 --> {jsonpath5}")
...
说明:
response
: 保存接口返回的数据,可以直接以,字典列表的方式提取。jmespath()
: 根据 JMESPath 语法规则,默认提取接口返回的数据,也可指定resposne
数据提取。jsonpath()
: 根据 JsonPath 语法规则,默认提取接口返回的数据,index
指定下标,也可指定resposne
数据提取。
运行结果:
2022-05-19 00:57:08 log.py | DEBUG | [response]:
{'args': {'age': '18', 'hobby': ['basketball', 'swim'], 'name': 'tom'}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.25.0', 'X-Amzn-Trace-Id': 'Root=1-62852563-2fe77d4b1ce544696af60f10'}, 'origin': '113.87.15.99', 'url': 'http://httpbin.org/get?hobby=basketball&hobby=swim&name=tom&age=18'}
response1 --> tom
response2 --> ['basketball', 'swim']
response3 --> basketball
jmespath1 --> tom
jmespath2 --> ['basketball', 'swim']
jmespath3 --> basketball
jmespath4 --> basketball
jsonpath1 --> ['tom']
jsonpath2 --> [['basketball', 'swim']]
jsonpath3 --> ['basketball']
jsonpath4 --> basketball
jsonpath5 --> basketball
运行结果
...
2022-04-10 21:05:17.683 | DEBUG | seldom.logging.log:debug:34 - [response]:
{'args': {'age': '18', 'hobby': ['basketball', 'swim'], 'name': 'tom'}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.25.0', 'X-Amzn-Trace-Id': 'Root=1-6252d60c-551433d744b6869e5d1944d7'}, 'origin': '113.87.12.14', 'url': 'http://httpbin.org/get?hobby=basketball&hobby=swim&name=tom&age=18'}
2022-04-10 21:05:17.686 | DEBUG | seldom.logging.log:debug:34 - [jresponse]:
['basketball']
2022-04-10 21:05:17.689 | DEBUG | seldom.logging.log:debug:34 - [jresponse]:
['18']
genson
通过 assertSchema()
断言时需要写JSON Schema,但是这个写起来需要学习成本,seldom集成了GenSON ,可以帮你自动生成。
- 例子
import seldom
from seldom.utils import genson
class TestAPI(seldom.TestCase):
def test_assert_schema(self):
payload = {"hobby": ["basketball", "swim"], "name": "tom", "age": "18"}
self.get("/get", params=payload)
print("response \n", self.response)
schema = genson(self.response)
print("json Schema \n", schema)
self.assertSchema(schema)
- 运行日志
...
response
{'args': {'age': '18', 'hobby': ['basketball', 'swim'], 'name': 'tom'}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.25.0', 'X-Amzn-Trace-Id': 'Root=1-626574d0-4c04bb7e76a53e8042c9d856'}, 'origin': '173.248.248.88', 'url': 'http://httpbin.org/get?hobby=basketball&hobby=swim&name=tom&age=18'}
json Schema
{'$schema': 'http://json-schema.org/schema#', 'type': 'object', 'properties': {'args': {'type': 'object', 'properties': {'age': {'type': 'string'}, 'hobby': {'type': 'array', 'items': {'type': 'string'}}, 'name': {'type': 'string'}}, 'required': ['age', 'hobby', 'name']}, 'headers': {'type': 'object', 'properties': {'Accept': {'type': 'string'}, 'Accept-Encoding': {'type': 'string'}, 'Host': {'type': 'string'}, 'User-Agent': {'type': 'string'}, 'X-Amzn-Trace-Id': {'type': 'string'}}, 'required': ['Accept', 'Accept-Encoding', 'Host', 'User-Agent', 'X-Amzn-Trace-Id']}, 'origin': {'type': 'string'}, 'url': {'type': 'string'}}, 'required': ['args', 'headers', 'origin', 'url']}
mock URL
seldom 3.2.3 支持
seldom 运行允许通过confrun.py
文件中mock_url()
配置mock URL映射。
- confrun.py
配置要映射的mock URL。
...
def mock_url():
"""
:return:
"""
config = {
"http://httpbin.org/get": "http://127.0.0.1:8000/api/data",
}
return config
- test_api.py
import seldom
class TestRequest(seldom.TestCase):
"""
http api test demo
"""
def test_get_method(self):
payload = {'key1': 'value1', 'key2': 'value2'}
self.get("/get", params=payload)
self.assertStatusCode(200)
if __name__ == '__main__':
seldom.main(base_url="http://httpbin.org")
- 运行
> python test_api.py
2023-07-30 14:47:08 | INFO | request.py | -------------- Request -----------------[🚀]
2023-07-30 14:47:08 | INFO | request.py | [method]: GET [url]: http://httpbin.org/get
2023-07-30 14:47:08 | DEBUG | request.py | [params]:
{
"key1": "value1",
"key2": "value2"
}
2023-07-30 14:47:08 | DEBUG | request.py | mock url: http://127.0.0.1:8000/api/data
2023-07-30 14:47:08 | INFO | request.py | -------------- Response ----------------[🛬️]
2023-07-30 14:47:08 | INFO | request.py | successful with status 200
2023-07-30 14:47:08 | DEBUG | request.py | [type]: json [time]: 0.002738
2023-07-30 14:47:08 | DEBUG | request.py | [response]:
[{'item_name': 'apple'}, {'item_name': 'banana'}, {'item_name': 'orange'}, {'item_name': 'watermelon'}, {'item_name': 'grape'}]
2023-07-30 14:47:08 | INFO | case.py | 👀 assertStatusCode -> 200.
通过日志可以看到 http://httpbin.org/get
替换成为 http://127.0.0.1:8000/api/data
执行。 当你不想mock的时候只需要修改 mock_url() 即可,对于用例来说无影响。
@retry装饰器
@retry()
装饰器用于用法失败充实,例如封装的登录方法,允许API调用失败后再次尝试。
示例如下:
from seldom.request import HttpRequest
from seldom.request import check_response, retry
class LoginAPIObject(HttpRequest):
@retry(times=2, wait=3)
@check_response(ret="form.token")
def user_login(self, username: str, password: str) -> str:
"""
模拟:登录API
"""
params = {"username": username, "token": password}
r = self.post("/error", json=params)
return r
if __name__ == '__main__':
login = LoginAPIObject()
login.user_login("tom", "abc123")
@retry()
装饰器,times
参数指定重试次数,默认3
次,wait
参数指定重试间隔,默认1s
。@retry()
装饰器可以单独使用,也可以和@check_response()
装饰器一起使用,如果一起使用的话,需要在上方。
运行结果:
2024-03-04 22:36:09 | INFO | request.py | -------------- Request -----------------[🚀]
2024-03-04 22:36:09 | INFO | request.py | [method]: POST [url]: /error
2024-03-04 22:36:09 | DEBUG | request.py | [json]:
{
"username": "tom",
"token": "abc123"
}
2024-03-04 22:36:09 | WARNING | request.py | Attempt to execute <user_login> failed with error: 'Invalid URL '/error': No scheme supplied. Perhaps you meant https:///error?'. Attempting retry number 1...
2024-03-04 22:36:12 | INFO | request.py | -------------- Request -----------------[🚀]
2024-03-04 22:36:12 | INFO | request.py | [method]: POST [url]: /error
2024-03-04 22:36:12 | DEBUG | request.py | [json]:
{
"username": "tom",
"token": "abc123"
}
2024-03-04 22:36:12 | WARNING | request.py | Attempt to execute <user_login> failed with error: 'Invalid URL '/error': No scheme supplied. Perhaps you meant https:///error?'. Attempting retry number 2...
2024-03-04 22:36:15 | INFO | request.py | -------------- Request -----------------[🚀]
2024-03-04 22:36:15 | INFO | request.py | [method]: POST [url]: /error
2024-03-04 22:36:15 | DEBUG | request.py | [json]:
{
"username": "tom",
"token": "abc123"
}
Traceback (most recent call last):
File "D:\github\seldom\api\auth_object.py", line 20, in <module>
login.user_login("tom", "abc123")
....
File "C:\Users\fnngj\.virtualenvs\seldom-wKum2rzm\Lib\site-packages\requests\models.py", line 439, in prepare_url
raise MissingSchema(
requests.exceptions.MissingSchema: Invalid URL '/error': No scheme supplied. Perhaps you meant https:///error?
从运行结果可以看到,调用接口重试了2次,如果仍然错误,抛出异常。