1.1 接口测试项目搭建
1.1.1 教育局招生管理系统部署
教育局招生管理系统是基于java+mysql,下面介绍它的部署过程。
1.从我的网盘下载部署文件。
2.安装jdk以及配置环境变量。
点击
文件进行安装。
下一步下一步直接安装。
本人的安装路径是C:\Program Files\Java\jdk1.7.0_17。
安装完成后需要设置环境变量从而使编译器正常使用。右击“计算机”选择“属性”选择左边“高级系统设置”à选择上面“高级”选项卡点击右下角“环境变量”按钮。
接下来弹出的对话框会出现用户变量和系统变量。用户变量对当前登录账户有效,系统变量对所有用户都有效,读者可根据需要设置。
环境变量配置的方法/步骤:
1). 在系统变量里点击新建,变量名填写JAVA_HOME,变量值填写JDK的安装路径,在这里就填写“C:\Program Files\Java\jdk1.7.0_17”。
2). 在系统变量里点击新建变量名填写CLASSPATH,变量值填写。“.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar”,注意不要忘记前面的点和中间的分号。
3). 在系统变量里找到Path变量,这是系统自带的,不用新建。双击Path,由于原来的变量值已经存在,故应在已有的变量后加上“;%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin”。注意前面的分号。
验证的方法:在运行框中输入cmd命令,回车后输入javac,按回车出现以下画面。
3.解压 apache-tomcat-7.0.42 压缩包,把 recruit.students.war 包放到E:\apache-tomcat-7.0.42\webapps 下。
4. 点击 安装mysql 服务端。
设置mysql账户和密码: root /root
默认下一步下一步安装完成。
5.用mysql 客户端工具 navicat 连接mysql ->新建一个recruit_students 库。
6.把 recruit_students_sql 数据文件导入到新建的库中。
7.导完之后,会看到如下图一些表。
8.修改war包下的数据库配置文件:datasource.properties。
E:\apache-tomcat-7.0.42\webapps\recruit.students\WEB-INF\classes 下。
配置jdbc的URL。
9.mysql 数据库用户访问授权。
授权语句:
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'root' WITH GRANT OPTION;
commit;
10.点击E:\apache-tomcat-7.0.42\bin下的startup 启动tomcat.
11.访问教育局招生系统。
访问地址:http://127.0.0.1:8090/recruit.students/login/view。
初始账户和密码: admin /test123
备注:tomcat默认的端口是8080,我用的是8090,是因为修改了tomcat的端口。
17.1.2教育局招生管理系统的功能清单
1.2 项目第一个接口测试用例
教育局招生管理系统登录接口用例实现步骤:
1、打开抓包工具:fiddler。
2、登录教育局招生管理系统。
3、抓取登录http请求。
4、分析登录http请求(请求地址、是否重定向、get请求还是post请求、请求的头信息、请求的response)。
5、数据的处理(处理抓取到的头信息)
6、编写接口代码。
7、人工验证接口测试结果。(后续讲断言)
第一步:下面对fiddler抓取到的数据进行分析。
【请求方法】:带参数的get
【请求地址】:http://127.0.0.1:8090/recruit.students/login/in?account=admin&pwd=660B8D2D5359FF6F94F8D3345698F88C
【请求头信息】:
Host: 127.0.0.1:8090
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://127.0.0.1:8090/recruit.students/login/view
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=AED5BBB1B5E4F9BEDCC3A0FC88668871; userInfoCookie=""
【请求的response】:空
请求的 response 为空是因为登录的时候,做了跳转,状态码为:302,跳转到了
http://127.0.0.1:8090/recruit.students/school/manage/index 这个地址,这个状态码为200。
查看http://127.0.0.1:8090/recruit.students/school/manage/index 这个地址请求的response,返回的是登陆后的信息。
通过分析,我们是清楚的了解到这个接口的情况。
第二步:接着还需要请求头信息的处理,去掉一些没用的请求头信息,保留如下:
"Connection": "keep-alive",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Referer": "http://127.0.0.1:8090/recruit.students/login/view",
【Connection】:如果只是测试登录接口,这个参数可以去掉,如果需要测试登录之后新建学校,那这个头信息就需要保留。
【User-Agent】:模拟用户利用浏览器访问Web网站的真实行为,每个接口都需要。
【Referer】:登录重定向的时候用到。
第三步:最后编写代码实现。
程序实现:
方式一:直接在URL上写完整的URL。
import requests
url="http://127.0.0.1:8090/recruit.students/login/in?account=admin&pwd=660B8D2D5359FF6F94F8D3345698F88C"
#把请求头信息进行处理,去掉一些没用的,保留一些有用头信息。
headers = {
"Connection": "keep-alive",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Referer": "http://127.0.0.1:8090/recruit.students/login/view",
}
# 发送get请求
response = requests.get(url,headers = headers)
# 查看响应内容,response.text 返回的是Unicode格式的数据
print(response.text)
# 查看响应码
print(response.status_code)
运行结果:
方式二:带参数的get请求。
import requests
url="http://127.0.0.1:8090/recruit.students/login/in?"
#把请求头信息进行处理,去掉一些没用的,保留一些有用头信息
headers = {
"Connection": "keep-alive",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Referer": "http://127.0.0.1:8090/recruit.students/login/view",
}
# URL参数
payload = {'account': 'admin','pwd':'660B8D2D5359FF6F94F8D3345698F88C'}
# 发送get请求
response = requests.get(url,headers = headers,params=payload)
# 查看响应内容,response.text 返回的是Unicode格式的数据
print(response.text)
# 查看响应码
print(response.status_code)
运行结果:
方式三:data=payload 的Post请求。
import requests
url="http://127.0.0.1:8090/recruit.students/login/in?"
#把请求头信息进行处理,去掉一些没用的,保留一些有用头信息·
headers = {
"Connection": "keep-alive",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Referer": "http://127.0.0.1:8090/recruit.students/login/view",
}
# URL参数
payload = {'account': 'admin','pwd':'660B8D2D5359FF6F94F8D3345698F88C'}
# 发送Post请求
response = requests.post(url,headers = headers,data=payload)
# 查看响应内容,response.text 返回的是Unicode格式的数据
print(response.text)
# 查看响应码
print(response.status_code)
运行结果:
这3种方式都实现了教育局招生管理系统登录接口用例,我们再打印请求response后的URL
http://127.0.0.1:8090/recruit.students/school/manage/index;jsessionid=13284D7E10CA9E92A443873A59D9E3A1,从这个地址看出,我们登录的时候,接口重定向,跳转到了http://127.0.0.1:8090/recruit.students/school/manage/index 地址,跟我们通过抓包的结果是一样的。
第四步:验证结果。
通过程序运行的结果和抓包的结果对比,验证通过。
1.3 测试用例断言
接口用例的断言设计,客户端需要发送了http请求,服务端返回 response(html内容),就需要我们考虑提取html内容里的元素作为用例的检查点,例如
教育局招生管理系统登录接口用例登录成功后,我们可以设置断言看是否可以从服务端返回 response(html内容)中获取“退出登录”文本。
第一步:首先,我们先发送请求,获取服务端返回 response(html内容)进行分析。
为了更进一步确认,可以把返回的html内容拷贝到 EditPlus 3 下查看。
第二步,编写代码,进行断言判断。
程序实现:
from bs4 import BeautifulSoup
import requests
url="http://127.0.0.1:8090/recruit.students/login/in?"
#把请求头信息进行处理,去掉一些没用的,保留一些有用头信息·
headers = {
"Connection": "keep-alive",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Referer": "http://127.0.0.1:8090/recruit.students/login/view",
}
# URL参数
payload = {'account': 'admin','pwd':'660B8D2D5359FF6F94F8D3345698F88C'}
# 发送get请求
response = requests.get(url,headers = headers,params=payload)
# 查看响应内容,response.text 返回的是Unicode格式的数据
html=response.text
#创建 Beautiful Soup 对象
soup = BeautifulSoup(html,"lxml")
# 获取“退出登录”文本
info =soup.select('.toprighthref')[0].get_text()
#断言,检查客户端发送get请求结果是否符合预期。
try:
assert info == "退出登录"
print('-----pass------')
except ValueError:
print("----fail-----")
运行结果:
这里获取html的文本信息,只是使用前面介绍的BeautifulSoup,大家可以试试用正则表达式或者XPath试试。
备注:http请求的端口,尽量不要用相应的状态码(response.status_code)作为断言,例如:状态码(200),就认为是用例成功,现实中,很多请求,状态码返回 200,实际上是请求失败的。所以用来做断言的条件要准确和唯一。
1.4 接口测试用例的设计
Web接口测试其实可以等同于功能测试,只是被测对象是接口,无界面交互而已;所以用例设计的方法是通用的。
常用的测试方法如下:
1、等价类划分法
2、边界值分析
3、因果图判定法
4、场景分析法
17.4.1接口测试用例设计关注点
1、接口的协议类型(http还是https…)。
2、 接口请求的方法(get/post…)。
3、 参数是否必填。
4、参数间是否存在关联。
5、参数取值范围。
6、业务规则。
17.4.2接口测试用例设计思路
1) 优先级--针对所有接口。
1、暴露在外面的接口,因为通常该接口会给第三方调用。
2、供系统内部调用的核心功能接口。
3、供系统内部调用非核心功能接口。
2) 优先级--针对单个接口。
1、正向用例优先测试,逆向用例次之(通常情况,非绝对)。
2、是否满足前提条件 > 是否携带默认参值参数 > 参数是否必填 > 参数之间是否存在关联 > 参数数据类型限制 >参数数据类型自身的数据范围值限制。
17.4.3接口测试用例设计分析
通常,设计接口测试用例需要考虑以下几个方面:
1、是否满足前提条件
有些接口需要满足前置条件,才可成功获取数据。常见的,需要登陆Token。
逆向用例:
针对是否满足前置条件(假设为n个条件),设计0~n条用例。
2、是否携带默认值参数。
正向用例:
带默认值的参数都不填写、不传参,必填参数都填写正确且存在的“常规”值,其它不填写,设计1条用例。
3、业务规则、功能需求。
这里根据实际情况,结合接口参数说明,可能需要设计n条正向用例和逆向用例
4、参数是否必填。
逆向用例:
针对每个必填参数,都设计1条参数值为空的逆向用例。
5、参数之间是否存在关联。
有些参数彼此之间存在相互制约的关系。
逆向用例:
根据实际情况,可能需要设计0~n条用例。
6、参数数据类型限制。
逆向用例:
针对每个参数都设计1条参数值类型不符的逆向用例。
7、参数数据类型自身的数据范围值限制。
正向用例:
针对所有参数,设计1条每个参数的参数值在数据范围内为最大值的正向用例。
逆向用例:
针对每个参数(假设n个),设计n条每个参数的参数值都超出数据范围最大值的逆向用例。
针对每个参数(假设n个),设计n条每个参数的参数值都小于数据范围最小值的逆向用例。
以上几个方面考虑全的话,基本可以做到如下几个方面的覆盖:
主流程测试用例:正常的主流程功能校验。
分支流测试用例:正常的分支流功能校验。
异常流测试用例:异常容错校验。
17.4.4接口测试用例设计模板
备注:模板会分享到网盘,大家可以从网盘上下载。
1.5 教育局招生管理系统-接口测试用例
1.5.1测试用例1-新增学校
用例步骤:
1、登录教育局招生管理系统
2、新建学校(学校名称:tschool;学校类型:小学;录取学生权限:勾选;备注:create a new school)
3、填写完成,点提交。
通过fiddler抓包。
我们可以收集到接口的相关信息:
【请求方法】:POST
【URL】: http://127.0.0.1:8090/recruit.students/school/manage/addSchoolInfo
【headers】:
{"Connection": "keep-alive",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Referer": "http://127.0.0.1:8090/recruit.students/school/manage/index",}
【data】:
{"schoolName":"t_school",
"listSchoolType[0].id": "2",
"canRecruit":"" 1,
"remark":"create a new school",}
学校创建成功之后,我们可以通过学校列查看新建的数据。
学校列表是另外个接口:http://127.0.0.1:8090/recruit.students/school/manage/schoolInfoList
经过分析,我们就可以写接口测试用例了。
程序实现:
import requests
import json
from urllib import parse
# 第一步:登录教育局招生管理系统
url_login="http://127.0.0.1:8090/recruit.students/login/in?account=admin&pwd=660B8D2D5359FF6F94F8D3345698F88C"
#把请求头信息进行处理,去掉一些没用的,保留一些有用头信息
headers = {
"Connection": "keep-alive",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Referer": "http://127.0.0.1:8090/recruit.students/login/view",
}
# 发送get请求
response = requests.get(url_login ,headers = headers)
# 第一步:登录教育局招生管理系统
url_login="http://127.0.0.1:8090/recruit.students/login/in?account=admin&pwd=660B8D2D5359FF6F94F8D3345698F88C"
#把请求头信息进行处理,去掉一些没用的,保留一些有用头信息·
headers1 = {
"Connection": "keep-alive",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Referer": "http://127.0.0.1:8090/recruit.students/login/view",
}
# 发送get请求
r1 = requests.get(url_login,headers = headers1)
#print(r1.text)
# 新建学校
url_create_school="http://127.0.0.1:8090/recruit.students/school/manage/addSchoolInfo"
#把请求头信息进行处理,去掉一些没用的,保留一些有用头信息·
headers2 = {"Connection": "keep-alive",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Referer": "http://127.0.0.1:8090/recruit.students/school/manage/index",
"X-Requested-With": "XMLHttpRequest",
"Cookie": "JSESSIONID=09CD90A3357DEBD4F3B0F2CF3B387DCA",
}
formdata = {
"schoolName":"t_school2",
"listSchoolType[0][id]":"2",
"canRecruit":"1",
"remark":"create a new school_2",
}
# 通过urlencode()转码
postdata = parse.urlencode(formdata)
print(postdata)
# 创建session对象,可以保存Cookie值。
ssion = requests.session()
# 发送附带用户名和密码的请求,并获取登录后的Cookie值,保存在ssion里。
r2 = ssion.post(url_create_school,headers = headers2,data=postdata)
html = r2.text
print(html)
# 查看响应码
print(r2.status_code)
运行结果:
测试用例2:从学校列表查询学校。
用例步骤:
1、登录教育局招生管理系统
2、从学校列表查询学校(学校名称: t_school)
3、点查询。
用fiddler抓包
代码实现:
import requests
import json
from urllib import parse
import requests
# 第一步:登录教育局招生管理系统
url_login="http://127.0.0.1:8090/recruit.students/login/in?account=admin&pwd=660B8D2D5359FF6F94F8D3345698F88C"
#把请求头信息进行处理,去掉一些没用的,保留一些有用头信息
headers = {
"Connection": "keep-alive",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Referer": "http://127.0.0.1:8090/recruit.students/login/view",
}
# 发送get请求
response = requests.get(url_login ,headers = headers)
# 第一步:登录教育局招生管理系统
url_login="http://127.0.0.1:8090/recruit.students/login/in?account=admin&pwd=660B8D2D5359FF6F94F8D3345698F88C"
#把请求头信息进行处理,去掉一些没用的,保留一些有用头信息·
headers1 = {
"Connection": "keep-alive",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Referer": "http://127.0.0.1:8090/recruit.students/login/view",
}
# 发送get请求
r1 = requests.get(url_login,headers = headers1)
#print(r1.text)
# 查询学校
url_find_school="http://127.0.0.1:8090/recruit.students/school/manage/schoolInfoList"
#把请求头信息进行处理,去掉一些没用的,保留一些有用头信息·
headers2 = {""
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:62.0) Gecko/20100101 Firefox/62.0",
"Accept": "application/json, text/javascript, */*; q=0.01",
"Referer": "http://127.0.0.1:8090/recruit.students/school/manage/index",
"X-Requested-With": "XMLHttpRequest",
"Cookie": "JSESSIONID=09CD90A3357DEBD4F3B0F2CF3B387DCA",
"Connection": "keep-alive",
}
formdata = {
"schoolName":"t_school",
"schoolType":"0",
"page":"1",
"pageSize":"15",
}
# 通过urlencode()转码
postdata = parse.urlencode(formdata)
#打印转码后的数据
print(postdata)
# 创建session对象,可以保存登录Cookie值。
ssion = requests.session()
# 发送附带用户名和密码的请求,并获取登录后的Cookie值,保存在ssion里。
r2 = ssion.post(url_find_school,headers = headers2,data=postdata)
html = r2.text
print(html)
# 查看响应码
print(r2.status_code)
运行结果:
测试用例3:禁用学校。
通过fiddler抓包。
程序实现:
import requests
import json
from urllib import parse
import requests
# 第一步:登录教育局招生管理系统
url_login="http://127.0.0.1:8090/recruit.students/login/in?account=admin&pwd=660B8D2D5359FF6F94F8D3345698F88C"
#把请求头信息进行处理,去掉一些没用的,保留一些有用头信息
headers = {
"Connection": "keep-alive",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99
Safari/537.36",
"Referer": "http://127.0.0.1:8090/recruit.students/login/view",
}
# 发送get请求
response = requests.get(url_login ,headers = headers)
# 第一步:登录教育局招生管理系统
url_login="http://127.0.0.1:8090/recruit.students/login/in?account=admin&pwd=660B8D2D5359FF6F94F8D3345698F88C"
#把请求头信息进行处理,去掉一些没用的,保留一些有用头信息·
headers1 = {
"Connection": "keep-alive",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99
Safari/537.36",
"Referer": "http://127.0.0.1:8090/recruit.students/login/view",
}
# 发送get请求
r1 = requests.get(url_login,headers = headers1)
#print(r1.text)
# 查询学校
url_DisableSchool="http://127.0.0.1:8090/recruit.students/school/manage/enableOrDisableSchool"
#把请求头信息进行处理,去掉一些没用的,保留一些有用头信息·
headers2 = {""
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:62.0) Gecko/20100101 Firefox/62.0",
"Referer": "http://127.0.0.1:8090/recruit.students/school/manage/index",
"Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest",
"Content-Length": "46",
"Cookie": "JSESSIONID=09CD90A3357DEBD4F3B0F2CF3B387DCA",
"Connection": "keep-alive",
}
# 接口的数据类型是json格式
formdata = {"id":"820890","disable":0,"schoolId":"251"}
# 通过urlencode()转码
postdata = parse.urlencode(formdata)
#打印转码后的数据
print(postdata)
# 创建session对象,可以保存登录Cookie值。
ssion = requests.session()
# 发送附带用户名和密码的请求,并获取登录后的Cookie值,保存在ssion里。
r2 = ssion.post(url_DisableSchool,headers = headers2,data=postdata)
html = r2.text
print(html)
# 查看响应码
print(r2.status_code)
运行结果:
测试用例4:启用学校。
程序实现:
import requests
import json
from urllib import parse
import requests
# 第一步:登录教育局招生管理系统
url_login="http://127.0.0.1:8090/recruit.students/login/in?account=admin&pwd=660B8D2D5359FF6F94F8D3345698F88C"
#把请求头信息进行处理,去掉一些没用的,保留一些有用头信息
headers = {
"Connection": "keep-alive",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Referer": "http://127.0.0.1:8090/recruit.students/login/view",
}
# 发送get请求
response = requests.get(url_login ,headers = headers)
# 第一步:登录教育局招生管理系统
url_login="http://127.0.0.1:8090/recruit.students/login/in?account=admin&pwd=660B8D2D5359FF6F94F8D3345698F88C"
#把请求头信息进行处理,去掉一些没用的,保留一些有用头信息·
headers1 = {
"Connection": "keep-alive",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Referer": "http://127.0.0.1:8090/recruit.students/login/view",
}
# 发送get请求
r1 = requests.get(url_login,headers = headers1)
#print(r1.text)
# 启用学校
url_DisableSchool="http://127.0.0.1:8090/recruit.students/school/manage/enableOrDisableSchool"
#把请求头信息进行处理,去掉一些没用的,保留一些有用头信息·
headers2 = {""
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:62.0) Gecko/20100101 Firefox/62.0",
"Referer": "http://127.0.0.1:8090/recruit.students/school/manage/index",
"Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest",
"Cookie": "JSESSIONID=365D9E30F67F88F7301B32AA83C14011",
"Connection": "keep-alive",
}
# 接口的数据类型是json格式
formdata = {"id":"820890","disable":1,"schoolId":"251"}
# 通过urlencode()转码
postdata = parse.urlencode(formdata)
#打印转码后的数据
print(postdata)
# 创建session对象,可以保存登录Cookie值。
ssion = requests.session()
# 发送附带用户名和密码的请求,并获取登录后的Cookie值,保存在ssion里。
r2 = ssion.post(url_DisableSchool,headers = headers2,data=postdata)
html = r2.text
print(html)
# 查看响应码
print(r2.status_code)
运行结果:
备注:这里我就不写全部单个的用例了,用例情况类似,一个一个去实现,同时,这里我也暂时不做断言,后续讲单元测试框架再讲框架自动的断言。