一、接口基础
1、接口:前后端沟通的桥梁,数据传输通道,包括:外部接口,内部接口
2、接口类型:HTTP接口、web Service接口、Restful接口
3、HTTP:超文本传输协议,用户传输html超媒体文档的应用层协议,是在web上进行数据交换的基础,是一种client-server协议
- http支持客户端/服务器模式,客户端发送请求,服务器响应数据,常用的方法:get、post、put、delete
- http协议简单灵活,服务器的程序规模小,通信速度快,对事物处理没有记忆能力,一次请求响应完毕后,就会断开连接,也不保存连接状态,下一次客户端向同样的服务器发起请求时,由于他们已经忘了彼此,需要重新进行连接,限制每次连接只处理一个请求,完成请求和应答后就断开连接,节约传输时间
4、http与https:
- https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
- http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
- http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
- (这个只是默认端口不一样,实际上端口是可以改的)
- http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
5、cookie和session
- cookie存储在客户端,大数据存储在浏览器,session用于控制客户端和服务器的连接,session存储在服务器
- 存储容量不同,cookie有上线,session无上限
- 存储方式不同,cookie有限制,session可以存储任何类型的数据
- 安全性不同,cookie不安全,可以篡改
- 有效期:cookie可以设置长期有校,session如果设置超时时间过长,容易导致内存溢出
- 服务器压力不同,session存储在服务器端,当访问增多,会比较占用服务器的性能
6、cookie应用场景:用户的登录状态,记录用户的习惯,购物车
7、session应用场景:登录验证
8、get、post请求区别:
以get请求为例分析下请求过程
1).浏览器请求tcp连接(第一次握手);
2).服务器答应进行tcp连接(第二次握手);
3).浏览器确认,并发送get请求头和数据(第三次握手,这个报文比较小,所以http 会在此时进行第一次数据发送);
4).服务器返回200OK响应;
- get参数通过url传递,post放在request body中
- get请求再url中传递的参数是有长度限制的,post没有
- get比post更不安全,因为参数直接暴露在url中,所以不能传输敏感信息
- get请求只能进行url编码,而post支持多种编码方式
- get请求参数会被完整的保留在浏览历史记录里,而post中的参数不会被保留
- 对于get请求,浏览器会把 请求头和data一并发出去,服务器响应返回数据
- 对于post请求,浏览器先发送header、服务器响应100,然后继续,浏览器再发送data,服务器响应200,返回数据
9、http请求报文/响应报文的组成:
request:请求行、请求头部、请求数据
response:状态行、消息报文、响应正文
10、常见响应状态码
- 1XX 信息性状态码
- 2XX成功状态码
- 3XX重定向状态码
- 4XX客户端错误状态码
- 5XX服务器错误状态码
11、常见响应头含义
- Allow:服务器支持哪些请求方法(如GET、POST等)。
- Content-Encoding:告诉浏览器,服务器的数据压缩(Encode)格式。实体报头域被使用作媒体类型的修饰符,它的值指示了已经被应用到实体正文的附加内容编码,因而要获得Content- Type报头域中所引用的媒体类型,必须采用相应的解码机制。有在解码之后才可以得到Content-Type头指定的内容类型。利用gzip压缩文档能够显著地减少HTML文档的下载时间。
- Content- Type:告诉浏览器,回送数据的类型。Servlet默认为text/plain,但通常需要显式地指定为text/html。
- Date:当前的GMT时间,例如,Date:Mon,31Dec200104:25:57GMT。Date描述的时间表示世界标准时,换算成本地时间,需要知道用户所在的时区。可以用setDateHeader来设置这个头以避免转换时间格式的麻烦。
- Expires:告诉浏览器把回送的资源缓存多长时间,-1或0则是不缓存。
- Last-Modified:告诉浏览器当前资源缓存最后改动时间。客户可以通过If-Modified-Since请求头提供一个日期,该请求将被视为一个条件GET,只有改动时间迟于指定时间的文档才会返回,否则返回一个304(Not Modified)状态。Last-Modified也可用setDateHeader方法来设置。
- Location:响应报头域用于重定向接受者到一个新的位置,这个头配合302状态码使用,告诉客户端找谁,用于重定向接收者到一个新URI地址。表示客户应当到哪里去提取文档。Location通常不是直接设置的,而是通过HttpServletResponse的sendRedirect方法,该方法同时设置状态代码为302。
- Refresh:告诉浏览器隔多久刷新一次,以秒计。
- Server:服务器通过这个头告诉浏览器服务器的类型。Server响应头包含处理请求的原始服务器的软件信息。此域能包含多个产品标识和注释,产品标识一般按照重要性排序。Servlet一般不设置这个值,而是由Web服务器自己设置。
- Set-Cookie:设置和页面关联的Cookie。Servlet不应使用response.setHeader(“Set-Cookie”, …),而是应使用HttpServletResponse提供的专用方法addCookie。
- Transfer-Encoding:告诉浏览器,传送数据的编码格式。
- WWW-Authenticate:客户应该在Authorization头中提供什么类型的授权信息?在包含401(Unauthorized)状态行的应答中这个头是必需的。
- setContentType:设置Content-Type头。大多数Servlet都要用到这个方法。
- setContentLength:设置Content-Length头。对于支持持久HTTP连接的浏览器来说,这个函数是很有用的。
- addCookie:设置一个Cookie(Servlet API中没有setCookie方法,因为应答往往包含多个Set-Cookie头)。
- Cache-Control:控制浏览器不要缓存数据 no-cache
12、浏览器地址输入url,回车访问会经历怎样的过程?
- 浏览器向DNS服务器请求解析该URL中的域名所对应的IP地址
- 解析出IP地址后,根据该IP地址和默认端口号80,和服务器建立TCP连接
- 浏览器发出读取文件的HTTP请求,该请求报文作为TCP三次握手的第三个报文的数据发给服务器
- 服务器对浏览器请求做出响应,并把对应的html文本发送给浏览器
- 释放tcp连接
- 浏览器将该html文本显示内容
13、接口依赖token的处理思路:
- 抽取登录接口返回值中的token---->jsonpath
- 使用全局变量存储token
- 其它接口将token放入请求头,发送请求
14、接口测试的流程
- 需求评审,熟悉业务和需求
- 开发提供接口文档
- 根据开发交付的接口文档,编写接口测试用例
- 接口用例评审
- 执行接口测试
- 提交接口测试报告
15、常见接口文档的基本组成
- 接口说明
- 调用url
- 请求方法(get、post)
- 请求参数、参数类型、请求参数说明等
- 返回参数说明
16、如何设计接口测试用例
以功能测试角度为例:业务功能:正常场景、异常场景;边界值:必填、组合可选参数、参数有无、null、参数的顺序、个数、类型、参数字符串长短、参数包含特殊字符
其它类型测试:性能测试、安全测试(前后端数据传输是否加密传输)
二、requests模块-get、post请求
1、get请求
语法格式:requests.get(url, params=None, **kwargs)
如:requests.get(url=url, headers=headers, params=params)
- url:请求url地址
- headers:请求头
- params:参数
import requests
url = 'https://api.apiopen.top/getJoke'
data = {
"page": 10,
"count": 5,
"type": "video"
}
res = requests.get(url=url, data=data)
# 获取响应状态码
print(res.status_code)
# 获取响应消息
print(res.content)
# 获取响应数据,文本格式
print(res.text)
# 获取cookie
print(res.cookies)
print(res.json())
2、post请求
1)分析源码
2)什么时候传入data、什么时候传入json?
post请求参数到底是传data还是json,这时候我们要看请求头里的content-type类型
- 不管json是str还是dict,如果不指定headers中的content-type,默认为application/json;
- data为dict时,如果不指定content-type,默认为application/x-www-form-urlencoded,相当于普通form表单提交的形式;
- data为str时,如果不指定content-type,默认为application/json。
3)基本使用方法
res=requests.post(url=url,data=data)
res=requests.post(url=url,json=data,headers=headers)
# 获取响应状态码
print(res.status_code)
# 获取响应消息
print(res.content)
# 获取响应数据,文本格式
print(res.text)
# 获取cookie
print(res.cookies)
print(res.json())
三、cookie、session、token的应用
1、cookie应用
1)、发送登录接口请求,获取cookie
cookies=res.cookies
2)、另一个接口发送请求时携带登录cookie
接口2=requests.post(url=url,json=data,cookies=cookies)
2、session应用
1)创建session会话管理
session=requests.session()
2) 登录接口=session.post(url=url,data=data)
3) 依赖session接口2=session.post(url=url,data=data)
3、token应用
场景:接口2依赖登录接口的返回值token,才能正常请求
1)发送登录接口请求获取token值
token=login_response.json()["token"]
2)第二个接口
headers={“authorization”:token}
接口2请求=requests.get(url=url,headers=headers)
4、小结:如何选token、session、cookie
token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登陆后,服务器生成一个token并将此token返回给客户端,以后客户端只需带上这个token请求数据即可,无需带上用户名和密码,所以对于中小型网站 cookie、session足矣,如果是企业级网站,并需要处理大量请求,token更适合
四、requests请求封装
分析get、post、request源码,总结:不管是get、post还是其它请求类型,最终都是回调request函数
import requests
class RequestHandler:
def __init__(self):
""" session管理器"""
self.session=requests.session()
def visit(self,method,url,params=None,data=None,json=None,headers=None,**kwargs):
return self.session.request(method,url,params=params,data=data,json=json,headers=headers)
def close_session(self):
""" 关闭session"""
self.session.close()
五、Unittest框架
1、单元测试:对于接口测试来讲,单元测试就是将访问接口的过程封装在函数里面,接口测试就变成了单元测试,单元测试是对某个模块,某个类,某个函数进行结果输出后的验证测试,而对于测试来讲,单元测试就是为了执行测试用例,常用框架:unittest、pytest,unittest常用作接口自动化,后者常用作web、app自动化。
2、unittest组成:TestCase(测试用例),TestSuite(测试套件),TestRunner(测试运行器),TestFixture(测试环境数据准备和清理) 。
3、unittest实现思路:
- 导入unittest
- 创建一个测试类,继承unittest.TestCase方法
- 重写setUp和tearDown(非必要)
- 定义以test开头的测试函数,识别测试用例
- 调用unittest.main()方法运行测试用例
- 用例执行后设计,断言比对测试是否通过
- 查看测试结果
4、setUp函数:初始化环境;
tearDown函数:清洗环境
执行顺序是:setUp->testA->tearDown->setUp->testB>tearDown
5、收集测试用例,使用TestSuite()
suite=unittest.TestSuite()
suite.addTest("用例名称")
6、使用TestLoader加载测试用例
1)使用测试类加载用例(loadTestsFromTestCase)
suite=unittest.TestSuit()
loader=unittest.TestLoader()
suite.addTest(loader.loadTestsFromTestCase(测试用例类名1))
2)使用测试类所在模块加载用例
suite=unittest.TestSuit()
loader=unittest.TestLoader()
suite.addTest(loader.loadTestsFromTestCase(测试用例模块名)) #测试用例模块名直接传入
7、生成测试报告:HTMLTestRunner、nnreport->BeautifulReport
report = nnreport.BeautifulReport(f)
report.report(file_title, filename, log_path=".")
8、常用断言方法
序号 | 断言方法 | 断言描述 |
1 | assertEqual(arg1, arg2, msg=None) | 验证arg1=arg2,不等则fail |
2 | assertNotEqual(arg1, arg2, msg=None) | 验证arg1 != arg2, 相等则fail |
3 | assertTrue(expr, msg=None) | 验证expr是true,如果为false,则fail |
4 | assertFalse(expr,msg=None) | 验证expr是false,如果为true,则fail |
5 | assertIs(arg1, arg2, msg=None) | 验证arg1、arg2是同一个对象,不是则fail |
6 | assertIsNot(arg1, arg2, msg=None) | 验证arg1、arg2不是同一个对象,是则fail |
7 | assertIsNone(expr, msg=None) | 验证expr是None,不是则fail |
8 | assertIsNotNone(expr, msg=None) | 验证expr不是None,是则fail |
9 | assertIn(arg1, arg2, msg=None) | 验证arg1是arg2的子串,不是则fail |
10 | assertNotIn(arg1, arg2, msg=None) | 验证arg1不是arg2的子串,是则fail |
11 | assertIsInstance(obj, cls, msg=None) | 验证obj是cls的实例,不是则fail |
12 | assertNotIsInstance(obj, cls, msg=None) | 验证obj不是cls的实例,是则fail |
六、excel数据分离及ddt实现数据驱动
1、openpyxl:一个excel对应一个workbook对象,一个sheet对应一个worksheet对象,而一个单元格对应一个cell对象
2、基本操作
# 读取excel文件
workbook = openpyxl.load_workbook(path)
# 读取所有sheet
sheet = workbook.get_sheet_names()
# 获取某个sheet
sheet = workbook[sheet[0]]
# 获取某个cell的值
cell_val = sheet.cell(row=2, column=2).value
3、封装读取excel方法
from openpyxl import load_workbook
class DoExcel:
def __init__(self,filepath):
self.wb = load_workbook(filepath)
self.sh = self.wb["Sheet1"]
def read_all_casedatas(self):
all_casedatas = []
for row in range(2,self.sh.max_row+1):
case_data ={}
case_data["case_id"] = self.sh.cell(row =row,column =2).value
case_data["url"] = self.sh.cell(row =row,column =3).value
case_data["method"] = self.sh.cell(row =row,column = 4).value
case_data["request_data"] = self.sh.cell(row =row,column =5).value
case_data["expect_data"] = self.sh.cell(row =row,column =6).value
case_data["compare_type"] = self.sh.cell(row =row,column =7).value
all_casedatas.append(case_data)
return all_casedatas
4、ddt简介
- 名称:数据驱动测试
- 作用:由外部数据集合来驱动测试用例的执行
- 核心思想:数据和代码分离
- 应用场景:一组数据来执行相同的操作
- 优点:当测试数据发生大量变化的情况下,测试代码可以保持不变
- 实际应用:excel存储测试数据,ddt读取测试数据到单元测试框架,参数化的应用
5、ddt中的:ddt、data、unpack
- ddt:装饰类
- data:装饰测试方法,参数是一系列的值
- unpack:传递的是复杂的数据结构时使用,比如元组或列表,添加unpack之后,ddt会自动把元组或者列表对应到多个参数上,字典也可以这样处理;当没有加unpack时,方法的参数只能填一个。
6、具体使用举例:
七、接口自动化-配置文件
1、配置文件的几种形式:yaml、py文件、ini文件
2、实例:py形式的配置文件
import os
# abspath获取当前文件所在的绝对路径
BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 存放测试用例
CASE_PATH = os.path.join(BASE_PATH, "testcases")
# 存放测试报告
REPORT_PATH = os.path.join(BASE_PATH, "report")
# 存放日志文件
LOG_PATH = os.path.join(BASE_PATH, "logs")
# 存放测试数据
DATA_PATH = os.path.join(BASE_PATH, "testdatas")
# mysql连接信息
MYSQL_INFO = {
"host": "",
"user": "",
"password": "",
"port": 3306,
"db": "",
"charset": 'utf8',
"autocommit": True,
}
# redis连接信息
REDIS_INFO = {
"host": "",
"password": "",
"port": 6379,
"db": 0,
}
八、pymysql、及redis数据库操作
import pymysql
from AutoApiTest.config import config
import redis
class MysqlUtil:
def __init__(self):
self.__connection = pymysql.connect(**config.MYSQL_INFO)
self.cursor = self.get_mysql_cursor()
# self.cursor = self.__connection.cursor()
def get_mysql_cursor(self):
return self.__connection.cursor()
def execute(self, sql):
config.log.info("执行的sql是%s" % sql)
return self.cursor.execute(sql)
def fetchall(self):
return self.cursor.fetchall()
def fetchone(self):
return self.cursor.fetchone()
def fetchmany(self):
return self.cursor.fetchmany()
def __del__(self):
self.cursor.close()
self.__connection.close()
class RedisUtil:
def __init__(self):
self.redis = redis.Redis(**config.REDIS_INFO)
""
def get(self, key):
return self.redis.get(key)
def set(self, key, value):
return self.redis.set(key, value)
def hget(self, key, field):
return self.redis.hget(key, field)
def hset(self, key, field, value):
return self.redis.hset(key, field, value)
九、日志模块的应用
1、常见日志的级别:
DEBUG
:调试级别(Value=10),打印非常详细的日志信息,通常仅在Debug时使用,如算法中每个循环的中间状态;INFO
:信息级别(Value=20),打印一般的日志信息,突出强调程序的运行过程 ,主要用于处理请求或者状态变化等日常事务;WARNING
:警告级别(Value=30),打印警告日志信息,表明会出现潜在错误的情形,如某些预期之外的情况发生或者在将来可能发生什么,此情况一般不会影响软件的正常实用,如用户登录密码错误;-
ERROR
:错误级别(Value=40),打印错误异常信息,该级别的错误可能会导致系统的一些功能无法正常使用,如IO操作失败或者连接问题; CRITICAL
:严重错误(Value=50),一个严重的错误,导致系统可能无法继续运行,如内存耗尽、磁盘空间为空,一般很少使用;
2、logging模块基本构成
- Logger:日志
- LogRecord:日志记录器,将日志传到相应的处理器处理
- Handler:处理器,将日志记录发送到合适的目的地
- Filter:过滤器,提供更好的粒度控制,可以决定输出哪写日志记录
- Formatter:格式化,指定日志格式输出的布局
3、基本实现步骤:
# 创建一个logger
logger = logging.getLogger('mylogger')
logger.setLevel(logging.DEBUG)
# 创建一个handler,用于写入日志文件
cunTime = time.strftime("%Y-%m-%d %H%M",time.localtime())
fh = RotatingFileHandler(conf_dir.logs_dir+"/Api_Autotest_log_{0}.log".format(cunTime),backupCount=20,encoding='utf-8')
fh.setLevel(logging.DEBUG)
# 再创建一个handler,用于输出到控制台
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
# 定义handler的输出格式
formatter = logging.Formatter('[%(asctime)s][%(thread)d][%(filename)s][line: %(lineno)d][%(levelname)s] ## %(message)s')
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# 给logger添加handler
logger.addHandler(fh)
logger.addHandler(ch)
十、接口自动化--动态数据处理
场景:接口测试用例中需要生成不同的手机号
解决办法:
- 在测试用例中将手机号字段写死{"mobile":“#phone#”,"pwd":123}
- 定义一个动态生成手机号的方法
- 从excel中读取测试用例
- 判断测试用例中是否包含#phone#
- 如果包含,则调用随机生成手机号的方法
- 如果这个手机号存在于数据库中,重新生成
- 如果不存在于库中,则用str.replace(old,new)动态替换数据
十一、接口自动化框架搭建思路
1、基本设计思路:数据驱动+结构分层
数据驱动:将维护数据与代码分离,接口调用行为一致,针对不同的参数组合驱动 不同的测试场景,减少代码冗余
结构分层:数据层+用例层+逻辑层
数据层:测试数据支撑的.xls文件
用例层:用例执行的test_myrequest文件
逻辑层:封装的do_db,do_excel等方法
十二、常见面试题总结
1、平时常见的接口测试产生的bug:常规错误,接口未实现,未按照约定返回结果,边界值处理错误,输入异常值,接口报错,接口明文传输,性能问题等等
2、接口测试如何开展的-->见上文接口测试流程
3、接口测试怎么测->见上文接口测试用例设计方法
4、session和cookie的区别
5、get和post区别
6、测试数据的存放位置
- 对于账号密码,这种管全局的参数,可以用命令行参数,单独抽出来,写的配置文件里(如ini);
- 对于一些一次性消耗的数据,比如注册,每次注册不一样的数,可以用随机函数生成;
- 对于一个接口有多组测试的参数,可以参数化,数据放yaml,text,json,excel都可以;
- 对于可以反复使用的数据,比如订单的各种状态需要造数据的情况,可以放到数据库,每次数据初始化,用完后再清理;
- 对于邮箱配置的一些参数,可以用ini配置文件;
- 对于全部是独立的接口项目,可以用数据驱动方式,用excel/csv管理测试的接口数据;
- 对于少量的静态数据,比如一个接口的测试数据,也就2-3组,可以写到py脚本的开头
格式调整不好了,更规整的版本见石墨文档:https://shimo.im/docs/RWyxv9PkhwKwDwvp