以 QQ 内一个可以随意访问的请求为例:
https://mma.qq.com/mqqactivity/cgi/monitor/report
请求结果:
{"code": 100100}
接下来就针对这个“接口请求”写一个简单的校验脚本(拼接的参数只做参考):
思路:
发出一个接口请求,并对请求结果进行校验,比如请求是否成功,返回值的类型以及内容是否符合预期
原因:
请求失败可能是服务器挂了,也可能是超时或者接口变更;返回值类型如果不对,可能导致客户端崩溃或者无法处理及展示;返回值内容的检查就是比较基础的了,比如和数据库或者通过计算进行判断是否准确、满足需要
import json import requests qq_url = "https://mma.qq.com/mqqactivity/cgi/monitor/report?" qq_para = { "num": "123", "test": "ceshi" } # 参数要使用json格式,所以用json.dumps()进行转换传输 r = requests.get(qq_url, json.dumps(qq_para)) print(r) print(r.text) print(r.json()) code = r.json()["code"] if __name__ == "__main__": assert r.status_code == 200, "response status code is {}".format(r.status_code) assert isinstance(code, int), "code type is {}".format(type(code)) assert code == 100100, "code is {}".format(code)
说明:
1、首先需要使用 pip 命令下载 requests 库,导入 json 和 requests 模块 ;
2、设置俩变量 qq_url 和 qq_para,也可以设置成三个变量:host、path、para ;
3、使用 requests.get 方法发送请求并将响应结果赋值给变量 r ;
4、分别输出 r、r.text、r.json(),也可以把 r.status_code 打印出来;
5、将 r.json()["code"] 的值赋给 code ,然后对响应状态码、响应结果的类型和值进行校验。
运行结果:
更改三个断言中的判断条件,看看断言失败后是否会给出正确的提示信息:
如图,当第一条断言失败后,后面的 case 就不会继续执行,而是直接抛出一个 Traceback ,告诉我们断言失败并输出我们指定的信息内容,这样可以方便我们快速定位问题,知道到底是哪里出现了问题。当然,不写也是 OK 的:
看运行结果,是满足预期的。下面我们就详细的了解下代码中的 requests.get 方法,源码如下:
看注释,就是发送一个 GET 请求,第一个参数是 url ;第二个是参数 params 默认值为None,所以是可选的,可传可不传;第三个参数 **kwargs 是可变长关键字参数,key - value 形式的,也是可选参数;返回的是个响应对象,可以通过句点的方式使用对象中的属性,比如:“.text”字符串形式的响应内容(str)、“.json()”使用 json 处理过的响应数据(dict)、“.status_code”HTTP的请求返回状态,也就是响应状态码(比如:200、404…):
下面就根据上面的简版代码做一版优化:首先,拆分一下,数据或者通用变量都放在一个文件中(qq_data.py),请求放在一个文件中(qq_report.py),响应数据的校验放在一个文件中(test_qq_report.py),这样不管要增加多少个接口,都比较好处理。
qq_data.py
1 """ 2 基础数据 3 """ 4 5 qq_host = "https://mma.qq.com/" 6 qq_path = "mqqactivity/cgi/monitor/report?" 7 qq_para = { 8 "num": "123", 9 "test": "ceshi" 10 }
qq_report.py
1 """ 2 随便校验一个网络请求 3 """ 4 5 import requests 6 import unittest 7 from homework_class import qq_data 8 9 10 class Re(unittest.TestCase): 11 12 def re_url(self, **kwargs): 13 """拼接请求连接""" 14 host = qq_data.qq_host 15 path = qq_data.qq_path 16 if kwargs: 17 para = "" 18 for key, value in kwargs.items(): 19 para += "{}={}&".format(key, value) 20 self.url = host + path + para 21 return self.url[:-1] 22 else: 23 self.url = host + path 24 return self.url 25 26 def re_response(self, **kwargs): 27 """获取响应数据,并校验请求是否成功""" 28 obj = requests.get(self.re_url(**kwargs)) 29 code = obj.status_code 30 self.assertEqual(code, 200, "code is {}, url is {}".format(code, self.re_url(**kwargs))) 31 return obj
test_qq_report.py
1 """ 2 随便校验一个网络请求 3 """ 4 import unittest 5 from homework_class import qq_report, qq_data 6 7 8 class QQReport(unittest.TestCase): 9 10 @classmethod 11 def setUpClass(cls): 12 cls.res = qq_report.Re() 13 cls.obj = cls.res.re_response(**qq_data.qq_para) 14 print(cls.obj) 15 cls.code = cls.obj.json()["code"] 16 17 def test_code_type(self): 18 """检查code类型是否为int类型""" 19 print(type(self.code)) 20 self.assertTrue(isinstance(self.code, int)) 21 22 def test_code_value(self): 23 """检查code是否为100100""" 24 print(self.code) 25 self.assertEqual(self.code, 100100) 26 27 if __name__ == "__main__": 28 unittest.main()
运行一下:
qq_data.py 基础数据部分没啥可说的,后面可以直接添加不同的host或者相同 host 的不同 path 或者不同场景所需要的参数。
qq_report.py 请求处理和响应获取部分,这里面用到了基础数据模块中的数据,也用到了 unittest 模块,因为在 Re 类中继承了 unittest.TestCase 类,在 re_response 方法中使用断言校验了请求是否发送成功(因为如果请求失败了,后面的校验其实也没必要,所以就写在这里了。但是如果多个请求互不影响,其实可以放在test里面做校验,避免一条请求不通过其他的case也跑不了的情况);在 re_url 方法中将 host、path 以及可能存在的参数进行拼接,并将拼接好的url返回回来。
test_qq_report.py 中最先定义了一个 setUpClass 类方法,用 @classmethod 装饰器进行了装饰,所有 case 运行前只运行一次(可以了解下它和 setUp() 方法的区别);后面有两个以 test_ 开头的测试方法,分别校验了响应数据的类型和值是否符合预期。
接下来再补充一些日志,让运行结果看起来更清楚些:
过程:
新建一个 log.py 文件,因为我只想让它在控制台中输出时间、脚本文件名、以及我所需要输出的信息日志,所以需要先导入 logging 模块,建一个 Log 类,在构造方法中创建 logger 并设置日志等级,然后设置日志输出的格式:
1 import inspect 2 import logging 3 4 5 class Log(object): 6 7 def __init__(self, name=None): 8 if name: 9 called_name = name 10 else: 11 called_name = str(inspect.stack()[1][1]).split("/")[-1] 12 self.logger = logging.getLogger(called_name) 13 self.logger.setLevel(logging.DEBUG) 14 # 日志输出格式 15 self.fmt = logging.Formatter(‘[%(asctime)s] - %(name)s -> %(levelname)s: %(message)s‘) 16 17 def log_print(self, level, msg): 18 # 创建一个终端Handler,用于输出到控制台 19 console_sh = logging.StreamHandler() 20 console_sh.setLevel(logging.DEBUG) 21 # fh = logging.FileHandler(‘log.txt‘, mode=‘w‘, encoding=‘UTF-8‘) 22 # fh.setLevel(logging.DEBUG) 23 console_sh.setFormatter(self.fmt) 24 25 self.logger.addHandler(console_sh) 26 if level == "debug": 27 self.logger.debug(msg) 28 elif level == "info": 29 self.logger.info(msg) 30 elif level == "warning": 31 self.logger.warning(msg) 32 elif level == "error": 33 self.logger.error(msg) 34 elif level == "critical": 35 self.logger.critical(msg) 36 self.logger.removeHandler(console_sh) 37 38 def debug(self, msg): 39 self.log_print("debug", msg) 40 41 def info(self, msg): 42 self.log_print("info", msg) 43 44 def warning(self, msg): 45 self.log_print("warning", msg) 46 47 def error(self, msg): 48 self.log_print("error", msg) 49 50 def critical(self, msg): 51 self.log_print("critical", msg)
我们也可以看一下 Formatter 的源码中支持哪些字段的设置:
我这里只用到了 %(asctime)s、%(name)s、%(levelname)s、%(message)s 四个字段,对应的输出样式为:
[2021-07-02 20:18:11,047] - test_qq_report.py -> INFO: ---------- setUpClass ----------
下面的 log_print 方法中先创建一个终端 Handler 并且设置等级和输出格式,然后根据不同的等级调用不同的方法,这里面判断的 5 个等级就是 levelname 对应的等级。之后哪个地方需要输出日志,导入日志模块,创建完实例,调用一下对应等级的方法就可以了。
这里面还要详细说一下为什么它能够输出对应的脚本文件名字,很牛批的…
就是第 11 行代码中用到的 inspect.stack() 获取调用栈,返回的内容是个包含元组对象的列表,代码中 inspect.stack()[1][1] 返回的就是:列表中第二个元组对象,取这个对象中第二个元素的值。这一整行的代码就是将刚刚取出来的值转成字符串类型,使用 “/” 进行分割,然后取最后一部分的内容赋给 called_name 。这样说起来可能比较抽象,所以我把 inspect.stack() 打印出来了,如下:
温故而知新:
- 列表
- 元组
可以尝试输出对应的测试方法名称,如上~~
原文:https://www.cnblogs.com/YH-BeI/p/14965699.html