目录
一、软件测试基础
1、生命周期和原则
(1)软件的声明周期
- 软件计划与可行性分析
- 需求分析
- 设计
- 编码
- 软件测试
- 运行与维护
(2)软件测试的目的
软件质量保证的一种手段,目的是发现错误以及避免这些错误的发生,使产品达到完美
(3)软件测试阶段
- 制定测试计划
- 制作测试方案
- 单元测试(程序测试,一般由开发人员进行)
- 功能测试
- 性能测试
- 集成测试(子系统测试)
- 系统测试
- 验收测试(产品运营和客户验收)
(4)测试的原则
- 尽早的,持续地进行测试,从需求评审开始需要测试介入
- 测试用例由输入数据和与之对应的输出结果组成,需要包括合理和不合理的输入条件
- 保存测试计划,方案,用例,BUG记录及最终分析报告等文档
2、种类和端
(1)常见的测试种类
# 黑盒测试
在黑盒测试中,被测对象的内部结构和运作情况对测试人员是不可见的,测试人员检查程序功能是否按照规格说明书规定正常使用,是否能接收数据及产生正确的输出信息,并且满足数据库或者外部信息的完整性,也叫功能测试,市场上多数是手工测试,进阶的话就是自动化功能UI测试
# 冒烟测试
对软件的基本功能进行测试,针对每个版本或每次需求变更后,在正式测试前对产品或系统的一次简单的验证性测试,通过后才进行后续的其他测试
# 白盒测试
- 按照程序内部结构,逻辑驱动测试程序,用代码内部的分支,路径,条件,使程序设计的控制结构设计测试用例
- 是一种测试用例设计方法,在这里盒子指的是被测试的软件,顾名思义即盒子是可视的,可以清楚盒子内部的东西以及里面是如何运作的,
- 因此白盒测试需要你对系统内部的结构和工作原理有一个清楚的了解,基本就是审查开发人员的代码
# 自动化测试
- UI自动化测试-Selenium
- 接口自动化测试
# 兼容性测试
- 浏览器兼容
- 手机系统兼容性
- 网络兼容
# 其他
- 负载测试
- 性能-压力测试
- 安全测试
(2)常见测试端
-
PC端网站
-
PC端软件(少)
-
手机端app
-
手机端(H5、小程序...)
-
其他
3、测试用例介绍和用例内容
(1)什么是测试用例
- 测试用例(Test Case)是指对一项特定的软件产品进行测试任务的描述,体现测试方案、方法、技术和策略
- 内容包括测试目标、测试环境、输入数据、测试步骤、预期结果、测试脚本等,最终形成文档
- 测试工作量与测试用例的数量成比例
(2)测试用例的设计方法分类
测试用例的设计方法主要有黑盒测试法和白盒测试法
- 黑盒测试:也称功能测试,黑盒测试着眼于程序外部结构,不考虑内部逻辑结构,主要针对软件界面和软件功能进行测试
- 白盒测试:也称结构测试、透明盒测试、逻辑驱动测试或基于代码的测试。白盒法全面了解程序内部逻辑结构,对所有逻辑路径进行测试
(3)测试用例设计方法
每个测试需求至少编制两个测试用例:
- 一个测试用例用于证明该需求已经满足,通常称作正面测试用例
- 另一个测试用例反映反常或意外的条件或数据,用于论证只有在所需条件下才能够满足该需求,称作负面测试用例
设计原则:从高到低,与功能一一对应,根据需求设计,由有经验的人员设计,编写好后需要进行评审
设计方法:边界值分析法、等价类划分、错误猜测法、因果图法、状态图法、测试大纲法、随机测试、场景法
(4)测试用例记录编写方式
- word文档
- 脑图 xmind(推荐)
(5)测试用例内容
测试用例主要包含内容(各个公司大同小异,看公司和团队规范) 编号、模块、优先级、标题、环境/前置条件、输入数据/动作、预期结果、测试结果、结论、设计和测试人员
4、软件缺陷Bug管理
(1)软件缺陷
软件缺陷即bug,可以是页面缺陷、数据缺陷、逻辑缺陷等
产生原因:项目期限的压力、软件复杂度高、沟通不到位、缺少足够的技术和经验
(2)缺陷管理
目的:加快缺陷的修正、产品的质量评估、预防缺陷和团队技术积累
工具:Jira、禅道、Bugzilla、自研软件等
(3)缺陷报告的内容
- 缺陷编号和标题、描述
- 环境基本信息:操作系统、测试版本、产品和模块
- 缺陷类型
- 缺陷复现步骤
- 缺陷的严重程度
- 缺陷的优先级
- 缺陷的状态
- 缺陷相关人员:提交人,指定解决人,验证人
(4)测试人员基本工作
参与需求评审
编写测试用例
团队测试用例评审
进行测试(看公司和业务选择) 功能 接口 数据 性能 安全 ...
管理和跟进Bug
- 记录bug
- 回归测试
- 跟着项目上线时间点或者里程碑安排
发送测试报告给团队
- 累计bug总量
- 已经修复bug数量
- 现存严重bug数量
上线前发测试报告
- 是否达到上线标准
- 上线时间点
- 相关人员和准备
监控报警
- 业务可用性监控:比如业务宕机了,错误码增加
- 数据监控:访问量波动大、订单成交额异常、优惠券量发放异常
二、Http协议
参考文章:Jmeter5.X性能测试【完整版】-CSDN博客(第一章)
三、接口自动化环境搭建
1、自动化测试
(1)自动化测试分类
-
UI功能自动化
-
接口自动化:接口自动化分为[压力测试自动化、安全测试自动化]
(2)自动化测试技术介绍
# 功能自动化
- selenium:专门做web端的自动化测试工具,可以在 Windows、Linux 和 Mac的 Chrome和 Firefox 中运行;免费,主要做功能测试,也可以做接口自动化测试; 多语言:Java、Python
- appium:自动化测试开源工具,支持 iOS 平台和 Android 平台上的原生应用,web应用和混合应用;跨平台的,可以用在OSX,Windows以及Linux桌面系统;在Python的appium包继承了Selenium
# 接口自动化
第一种:unittest+requests
- unittest:是python自带的测试库,是单元测试框架不仅可以适用于单元测试,还可以适用WEB自动化测试用例的开发与执行,且提供了丰富的断言方法,进阶可以用pytest,但是多数情况下unittest容易入门
- requests:用python语言基于urllib编写的HTTP库,Requests比urllib更加方便,主要是用来发送各类型的http请求,且可以轻松支持代理
第二种:jmeter和postman
- 跨平台,免费的接口测试工具,也可以做接口自动化测试,但不是特别便捷,jmeter更多用于接口压测,postman更多用于接口调试
更多如:Robot Framework、Monkey、Loadrunner等
2、接口自动化测试技术选型
- Python3.7 或 3.8
- unittest:自带
- requests:发送http请求【pip install requests】
- HTMLTestRunner: 用来生成测试报告,用的时候再来安装
- Fiddler:抓包工具
- Pycharm社区版集成开发环境,专业版或者社区版都行
3、测试接口文档地址
大家可以用公司的接口测试例子,或者随意百度一个接口文档进行测试
四、Fiddler抓包工具介绍
1、Fiddler抓包工具介绍和安装
(1)介绍
Fiddler是一个HTTP调试抓包工具,它通过代理的方式获取程序http通讯的数据,可以用其检测网页和服务器的交互数据
(2)核心流程
浏览器->Fiddler->服务器
客户端的所有请求都要先经过Fiddler,然后转发到相应的服务器;反之,服务器端的所有响应,也都会先经过Fiddler然后发送到客户端
(3)安装
官网:Web Debugging Proxy and Troubleshooting Tools|Fiddler
下载完成后直接傻瓜式安装即可
2、Fiddler网络抓包
五、接口自动化之Unittest理解
1、Unittest介绍和快速使用
(1)单元测试理解
单元测试是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义
总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试
- 比如编写的一个函数即为一个单元
- Java里单元指一个类
- 图形化的软件中可以指一个窗口或一个菜单等
- 接口测试里面,一个接口可以作为一个单元
(2)unittest理解
unittest是Python自带的单元测试框,具备编写用例、组织用例、执行用例、输出报告等自动化框架的条件,可以用来作自动化测试框架的用例组织执行框架
unittest框架的特性:
- 提供用例组织与执行:当测试用例只有几条的时候可以不考虑用例的组织,但是当测试用例数量较多时,此时就需要考虑用例的规范与组织问题,unittest单元测试框架就是用来解决这个问题的。
- 提供丰富的断言方法:既然是测试,就有一个预期结果和实际结果的比较问题,比较就是通过断言来实现,unittest单元测试框架提供了丰富的断言方法,通过捕获返回值,并且与预期值进行比较,从而得出测试通过与否。
- 提供丰富的日志:每一个失败用例我们都希望知道失败的原因,所有用例执行结束我们也希望知道整体执行情况,比如总体执行时间,失败用例数,成功用例数。unittest单元测试框架为我们提供了这些数据
unittest单元测试中最核心的四个部分是:
- TestCase(测试用例)
- TestSuite(测试套件)
- TestLoader(测试用例加载器)
- TextTestRunner(执行测试用例)
- TestFixture(测试环境数据准备和清理)
大体流程:编写TestCase,由TestLoader加载TestCase到TestSuite,然后由TextTestRunner来运行TestSuite,最后将运行的结果保存在TextTestResult中
(3)单元测试框架unittest入门
实现思路如下:
- 用import语句引入unittest模块【import unittest】
- 创建一个测试类,测试的类都继承unittest.TestCase方法
- setUp() 测试前的初始化工作;
- tearDown()测试后的清除工作 (在每个测试方法运行时被调用)
- 定义测试函数,函数名以test_开头,以识别测试用例
- 调用unittest.main()方法运行测试用例
- 用例执行后,需要判断用例是Pass还是Fail,可以使用unittest.TestCase模块的断言
注意:断言就是比对预期结果,如果不加断言,则没有结果对比,需要手动去检查运行的结果是否符合预期
(4)小例子
# -*- coding: UTF-8 -*-
import unittest
class UserTestCase(unittest.TestCase):
def setUp(self):
print("\nset up 开始")
def tearDown(self):
print("tearDown 执行结束")
def testCase1(self):
print("test case1")
def testCase2(self):
print("test case2")
if __name__ == '__main__':
unittest.main()
注意:
1、所有类中方法的入参为self,定义方法的变量也要是"self.变量" 2、定义测试用例时,方法必须以“test”开头进行命名,方法的入参为self 3、unittest.main()方法会搜索该模块下所有以test开头的测试用例方法,并自动执行它们 4、自己写的py文件不能用 unittest.py 命名,不然会找不到TestCase
2、Unittest单元测试
-
setUp和tearDown 每次用例执行前都会执行初始化条件和结束条件
-
执行所有用例只运行一次初始化和清理条件,需要使用setUpClass、tearDownClass
@classmethod
def setUpClass(cls):
print("在所有的测试用例执行之前,只执行一次============")
@classmethod
def tearDownClass(cls):
print("在所有的测试用例执行之后,只执行一次============")
- 如果需要跳过某个测试用例,如下
# -*- coding: UTF-8 -*-
import unittest
class UserTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
print("setUpClass 初始化")
@classmethod
def tearDownClass(cls):
print("tearDownClass 资源清理")
def testCase1(self):
print("test case1")
@unittest.skip("跳过testCase2的测试用例")
def testCase2(self):
print("test case2")
def testCase3(self):
print("test case3")
if __name__ == '__main__':
unittest.main()
3、Unitest测试套件TestSuite
(1)前提
测试用例的执行顺序是根据测试用例名称顺序执行的,如果需要自定义顺序,则需要用到unittest.TestSuite() 测试套件了
(2)测试套件
unittest.TestSuite() 测试套件:
- 用来确定测试用例的顺序,哪个先执行哪个后执行
- 如果一个class中有四个test开头的方法,则加载到suite中时则有四个测试用例
- 由TestLoder加载TestCase到TestSuite
- verbosity参数可以控制执行结果的输出,0 是简单报告、1 是一般报告、2 是详细报告 默认1 会在每个成功的用例前面有个“.” 每个失败的用例前面有个 “F”
TestSuite()方法,调用addTest来加载测试用例:类名('方法名')的集合
- addTest() 添加一个测试用例
- addTests([,,]) 添加多个测试用例
(3)示例
# -*- coding: UTF-8 -*-
import unittest
from case.UserTestCase import UserTestCase
class UserTestCase2(unittest.TestCase):
@classmethod
def setUpClass(cls):
print("setUpClass初始化")
@classmethod
def tearDownClass(cls):
print("tearDownClass 资源清理")
def testCase1(self):
print("UserTestCase2 test case1")
def testCase2(self):
print("UserTestCase2 test case2")
self.assertEqual(1, 1)
def testCase3(self):
print("UserTestCase2 test case3")
self.assertEqual(1, 1)
if __name__ == '__main__':
# verbosity 默认是1,为0的话最简洁,不输出每个用例执行结果,2 输出用例的详细执行结果
# unittest.main(verbosity=2)
# 构造一个测试套件
suite = unittest.TestSuite()
# 添加测试用例
# 类名('方法名')的集合
# suite.addTest(UserTestCase2("testCase3"))
# suite.addTest(UserTestCase2("testCase2"))
# suite.addTest(UserTestCase("testCase2"))
# 批量添加
suite.addTests([UserTestCase2("testCase3"), UserTestCase2("testCase2"), UserTestCase("testCase2")])
# 执行测试 TextTestRunner() 文本测试用例运行器,通过该类下面的run()方法来运行suite所组装的测试用例,入参为suite测试套件。
runner = unittest.TextTestRunner()
# run()方法是运行测试套件的测试用例,入参为suite测试套件
runner.run(suite)
4、TestLoader多个文件测试用例批量加载
TestLoader() 用例加载器,我们可以通过把用例都存放在这里,然后再通过Suite进行批量执行,但无法对case进行排序(TestSuite 手工添加)
示例:
# -*- coding: UTF-8 -*-
import unittest
from UserTestCase import UserTestCase
from UserTestCase2 import UserTestCase2
class VideoTestCase(unittest.TestCase):
def setUp(self):
print("set up 开始")
def tearDown(self):
print("tearDown 执行结束")
def testCase1(self):
print("VideoTestCase test case1")
def testCase2(self):
print("VideoTestCase test case2")
self.assertEqual(1, 1)
def testCase3(self):
print("VideoTestCase test case3")
self.assertEqual(1, 2)
if __name__ == '__main__':
# 构造测试套件
suite = unittest.TestSuite()
# 实例化 loader
loader = unittest.TestLoader()
# 加载 UserTestCase 下的全部用例
suite.addTests(loader.loadTestsFromTestCase(UserTestCase))
# 加载 UserTestCase2 下的全部用例
suite.addTests(loader.loadTestsFromTestCase(UserTestCase2))
# 加载 VideoTestCase 下的全部用例
# suite.addTests(loader.loadTestsFromTestCase(VideoTestCase))
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
5、Discover多个文件测试用例批量加载
discover 批量加载文件夹用例
- 参数【case_dir】: 待执行用例的目录
- 参数【pattern】:这个是匹配脚本名称的规则(test*.py意思是匹配test开头的所有脚本)
- 参数【top_level_dir】:这个是顶层目录的名称,一般默认等于None就行了
示例:
# -*- coding: UTF-8 -*-
import unittest
import os
def load_all_case():
"""加载指定路径的全部测试用例"""
# print(os.getcwd())
# 用例路径,case是文件名称
case_path = os.path.join(os.getcwd(), "case")
# print(case_path)
discover = unittest.defaultTestLoader.discover(case_path, pattern="*Case.py", top_level_dir=None)
print(discover)
return discover
if __name__ == '__main__':
runner = unittest.TextTestRunner(verbosity=2)
runner.run(load_all_case())
六、接口自动化之http请求requests理解
1、requests网络请求介绍
(1)什么Request请求库
python需要发起网络请求,在标准库中 urllib2 模块已经包含了平常我们使用的大多数功能,但是它的 API 使用起来让人感觉不太好
因此后续开发了Requests模块,继承了urllib2的所有特性,支持HTTP连接保持和连接池,支持使用cookie保持会话,支持文件上传等,本质就是封装了urllib3
(2)安装
pip install requests
(3)示例
# -*- coding: UTF-8 -*-
import requests
response = requests.get("https://www.baidu.com/")
print(response.text)
注意:包模块命名记得不能用http
2、常见api介绍
(1)响应内容
http状态码 【response.status_code】
使用【response.text】
- Requests 会基于 HTTP 响应的文本编码自动解码响应内容,大多数 Unicode 字符集都能正常解码
使用【response.content】
- 返回的是服务器响应数据的原始二进制字节流,一般用来保存图片等二进制文件
使用 response.json()
示例:
# -*- coding: UTF-8 -*-
import requests
response = requests.get("https://api-v2.xdclass.net/api/rank/v1/hot_product")
# 获取http状态码
print(response.status_code)
print(response.text)
print(response.content)
print(response.json())
(2)get请求带参数示例
# -*- coding: UTF-8 -*-
import requests
data = {"productId": 96}
# https://api-v2.xdclass.net/api/product/v1/detail?productId=96
response = requests.get("https://api-v2.xdclass.net/api/product/v1/detail", data)
# 获取http状态码
print(response.status_code)
print(response.text)
print(response.content)
print(response.json())
(3)post请求带参数示例
# -*- coding: UTF-8 -*-
import requests
# 带参数post请求
data = {"phone": "xxx", "pwd": "xxx"}
response = requests.post("https://api-v2.xdclass.net/api/account/v1/login", json=data)
# 注意点: post提交方式有两个传参方式,针对不同的content-type, 务必要指定接口是哪个类型,表单提交还是json提交
# Content-Type: application/x-www-form-urlencoded
# requests.post("url", data=data)
# Content-Type: application/json
# requests.post("url", json=data)
# 获取http状态码
print(response.status_code)
print(response.text)
print(response.content)
print(response.json())
注意点: post提交方式有两个传参方式,针对不同的content-type, 务必要指定接口是哪个类型,表单提交还是json提交
Content-Type: application/x-www-form-urlencoded
# requests.post("url", data=data)
Content-Type: application/json
# requests.post("url", json=data)
3、添加http header实战
- 请求增加Header信息
# -*- coding: UTF-8 -*-
import requests
# 传值token的请求
params = {
"token": "dcloud-clienteyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ4ZGNsYXNzIiwiaGVhZF9pbWciOiJodHRwczovL2ZpbGUueGRjbGFzcy5uZXQvdXNlcl9maWxlLzIwMjIvZGVmYXVsdC84LmpwZWciLCJhY2NvdW50X2lkIjo2ODMzMzUxLCJ1c2VybmFtZSI6IuacgOW8uue8lueoizU2NzUiLCJyb2xlIjoiRk9SRVZFUl9WSVAiLCJtZWRhbCI6IkZPUkVWRVJfVklQLFRFU1RfT05FX1RPX09ORSIsImlhdCI6MTcyMjE0NjMwNSwiZXhwIjoxNzIyNzUxMTA1fQ.FrZDRI22VapaRFuQl228FVfa0TmPb-RFlfVsK_O7MTo"
}
# 查询登录信息
# response = requests.get("https://api-v2.xdclass.net/api/account/v1/check_token", params=params)
# 查看广告顶图信息
# response = requests.get("https://api-v2.xdclass.net/api/banner/v1/list?location=home_top_ad", params=params)
# 收藏视频
response = requests.post("https://api-v2.xdclass.net/api/product/v1/detail", data={"productId": 97}, params=params)
# 获取http状态码
print(response.status_code)
print(response.text)
print(response.content)
print(response.json())
注意:token会过期,需要抓取最新的
七、接口自动化测试实战
1、通用网络请求工具类封装
(1)背景
每个请求需要做异常捕获,日志记录,协议转换,封装工具方便进行统一维护
我们这里使用request封装通用工具类
(2)新建项目
新建项目,新建util、main、config、case包和requirement.txt
(3)开发
import requests
"""
Http工具类封装
"""
class RequestUtil:
def __init__(self):
pass
def request(self, url, method, headers=None, param=None, content_type=None):
"""
通用请求工具类
:param url:
:param method:
:param headers:
:param param:
:param content_type:
:return:
"""
try:
if method == 'get':
result = requests.get(url=url, params=param, headers=headers).json()
return result
elif method == 'post':
if content_type == 'application/json':
result = requests.post(url=url, json=param, headers=headers).json()
return result
else:
result = requests.post(url=url, data=param, headers=headers).json()
return result
else:
print("http method not allowed")
except Exception as e:
print("http请求报错:{0}".format(e))
if __name__ == '__main__':
# get 请求 ----查询广告顶图
url = "https://api-v2.xdclass.net/api/banner/v1/list?location=home_top_ad"
r = RequestUtil()
result = r.request(url, 'get')
print(result)
# post请求 ----查询收藏的视频
# url = "https://api-v2.xdclass.net/api/product/v1/detail"
# r = RequestUtil()
# data = {"productId": 97}
# headers = {"Content-Type": "application/x-www-form-urlencoded"}
# result = r.request(url, 'post', param=data, headers=headers)
# print(result)
2、网页首页接口测试用例编写
- 新建目录case以及IndexTestCase.py文件
- 开发测试用例,根据项目实际情况可以建立多个用例文件
(1)IndexTestCase.py文件
# coding = utf-8
import unittest
from xd_api_test.util.RequestUtil import RequestUtil
host = "https://api-v2.xdclass.net"
class IndexTestCase(unittest.TestCase):
def testIndexCategoryList(self):
"""
热门课程列表
"""
request = RequestUtil()
url = host + "/api/card/v1/list"
response = request.request(url, 'get')
self.assertEqual(response['code'], 0, "业务状态不正常")
self.assertTrue(len(response['data']) > 0, "热门课程分类列表为空")
def testIndexVideoCard(self):
"""
首页视频卡片
"""
request = RequestUtil()
url = host + "/api/page_item/v1/list?type=HOME_CATEGORY_BOTTOM"
response = request.request(url, 'get')
self.assertEqual(response['code'], 0, "业务状态不正常")
self.assertTrue(len(response['data']) > 0, "视频卡片为空")
video_card_list = response['data']
for card in video_card_list:
self.assertTrue(len(card['title']) > 0, "卡片标题为空 id=" + str(card['id']))
if __name__ == '__main__':
unittest.main(verbosity=2)
(2)UserTestCase.py文件
# coding = utf-8
import unittest
from xd_api_test.util.RequestUtil import RequestUtil
host = "https://api-v2.xdclass.net"
class UserTestCase(unittest.TestCase):
def testIndexCategoryList(self):
"""
收藏视频列表
"""
request = RequestUtil()
url = host + "/api/product/v1/detail"
data = {"productId": 97}
headers = {"Content-Type": "application/x-www-form-urlencoded"}
response = request.request(url, 'post', param=data, headers=headers)
self.assertEqual(response['code'], 0, "业务状态不正常")
self.assertTrue(len(response['data']) > 0, "收藏视频列表为空")
if __name__ == '__main__':
unittest.main(verbosity=2)
3、测试用例加载启动入口开发
编写了测试用例,开发自动加载脚本,【RunMain.py】
# coding = utf-8
import unittest
import os
def load_all_test():
"""
加载全部测试用例
:return:
"""
# 用例路径
case_path = os.path.join(os.getcwd(), "../case")
discover = unittest.defaultTestLoader.discover(case_path, pattern="*Case.py", top_level_dir=None)
return discover
if __name__ == '__main__':
runner = unittest.TextTestRunner(verbosity=2)
runner.run(load_all_test())
用例管理和思考
(1)问题:多个文件,多个测试用例、断言规则、是否需要特定的请求头、用例是否跳过、是否需要登录等 这些是否可以成配置化
(2)解决方案:通用管理用例,做成可配置的形式
(3)用例管理:
- Excel:使用Excel记录,但是操作过滤不方便,测试方案,用例管理不灵活,数据共享不方便,入手简单
- 数据库:数据库记录,操作过滤方便,用例管理灵活,数据共享方便,后续企业可以自研测试平台(前端+后端+数据库),需要有数据库基础