以 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
qq_report.py
test_qq_report.py 中最先定义了一个 setUpClass 类方法,用 @classmethod 装饰器进行了装饰,所有 case 运行前只运行一次(可以了解下它和 setUp() 方法的区别);后面有两个以 test_ 开头的测试方法,分别校验了响应数据的类型和值是否符合预期。
- unittest回顾
- setUp()方法
- import导入
- 变长参数
- 装饰器
- 继承
- 类
接下来再补充一些日志,让运行结果看起来更清楚些:
过程:
新建一个 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() 打印出来了,如下:
温故而知新:
- 列表 - 元组
可以尝试输出对应的测试方法名称,如上~~