测试基础
1.测试理论及公共部分
1.1软件测试类型(给你一个测试场景如何测试)
web端:
- 功能性测试(Functionality):关注功能是否正确。
- 可用性测试(Usability):关注产品是否好用。
- 兼容性测试(Compatibility):关注产品是否适用多种平台。
- 可靠性测试(Reliability):关注产品是否稳定可靠。
- 安全性测试(Security):关注产品是否存在漏洞。
- 性能测试(Performance):关注产品是否能够高效运行。
app:
- 功能测试
- 兼容性测试:不同的系统:安卓,iso(不同的iso版本) 。不同的手机品牌:小米,华为等等。不同的分辨率,不同的尺寸
- 安装卸载测试
- 在线升级测试(测试点:a.验证数字签名 b.升级后可以正常使用 c.在线跨版本升级)
- 异常测试 主要包含了断网、断电、服务器异常等情况下,客户端能否正常处理,保证数据正常性。
- 交互性测试 客户端作为手机特性测试,包含被打扰的情况13种,来电,来短信,低电量测试等,还要注意手机端硬件上,如:待机,插拔数据线,耳机等操作不会影响客户端。
- 电量与流量测试
- 外网与场景测试 主要是模拟客户使用网络环境,检验客户端程序在实际网络环境中使用情况及进行业务操作。外网测试主要覆盖到wifi\3G\4G、net\wap、电信\移动\联通,所有可能的组合进行测试。
原则:a.尽可能全面覆盖用户的使用场景,测试用例中需要包含不同网络排列组合的各种可能; b.模拟信号被屏蔽时候,客户端的影响等; c.做外部场景测试,在高山、丘陵、火车上等特殊环境下进行全面测试。
1.2.介绍一下你的自动化框架
1.项目是用python+request+unitest来做自动化
2.项目中包含以下文件夹或文件
-
主函数main.py .执行脚本的入口。使用unittest.TestSuite()封装测试套来管理测试用例
-
api文件夹。封装要测试的接口调用
-
data文件夹。存放测试数据
-
report文件夹。使用HTMLrunner生成html格式的测试报告
-
scripts文件夹。存放测试类。使用unitest来进行测试用例的管理。每个文件夹用test开头。每个测试类必须继承unitest.Testcase类。
setup,teardown每个用例执行前必须都会执行一次。classSetup,classTeardown调用每个类的时候执行一次。由于继承了unitest.Testcase类,
每个测试用例方法必须使用test开头。使用ddt来做数据驱动,ddt中使用@ddt.ddt装饰测试类,使用@unpuck和@ddt.date装饰测试用例。
-
conf文件夹。存放测试数据中用到的测试配置文件。使用configparser读取配置文件。configparser读取的配置文件中用[]来表示每个节点,使用;来注释,xxxx=xxxx来表示数据
-
tools文件夹。封装一些公共常用的方法。例如:封装数据库的连接,配置文件的读取。
-
app.py文件用于存放一些关联数据以及一些配置信息。例如:要使项目中的路径统一,在app.py文件中获取当前项目的根路径的绝对路径作为基准路径,其他文件中利用这个基准路径来做拼接。
1.3持续集成如何做的?自动化测试多久构建一次?
答:在我们公司的当中,我们会对IHRM系统进行了持续集成接口测试,用来验证旧版本的质量,同时 还能监控环境的稳定性。
在我们公司里面,使用了jenkins + git + 接口测试代码 + email + publish html report来做持续集成, 使用了到了定时构建、轮巡构建等构建。用来持续地运行和监控版本质量。 一般在我们公司当中,一天构建2次,主要是开发会在上午和下午下班前分别提交一次代码,所以构建2 次。 但是呢,有时候如果要监控环境地稳定性,那么是1小时执行1次。一天24次。
2.UI自动化测试
2.1.selenuium定位元素的方法
# id、name、class_name、tag_name、link_text、partial_link_text、 xpath、 css_selector
1. id、name、class_name:为元素属性定位(如果class有多个属性值,只能使用其中的一个)
2. tag_name:为元素标签名称
3. link_text、partial_link_text:为超链接定位(a标签)
4. XPath:为元素路径定位
5. CSS:为CSS选择器定位
css层级选择器
根据元素的父子关系来选择
格式1:element1>element2 通过element1来定位element2,并且element2必须为element1的直接子
元素
例如1:p[id='p1']>input <定位指定p元素下的直接子元素input>
格式2:element1 element2 通过element1来定位element2,并且element2为element1的后代元素
例如2:p[id='p1'] input <定位指定p元素下的后代元素input>
1. driver.find_element(By.CSS_SELECTOR, '#emailA').send_keys("123@126.com")
2. driver.find_element(By.XPATH, '//*[@id="emailA"]').send_keys('234@qq.com')
3. driver.find_element(By.ID, "userA").send_keys("admin")
4. driver.find_element(By.NAME, "passwordA").send_keys("123456")
5. driver.find_element(By.CLASS_NAME, "telA").send_keys("18611111111")
6. driver.find_element(By.TAG_NAME, 'input').send_keys("123")
7. driver.find_element(By.LINK_TEXT, '访问 新浪 网站').click()
8. driver.find_element(By.PARTIAL_LINK_TEXT, '访问').click()
1 xpath元素定位
1.1 什么是xpath
总结:xpath是用来在xml文件中进行元素定位的标记语言,html是一种特殊的xml,所以xpath也可以用在html中
1.2 Xpath定位策略
- 路径定位
- 属性定位
- 属性与逻辑结合
- 属性与层级结合
定位方法: find_element_by_xpath(xpath) # xpath表达式
按Ctrl+F 可以在搜索框对xpath和css表达式进行校验
1.2.1 路径定位
-
绝对路径 表达式是以 /html开头,元素的层级之间是以 / 分隔
相同层级的元素可以使用下标,下标是从1开始.
需要列出元素所经过的所有层级元素 , 工作当中, 一般不使用绝对路径
例:/html/body/div/fieldset/form/p[1]/input
-
相对路径 匹配任意层级的元素, 是以 //tag_name或者//* 开头
也可以使用下标,下标是从1开始。
例子://p[5]/button
# 导包
import time
from selenium import webdriver
# 创建浏览器驱动对象
driver = webdriver.Chrome()
# 打开测试网站
driver.get("file:///D:/software/UI%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E5%B7%A5%E5%85%B7/web%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B7%A5%E5%85%B7%E9%9B%86%E5%90%88/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 通过xpath的绝对路径定位用户名输入框并输入admin
driver.find_element_by_xpath("/html/body/div/fieldset/form/p/input").send_keys("admin")
# 等待3S
time.sleep(3)
# 通过xapth的相对路径定位密码输入框并输入123
driver.find_element_by_xpath("//form/p[2]/input").send_keys("123")
# 等待3S
time.sleep(3)
# 退出
driver.quit()
1.2.2 元素属性定位
- //*或者//tag_name //*[@attribute='value'] # attribute表示的是元素的属性名,value表示的是元素对应属性值
如果使用class的属性进行元素定位,需要用到class里面所有的值
# 导包
import time
from selenium import webdriver
# 创建浏览器驱动对象
driver = webdriver.Chrome()
# 打开测试网站
driver.get("file:///D:/software/UI%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E5%B7%A5%E5%85%B7/web%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B7%A5%E5%85%B7%E9%9B%86%E5%90%88/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 利用元素的属性信息精确定位用户名输入框,并输入:admin
driver.find_element_by_xpath("//*[@placeholder='请输入用户名']").send_keys("admin")
# 等待3S
time.sleep(3)
# 退出
driver.quit()
1.2.3 属性与逻辑结合定位
- //* 或者//tag_name 开头 //*[@attribute1='value1' and @attribute2='value2']
# 导包
import time
from selenium import webdriver
# 创建浏览器驱动对象
driver = webdriver.Chrome()
# 打开测试网站
driver.get("file:///D:/software/UI%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E5%B7%A5%E5%85%B7/web%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B7%A5%E5%85%B7%E9%9B%86%E5%90%88/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 使用属性与逻辑结合定位策略,在test1对应的输入框里输入:admin
driver.find_element_by_xpath("//input[@name='user' and @class='login']").send_keys("admin")
# 等待3S
time.sleep(3)
# 退出
driver.quit()
1.2.4 属性与层级结合定位
-
是以//*或者//tag_name开头 //p[@id='pa']/input
在任意层给当中,都可以结合属性来使用
# 导包 import time from selenium import webdriver # 创建浏览器驱动对象 driver = webdriver.Chrome() # 打开测试网站 driver.get("file:///D:/software/UI%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E5%B7%A5%E5%85%B7/web%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B7%A5%E5%85%B7%E9%9B%86%E5%90%88/pagetest/%E6%B3%A8%E5%86%8CA.html") # 使用层级与属性结合定位策略,在test1对应的输入框里输入:admin driver.find_element_by_xpath("//p[@id='p1']/input").send_keys("admin") # 等待3S time.sleep(3) # 退出 driver.quit()
1.2.5 XPATH扩展
-
//*[text() = 'value'] value表示的是要定位的元素的全部文本内容.
-
//*[contains(@attribute,'value')] attribute表示的属性名称, value表示的是字符串
要定位的元素中,attribute属性的属性值包含了value的内容。
-
//*[starts-with(@attribute,'value')] attribute表示的属性名称, value表示的是字符串
要定位的元素,attribute属性的属性值是以value开头
2、CSS定位
2.1 什么是CSS
总结:css是可以用来在selenium中定位元素的
CSS定位元素的方法: find_element_by_css_selector(css_selector) # css_selector表示的是CSS选择器表达式
2.2 CSS定位策略
- id选择器
- class选择器
- 元素选择器
- 属性选择器
- 层级选择器
2.2.1 id选择器
- 表达式: #id # 表示通过元素的ID属性进行元素选择 id 表示的的id属性的属性值
2.2.2 class选择器
- 表达式: .class # .表示通过元素的class属性进行元素选择, class表示的class属性的其中一个属性值
# 导包
import time
from selenium import webdriver
# 创建浏览器驱动对象
driver = webdriver.Chrome()
# 打开测试网站
driver.get("file:///D:/software/UI%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E5%B7%A5%E5%85%B7/web%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B7%A5%E5%85%B7%E9%9B%86%E5%90%88/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 通过css的id选择器定位用户名输入框,输入admin
driver.find_element_by_css_selector("#userA").send_keys("admin")
# 通过css的class选择器定位电子邮箱输入框,输入123@qq.com
driver.find_element_by_css_selector(".emailA").send_keys("123@qq.com")
# 等待3S
time.sleep(3)
# 退出
driver.quit()
2.2.3 元素选择器
- 就是通过元素标签名称来选择元素 。表达式: tag_name 不推荐使用
2.2.4 属性选择器
-
就是通过元素的属性来选择元素。 表达式:[attribute='value'] #attribute 表示的是属性名称,value表示的是属性值
如果使用的是class属性,需要带上class的全部属性值
# 导包
import time
from selenium import webdriver
# 创建浏览器驱动对象
driver = webdriver.Chrome()
# 打开测试网站
driver.get("file:///D:/software/UI%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E5%B7%A5%E5%85%B7/web%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B7%A5%E5%85%B7%E9%9B%86%E5%90%88/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 通过css的元素选择器定位用户名输入框,输入admin
driver.find_element_by_css_selector("input").send_keys("admin")
# 通过css的属性选择器定位电子邮箱输入框,输入123@qq.com
driver.find_element_by_css_selector("[class='emailA dzyxA']").send_keys("123@qq.com")
# 等待3S
time.sleep(3)
# 退出
driver.quit()
2.2.5层级选择器
-
父子层级关系选择 器
- 表达式: element1>element2 通过element1来找element2,并且element2是element1的直接子元素
-
隔代层级关系选择器
- 表达式: element1 element2 通过element1来找element2, 并且element2是element1的后代元素
# 导包
import time
from selenium import webdriver
# 创建浏览器驱动对象
driver = webdriver.Chrome()
# 打开测试网站
driver.get("file:///D:/software/UI%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E5%B7%A5%E5%85%B7/web%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B7%A5%E5%85%B7%E9%9B%86%E5%90%88/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 通过css的层级选择器定位用户名输入框输入admin
driver.find_element_by_css_selector(".zc #userA").send_keys("admin")
# 等待3S
time.sleep(3)
# 退出
driver.quit()
2.2.6CSS扩展
-
input[type^='value'] input表示标签名称,type表示属性名称, value表示的文本内容
查找元素type属性值是以value开头的元素
-
input[type$='value'] input表示标签名称,type表示属性名称, value表示的文本内容
查找元素type属性值以value结尾的元素
-
input[type*='value'] input表示标签名称,type表示属性名称, value表示的文本内容
查找元素type属性值包含value的元素
| 定位方式 | xpath | css |
|---|---|---|
| 元素名 | //div | div |
| id | //*[@id="user"] | #user |
| class | //*[@class='telA'] | .telA |
| 属性 | 1. //*[text()="xxx"] 2. //input[starts-with(@attribute,'xxx')] 3. //input[contains(@attribute,'xxx')] |
1. input[type^='p'](以p开头) 2. input[type$='d'] (以d结尾) 3. input[type*='w'] |
| 层级选择器 | //*[@id='p1']/input[1] | 1.p[id='p1']>input[1] 2.p[id='p1'] input |
2.2显示等待和隐式等待的区别
# 隐式等待的代码
driver = webdriver.Edge()
driver.implicitly_wait(9)
# 显示等待的代码
element = WebDriverWait(driver,10,poll_frequency=1).until(lambda x: x.find_element_by_id("userA"))
source.send_keys(Keys.NUMPAD9)
- 作用域:隐式为全局元素,显式等待为单个元素有效
- 使用方法:隐式等待直接通过驱动对象调用,而显式等待方法封装在WebDriverWait类中
- 达到最大超时时长后抛出的异常不同:隐式为NoSuchElementException,显式等待为TimeoutExcepti on
- 隐式等待要等页面所有元素加载完成才会查找元素,而显示等待不需要
2.3下拉选择框、弹出框、滚动条操作
1.下拉框的操作
说明:Select类是Selenium为操作select标签特殊封装的。
实例化对象: select = Select(element) element: 标签对应的元素,通过元素定位方式获取,
例如:driver.find_element_by_id("selectA")
操作方法:
1. select_by_index(index) --> 根据option索引来定位,从0开始
2. select_by_value(value) --> 根据option属性 value值来定位
3. select_by_visible_text(text) --> 根据option显示文本来定位
Select类实现步骤分析
1. 导包 Select类 --> from selenium.webdriver.support.select import Select
2. 实例化Select类 select = Select(driver.find_element_by_id("selectA"))
3. 调用方法:select.select_by_index(index)
示例代码:
from selenium.webdriver.support.select import Select
select = Select(driver.find_element_by_id("selectA"))
select.select_by_index(2) # 根据索引实现
select.select_by_value("sh") # 根据value属性实现
select.select_by_visible_text("A北京") # 根据文本内容实现
2.弹出框的操作
网页中常用的弹出框有三种
1. alert 警告框
2. confirm 确认框
3. prompt 提示框
1. 获取弹出框对象
alert = driver.switch_to.alert
2. 调用
alert.text --> 返回alert/confirm/prompt中的文字信息
alert.accept() --> 接受对话框选项
alert.dismiss() --> 取消对话框选项
示例代码
# 定位alerta按钮
driver.find_element_by_id("alerta").click()
# 获取警告框
alert = driver.switch_to.alert
# 打印警告框文本
print(alert.text)
# 接受警告框
alert.accept()
# 取消警告框
alert.dismiss()
3.滚动条的操作
#左右方向的滚动条可以使用window.scrollTo(左边距,上边距)方法
#example
js="window.scrollTo(200,1000)"
driver.execute_script(js)
2.4鼠标操作
说明:在Selenium中将操作鼠标的方法封装在ActionChains类中
实例化对象:
action = ActionChains(driver)
方法:
1. context_click(element) 右击 --> 模拟鼠标右键点击效果
2. double_click(element) 双击 --> 模拟鼠标双击效果
3. drag_and_drop(source, target) 拖动 --> 模拟鼠标拖动效果
4. move_to_element(element) 悬停 --> 模拟鼠标悬停效果
5. drag_and_drop_by_offset(element) 拖动 -->调用鼠标单元素拖动事件方法
5. perform() 执行 --> 此方法用来执行以上所有鼠标操作
为了更好的学习其他方法,我们先学习perform()执行方法,因为所有的方法都需要执行才能生效
示例代码:
# 模拟鼠标悬停在‘注册’按钮上
element = driver.find_element(By.CSS_SELECTOR, "button")
# 创建鼠标对象
action = ActionChains(driver)
# 调用鼠标悬停事件方法
action.move_to_element(element)
# 调用鼠标执行方法
action.perform()
2.5 键盘操作
* 模拟键盘上面的快捷键的操作
* 调用键盘操作的快捷键的方法 element.send_keys(快捷键的键值)
需要导入Keys类, 第一个字母是大写
单键值: 直接传入对应的键值
组合键: 键值之间由逗号分隔
send_keys(Keys.CONTROL, Keys.SHIFT, 'i')
常用的快捷键
1. send_keys(Keys.BACK_SPACE) 删除键(BackSpace)
2. send_keys(Keys.SPACE) 空格键(Space)
3. send_keys(Keys.TAB) 制表键(Tab)
4. send_keys(Keys.ESCAPE) 回退键(Esc)
5. send_keys(Keys.ENTER) 回车键(Enter)
6. send_keys(Keys.CONTROL,'a') 全选(Ctrl+A)
7. send_keys(Keys.CONTROL,'c') 复制(Ctrl+C)
8. send_keys(Keys.CONTROL, 'v') 粘贴
2.6frame切换、多窗口切换
* frame切换实现方法:
* driver.switch_to.frame(frame_reference) --> 切换到指定frame的方法
frame_reference:可以为frame框架的name、id或者定位到的frame元素
* . driver.switch_to.default_content() --> 恢复默认页面方法
frame切换原理总结:
* 针对同一层级的frame,如果要进行切换的话,需要切回到默认首页
* 针对所要进入的frame, 有多少个层级,就需要切换几次
* 不管当前在哪个层级,如果要回到默认首页,只需要调用一次回到默认首页的方法(driver.switch_to.default_content())
示例代码:
# 针对注册用户a输入用户名adminA
driver.switch_to.frame(driver.find_element(By.ID, "idframe1"))
driver.find_element(By.ID, "AuserA").send_keys("adminA")
# 回到默认首页面
driver.switch_to.default_content()
# 针对注册用户B输入用户名adminB
ele_frame = driver.find_element(By.ID, "idframe2")
driver.switch_to.frame(ele_frame)
driver.find_element(By.ID, "BuserA").send_keys("adminB")
窗口操作的三种方法
获取当前窗口句柄: driver.current_window_handle
获取所有窗口句柄: driver.window_handles 返回的是一个列表
切换窗口句柄: driver.switch_to.window(window_handle) window_handle表示的是要切换到哪个窗口句柄
窗口句柄:由操作系统生成的一串唯 一识别码,是一串字符。
# 获取当前窗口句柄信息
print(driver.current_window_handle)
# 获取所有窗口句柄
windows = driver.window_handles
# 切换窗口句柄
driver.switch_to.window(windows[-1])
3.接口测试
3.1你常用的接口请求方式和区别?
答:常见的接口请求方式有:Get、Post、Put、Delete这四个。
Get:用于查询
Post:用于新增
Put:用于修改
Delete:用于删除
但是,其中Get和Post有显著的区别。
1,Get请求没有请求体,而Post请求一般都有请求体
2,Get请求没有Post请求安全,因为Get请求的数据是放在URL中显示的,可以直接看到数据。 而Post请求是放在请求体,需要抓包才能看到数据。但是他们两个都不是特别安全。真正安全的办法, 是对敏感数据进行加密。
3,Get请求的数据包大小没有Post请求那么大
4,Get请求能支持的编码格式没有Post请求多,例如:Get请求一般不用来传递图片,Post请求可以用 来处理图片。GET请求只能传递ASCII数据,例如中文不是属于ASCII,所有GET请求中需要传递中文 时,需要encode编码数据,把它转换成ASCII码能够支持的数据
3.2发送HTTP请求时,传递参数的途径有哪些?
答:URL中的查询参数和URL的资源路径,请求体,请求头,Cookie
3.3HTTP和HTTPS的区别
1、HTTPS 协议需要到 CA (Certificate Authority,证书颁发机构)申请证书,一般免费证书较少,因而需要一定费用。(以前的网易官网是http,而网易邮箱是 https 。)
2、HTTP 是超文本传输协议,信息是明文传输,HTTPS 则是具有安全性的 SSL 加密传输协议。
3、HTTP 和 HTTPS 使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
4、HTTP 的连接很简单,是无状态的。HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 协议安全。(无状态的意思是其数据包的发送、传输和接收都是相互独立的。无连接的意思是指通信双方都不长久的维持对方的任何信息。)
3.4cookie和session的区别
由于http是无状态,无连接的,但是很多场景中需要保存用户会话过程。
1、数据存放位置不同:
cookie数据存放在客户的浏览器上,session数据放在服务器上。
2、安全程度不同:
cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,考虑到安全应当使用session。
3、性能使用程度不同:
session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie。
4、数据存储大小不同:
单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie,而session则存储与服务端,浏览器对其没有限制。
5、会话机制不同
session会话机制:session会话机制是一种服务器端机制,它使用类似于哈希表(可能还有哈希表)的结构来保存信息。
cookies会话机制:cookie是服务器存储在本地计算机上的小块文本,并随每个请求发送到同一服务器。 Web服务器使用HTTP标头将cookie发送到客户端。在客户端终端,浏览器解析cookie并将其保存为本地文件,该文件自动将来自同一服务器的任何请求绑定到这些cookie。
3.5OSI七层模型
上三层:
- 应用层:获取资源(http协议是在应用层)
- 表示层:资源编码解码
- 会话层:控制资源
下四层:
- 传输层:决定发送UDP,TCP(三次握手和四次挥手在传输层)
- 网络层:IP协议
- 数据链路层:添加MAC地址
- 物理层
TCP/IP模型:应用层(相当于OSI上三层),传输层(TCP传输控制协议,udp用户数据协议),网络层,网络访问层
TCP特点:面向连接(三次握手和四次挥手),基于字节流,可靠
3.6三次握手和四次挥手常见面试题
【问题1】为什么连接的时候是三次握手,关闭的时候却是四次握手?
答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
【问题2】为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。
【问题3】为什么不能用两次握手进行连接?
答:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。
现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。
【问题4】如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
3.7http工作流程
- 客户端输入url发起请求
- 客户端对域名进行解析
- 建立服务器连接(三次握手)
- http发送请求
- 服务端接收请求,服务器进行业务逻辑处理
- 服务器将处理结果发送客户端
- TCP关闭连接(4次挥手)
- 客户端展示内容
3.8给你一个接口,你如何来进行测试
1.正常测试
- 全部必填参数
- 全部参数(必填+非必填)
- 参数组合(必填+部分非必填)
2.异常测试
- 数据异常:长度、类型、是否为空、不满足业务
- 参数异常:多参、少参、无参、错误参数
- 业务异常:关注业务上的异常(有些数据不能重复推)
3.10接口测试流程
首先要自己熟悉一个项目? 功能+接口+自动化+性能是怎么处理?
第1步:需求分析,需求评审
第2步:架构师会输出接口规范; 前后端开发人员根据接口规范编码; 功能测试人员,开始靠想象根据需求设计测试用例; 接口测试人员,根据接口规范设计接口测试用例
第3步:后端开发人员,先提测接口
第4步:接口测试人员,对接口进行测试
第5步:接口测试通过之后,输出接口的测试报告
第6步:功能测试人员,根据接口的测试报告和前端开发的提测,来开始进入功能测试 接口测试人员,整理接口自动化脚本,并且开始在验收环境进行接口的验收测试。
第7步:功能测试人员测试环境测试通过后,进入验收环境进行功能的验收测试 UI自动化小组成员,开始编写回归测试的自动化UI测试的代码(web自动化和app自动 化)
第8步:发布到线上环境,进行最后一轮的功能回归测试。
第9步:跟踪生产BUG
3.11如何判断接口测试的结果(成功或失败)
答:这个问题,要结合实例的案例来进行说明,我举个例子,对注册接口进行测试时,要从3个方面要 校验注册的结果。
1,注册成功后,断言注册接口的返回数据是否与预期一致。
2,注册成功后,还要对比入库的数据,与预期是否一致,如果涉及多个表的操作,操作也要符合预 期。
3,注册成功后,还需要进行业务逻辑校验,如进行登陆操作,判断是否能够使用注册账号登陆成功, (前提条件登陆是正常的)
4.性能测试
4.1性能测试工作流程
1.性能测试需求分析
2.性能测试计划及方案
3.性能测试用例
4.测试脚本的编写
5.建立测试环境
6.执行测试脚本
7.性能测试监控
8.性能分析和调优
9.性能测试报告和总结
4.2并发数的计算
1.通过运营数量来统计(日活量、请求量。。。。)
(1)普通的计算方式: TPS = 总的请求数 / 总的时间 问题:对于同一天的时间内,不同的时间段,请求速率会有波动,这样计算会被平均掉,无法测试负载 高的情况
(2)二八原则: 核心:80%的请求数会集中在20%的时间内完成 TPS = 总的请求数 *80% / 总的时间 * 20 注意:二八原则的计算方法会比平均的计算方式更准确
(3)按照每天的具体业务数据进行计算(稳定性测试TPS) 当获取每天的具体业务统计数据时,就可以统计出业务请求集中的时间段作为有效业务时间;并统计有 效业务时间内的总请求数 TPS = 有效业务时间的总请求数 * 80% / 有效业务时间 * 20%
(4)模拟用户峰值业务操作的并发量:(压力测试TPS) 获取每天的交易峰值的时间段,及这个时间段内的所有请求的数量 TPS = 峰值时间内的请求数/峰值时间段 * 系数 系数可以是:2、3、6、10,由项目组自己觉得要达成的性能指标
4.3性能测试策略
-
基准测试
基准测试: 狭义上讲:也是单用户测试,测试环境确定以后,对业务模型中的重要业务做单独的测试,获取单用户运行时的各项性能指 标。(进行基础的数据采集)
广义上讲:是一种测量和评估软件性能指标的活动。你可以在某个时刻通过基准测试建立一个已知的性能水平(称为基准 线),当系统的软硬件环境发生变化之后再进行一次基准测试以确定那些变化对性能的影响。
基准测试数据的用途:
-
为多用户并发测试和综合场景测试等性能分析提供参考依据
-
识别系统或环境的配置变更对性能响应带来的影响
-
为系统优化前后的性能提升/下降提供参考指标
-
-
负载测试
说明:通过逐步增加系统负载,测试系统性能的变化,并最终确定在满足系统的性能指标情况下,系统所能够承受的最大负载量 的测试。
负载:指向服务器发送的请求数量,请求越多,负载越高
注意:负载测试关注的重点是逐步增加压力
-
稳定性测试
说明:稳定性测试是指,在服务器稳定运行(用户正常的业务负载下)的情况下进行长时间测试,并最终保证服务器能满足线上 业务需求。时长一般为1天、一周等。
-
其他:并发测试、压力测试、容量测试等
性能测试中,测试策略其实有很多种,但是掌握基础的用法后,其他不同名称的测试策略只是基础用法的一个变形用法。
并发测试:并发测试是指在极短的时间内,发送多个请求,来验证服务器对并发的处理能力。如:抢红包、抢购、秒杀活动 等。
压力测试:压力测试是在强负载(大数据量、大量并发用户等)下的测试,查看应用系统在峰值使用情况下操作行为,从而 有效地发现系统的某项功能隐患、系统是否具有良好的容错能力和可恢复能力。压力测试分为高负载下的长时间(如24小时 以上)的稳定性压力测试和极限负载情况下导致系统崩溃的破坏性压力测试。
容量测试:关注软件的极限压力下的各个极限参数值,例如:最大TPS,最大连接数,最大并发数,最多数据条数等。
5.数据库
5.1.Oracle数据库和Mysql数据库的区别
1.两者都是关系型数据库
2.Oracle是大型数据库而Mysql是中小型数据库,Oracle是商业型数据库,oraclemysql是开源的数据库。
3.MYSQL里可以用双引号包起字符串,ORACLE里只可以用单引号包起字符串。在插入和修改字符串前必须做单引号的替换:把所有出现的一个单引号替换成两个单引号。
4.oracle默认端口号是1521,mysql默认端口号是3306
1.复制某张表的数据到另外一张表
create table new_table_name as select * from old_table_name
2.将备份表数据插入到原表中
insert into old_table_name select * from new_table_name
3.oracle数据库排序
select * ,ROW_NUMBER() OVER(partition BY 字段名 order by 字段名) NUM_TOTAL FROM table_name where NUM_TOTAL > num
4.求和排序取前200名时,第200名有多条数据
sum(a) OVER(partition BY 字段名 order by 字段名 rows between unbounded preceding and current row)
5.取第一个值或最后一个值first_value或last_value(ROWS BETWEEN preceding and unbounded following)
select * ,first_value(字段名) OVER(partition BY 字段名 order by 字段名) NUM_TOTAL FROM table_name where NUM_TOTAL > num
6.创建dblink
create database link 'link_name' connect to 'username' identified by 'password' using
"hostname:port/servie_name"
7.查询dblink其他数据库的数据
select* from tablename@link_name
8.比较两张表数据不同
select * from A minus select * from B
9.同时执行多个sql的临时表
/*+leading(c) index */
10.基本函数 /*oracle语法 */
nvl(a,b) 判断是否为空
decode(a,b,c,d) 如果a和b相等取c,否则取d
11.字符的拼接
a||b
5.2 常用SQL
1.union 和 union all 的区别
union 会去重,union all 不会去重
5.3 窗口函数
1.窗口函数语法
<窗口函数> over (partition by <用于分组的列名>
order by <用于排序的列名>
rows/range子句<用于定义窗口大小> )
<窗口函数>可以放以下两种函数:
1) 专用窗口函数,包括后面要讲到的rank, dense_rank, row_number等专用窗口函数。
2) 聚合函数,如sum. avg, count, max, min等
1专用窗口函数
rank, dense_rank, row_number三个函数
- Rank:有相同名次,名次按实际个数走,会跳数字。
- Dense_rank: 有相同名次,名次不跳数
- Row_number:相同分数按行数排序
| 分数 | rank | dense_rank | row_number |
|---|---|---|---|
| 100 | 1 | 1 | 1 |
| 100 | 1 | 1 | 2 |
| 80 | 3 | 2 | 3 |
2.聚合函数
//创建表
DROP TABLE IF EXISTS tb_user_video_log, tb_video_info;
CREATE TABLE tb_user_video_log (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '自增ID',
uid INT NOT NULL COMMENT '用户ID',
video_id INT NOT NULL COMMENT '视频ID',
start_time datetime COMMENT '开始观看时间',
end_time datetime COMMENT '结束观看时间',
if_follow TINYINT COMMENT '是否关注',
if_like TINYINT COMMENT '是否点赞',
if_retweet TINYINT COMMENT '是否转发',
comment_id INT COMMENT '评论ID'
) CHARACTER SET utf8 COLLATE utf8_bin;
// 插入数据
INSERT INTO tb_user_video_log(uid, video_id, start_time, end_time, if_follow, if_like, if_retweet, comment_id) VALUES
(101, 2001, '2021-09-01 10:00:00', '2021-09-01 10:00:20', 0, 1, 1, null)
,(105, 2002, '2021-09-10 11:00:00', '2021-09-10 11:00:30', 1, 0, 1, null)
,(101, 2001, '2021-10-01 10:00:00', '2021-10-01 10:00:20', 1, 1, 1, null)
,(102, 2001, '2021-10-01 10:00:00', '2021-10-01 10:00:15', 0, 0, 1, null)
,(103, 2001, '2021-10-01 11:00:50', '2021-10-01 11:01:15', 1, 1, 0, 1732526)
,(106, 2002, '2021-10-01 10:59:05', '2021-10-01 11:00:05', 2, 0, 0, null);
1.查询每个视频的总点赞数
select video_id ,sum(if_like)over(partition by video_id) coun
from tb_user_video_log;
2.查询每个用户的查看结束时间排名
select video_id ,uid,end_time,rank()over(partition by video_id order by end_time ) 'rank'
from tb_user_video_log;
3.滑动窗口:rows&range用法
[<ROWS or RANGE clause> BETWEEN <Start expr> AND <End expr>]
- ROWS: 表示按照行的范围进行定义框架,根据order by子句排序后,取的前N行及后N行的数据计算(与当前行的值无关,只与排序后的行号相关)。常用:
rows n perceding表示从当前行到前n行(一共n+1行) - RANGE:表示按照值的范围进行定义框架,根据order by子句排序后,指定当前行对应值的范围取值,行数不固定,只要行值在范围内,对应行都包含在内。适用于对日期、时间、数值排序分组
| 边界可取值(Start expr & End expr) | 说明 |
|---|---|
| Current Row | 当前行 |
| N preceding | 前 n 行,n 为数字, 比如 2 Preceding 表示前2行 |
| unbounded preceding | 开头 |
| N following | 后N行,n 为数字, 比如 2 following 表示后2行 |
| unbounded following | 结尾 |
| range取特定日期区间 | 说明 |
|---|---|
| range interval 7-1 day preceding | 最近7天的值 |
| range between interval 1 day preceding and interval 1 day following | 前后一天和当天的值 |
| range between 1000 day preceding and 1000 day following | 当前数据加减1000的数据 |
6.Linux
6.1用户组
-
特权用户(超级用户)root
-
普通用户
sudoers :临时拥有超级用户的权限
其他用户
1.查看所有用户 cat /etc/passwd 2.添加用户 sudo useradd test 添加用户 yuanqi:x:1000:1000::/home/yuanqi:/bin/bash /用戶名/x表示密碼/1000表示用戶id/1000表示組id/用戶的評論信息/用户家目录/默认shell程序 shell是命令解释器 useradd -m -s /bin/bash test -m表示创建家目录 userdel -r test //删除用户 passwd test //修改用户密码
6.2 基本命令
1. pwd //查看当前路径
2. 查看文件
cd
ll
ll -a //查看所有文件
ll -i //显示文件大小
top //查看CPU信息
free //查看内存信息
netstat //查看端口信息
ps -aux //查看进程信息
ps -ef //查看进程信息
ps -ef | grep 'mysql' | wc -l //查看mysql进程数
ps -ef | grep 'mysql' | awk '{prwint $2}' //输出所有的进程信息
cp //复制文件
mv test.sh test.sh.$(date+%Y%m%d)//移动文件或者重命名文件
diff test1 test2 //比较两个文件
cat test.log //查看日志
tail -f -n 100 test.log //实时查看日志
head -n 100 //查看前100行信息
tail -n 100 //查看尾部100行信息
ln -s ../b.test.link //创建软链接
查看MySQL运行状态:service mysqld status
开启MySQL运行服务:service mysqld start
关闭MySQL与性服务:service mysqld stop
find / test //在根目录下查找所有test中的文件
chmod u+x 文件路径 //给用户权限
chmod 777 文件路径 //通过数字方法赋予权限
6.3 docker基本命令
1.启动docker服务
systemctl start docker
2.重启docker服务
systemctl restart docker
3. 停止docker服务
systemctl stop docker
4.查看镜像信息
docker images -a
5.拉取镜像
docker pull 镜像名称
6.删除镜像
docker rmi -f 镜像名称
7.创建容器
docker run [OPTIONS] IMAGE根据镜像新建并启动容器。IMAGE是镜像ID或镜像名称
OPTIONS说明:
--name=“容器新名字”:为容器指定一个名称
-d:后台运行容器,并返回容器ID,也即启动守护式容器
-i:以交互模式运行容器,通常与-t同时使用
-t:为容器重新分配一个伪输入终端,通常与-i同时使用
-P:随机端口映射
-p:指定端口映射,有以下四种格式:
ip:hostPort:containerPort
ip::containerPort
hostPort:containerPort
containerPort
8. 查看所有的政治运行的容器
docker ps列出当前所有正在运行的容器
docker ps -a列出所有的容器
docker ps -l列出最近创建的容器
docker ps -n 3列出最近创建的3个容器
docker ps -q只显示容器ID
docker ps --no-trunc显示当前所有正在运行的容器完整信息
9. 启动容器或者是停止容器
docker start 容器ID或容器名称启动容器
docker restart 容器ID或容器名称重新启动容器
docker stop容器ID或容器名称停止容器
10.查看容器日志
docker logs
docker logs -f -t --since --tail 容器ID或容器名称查看容器日志
如:docker logs -f -t --since=”2018-09-10” --tail=10 f9e29e8455a5
-f : 查看实时日志
-t : 查看日志产生的日期
--since : 此参数指定了输出日志开始日期,即只输出指定日期之后的日志
--tail=10 : 查看最后的10条日志
1.杀某个项目进程
ps -ef | grep dm.sh | grep -v grep | awk '{print $2}' | xargs kill -9
2.命令拼接
cd /sse/apps/dm/datamanager && ./run.sh ASH067 >> test.log
3.后台运行shell脚本
nohup test.sh >> test.log 2>&1 &
4.shell脚本for循环语句
for i in {1..150}; do command;sleep 20s; done;
5.查看日志中的某些信息
grep -i 'ORA-' *.log
6.过滤信息重定向到日志文件
grep -E 'string' file1 >> file2 (从文件1中查找信息写入到文件2中)
7.查找日志中有没有异常信息
grep -i Exception test.log| wc -l (统计有多少次异常信息,wc -l 统计条数)
8.备份crontab定时任务
crontab -l> ~/crontab`date+%Y%m%d`.txt
9.清除crontab定时任务
crontab -r
9.将备份的定时任务还原
crontab < ~/crontab`date+%Y%m%d`.txt
10.查看端口占用
netstat -anp | grep 端口号
或者 lsof -i:端口号
11.重命名文件(文件名中包含日期)
mv test.sh test.sh.$(date+%Y%m%d)
12.比较两个文件不同
diff test.sh test.sh.$(date+%Y%m%d)
7.python
7.1 迭代器、生成器、装饰器
1.迭代器
# 使用iter()返回一个迭代对象,迭代器可以使用next()函数获取一个数据对象
a = [1,5,6,8,9,4,6]
b = set(a) # 将b变成一个集合,set元素可以将列表或者是元组数据去重
for i in b:
print(i,end=' ') # 遍历集合
print('\n集合遍历完成')
c= iter(b) #返回一个迭代对象
while True:
try:
print(next(c),end=' ')
except StopIteration:
print('\n遍历完成')
break
2. 生成器
def simple_generator(number):
i =1
while i <= number:
yield i
i = i+1
if __name__ == '__main__':
it = simple_generator(10)
for i in it:
print(i)
3.迭代器和生成器的区别
- 实现方式不同
生成器用函数生成,函数返回了一个迭代器。函数中必须使用yield语句函数返回的才是一个迭代器。而迭代器使用了类的魔法方法__iter__()和__next__()来实现。调用iter()函数可以返回一个迭代器
2.生成方式不同
生成器可以逐个生成序列中的值,而迭代器是一次性生成整个序列,将其储存在内存中
3.执行方式不同
生成器像函数调用一样使用,可以在每次迭代时产生和恢复值,而迭代器按照序列的下一个元素依次执行
4.功能不同
生成器主要的功能时生成一个序列,而迭代器的主要功能时遍历序列
4.装饰器
装饰器的主要原理是闭包,就是函数中套用一个函数,返回的是函数
def my_decorator(func):
def wrapper(message, *args, **kwargs): # 不定长参数*args,**kwargs
print('wrapper of decorator')
func(message, *args, **kwargs)
return wrapper
@my_decorator
def greet(message):
print(message)
if __name__ == '__main__':
greet('hello world')
7.2 类和属性
1.类属性和静态属性
"""
@classmethod 用来修饰方法。使用在实例化前与类进行交互,但不和其实例进行交互的函数方法上。
@staticmethod 用来修饰类的静态方法。使用在有些与类相关函数,但不使用该类或该类的实例。如更改环境变量、修改其他类的属性等。
classmethod 和staticmethod都是在类不用实例化的时候可以调用,但是classmethod的方法装饰的方法可以调用类属性,不可以调用私有属性。而staticmethod装饰的方法没有类对象作为参数,不能调用类的属性
两者最明显的区别,classmethod 必须使用类的对象作为第一个参数,而staticmethod则可以不传递任何参数
"""
class test:
a = 1
def __init__(self):
self.attribute = 1
@classmethod
def func1(cls, b):
print('类属性为{}'.format(cls.a))
print(b)
@staticmethod
def func2(b):
print(b)
if __name__ == '__main__':
test.func1(9)
test.func2(8)
"""
"Python中一切皆对象"。数字2,类型int,int的超类<class 'object'>都是对象。除此之外,定义的函数,方法......都是对象。
object和type是python中的两个源对象的名字。object是所有类的超类(元类),type是所有类型的元类型。看到对象的类型,无一例外都是 type。先前说过type是用来获对象的类型的。事实上,它既是一个对象,也是获取其它对象的类型的方法。
1: 查看object的类型,看到object是type的实例。
2: object没有超类,因为它本身就是所有对象的超类。
3: object是type的超类,type的类型是它自己。
"""
7.3 私有属性和方法
"""
Python对于类的成员没有严格的访问控制限制,这与其他面相对对象语言有区别。关于私有属性和私有方法,有如下要点:
1、通常我们约定,两个下划线开头的属性是私有的(private)。其他为公共的(public);
2、类内部可以访问私有属性(方法);
3、类外部不能直接访问私有属性(方法);
4、类外部可以通过 ”_类名__私有属性(方法)名“ 访问私有属性(方法)
"""
class demo(object):
def __init__(self,name,age):
self.__name = name
self.__age = age
def __get__age1(self):
return self.__age
def get_age(self):
return self.__age
if __name__ == '__main__':
demo=demo('liming',20)
print(demo.get_age())
print(demo._demo__name) #外部访问类私有属性的方法
8.自动化测试框架
8.1 unitest测试框架
import unittest
import ddt
from util.read_excel import Read_excel
@ddt.ddt
class TpsoLongin(unittest.TestCase):
#测试类中必须继承自unittest.TestCase类
def setUp(self) -> None:
#每个用例开始前执行setup
self.session = requests.Session()
self.url_verify = "http://cx.shouji.360.cn/phonearea.php?number={}"
def tearDown(self) -> None:
self.session.close()
@classmethod
def setUpClass(cls) -> None:
pass
@classmethod
def tearDownClass(cls) -> None:
pass
@ddt.unpack
@ddt.data(*(Read_excel("./data/test.xlsx").read_data()))
def test_01success(self,mobile,status,city):
response =self.session.get(url=self.url_verify.format(mobile))
self.assertEqual(status,response.status_code)
self.assertIn(city,str(response.json()))
@ddt.unpack
@ddt.data(*(Read_excel("./data/test.xlsx").read_data()))
def test_02success(self,mobile,status,city):
response = self.session.get(url=self.url_verify.format(mobile))
self.assertEqual(status, response.status_code)
self.assertIn(city, str(response.json()))
8.2 pytest
1.定义测试类必须以Test开头
2.定义的测试方法必须以test开头
示例代码:
import pytest
class TestAdd():
def setup(self):
print("方法初始方法")
def tear_down(self):
print("销毁方法")
def setup_class(self):
print("类方法开始")
def teardown_class(self):
print("类方法结束")
def test_add(self):
print(1)
assert 1==1
def test_add2(self):
print(2)
assert 1 == 2
3.执行测试文件的方法
pytest -s -v .\demo.py
- -s 表示支持控制台打印,如果不加,print 不会出现任何内容
- -v 表示输出的是执行的用例的类名以及方法名
4.断言方法
assert 表达式
class TestADD: # 定义的类名必须是以Test开头
def test_add_01(self): # 定义的测试方法必须是以test开头
result = add(1, 2)
print(result)
# assert result == 3 判断相等
# assert result != 4 判断不相等
# assert result # 判断为True
#assert False # 判断为False
# assert "a" in "abc" # 判断包含
# assert "a" not in "abc" # 判断不包含
# assert result is None
assert result is not None
5.pytest方法级别中的fixture
pytest方法级别的fixture是针对每个测试方法,在执行测试方法前会执行fixture初始化的操作,在执行完测试方法后,执行fixture销毁的操作。
初始化的操作方法: def setup(self): 方法来实现。
销毁的操作方法: def teardown(self): 方法来实现。
-
pytest 类级别的fixture针对每个测试类的初始化和销毁的操作,可以放在以下两个方法中
-
类级别初始化的方法: def setup_class(self):
-
类级别销毁的方法: def teardown_class(slef):
方法名称固定,不能修改。
6.配置文件
pytest的配置文件有固定的三个名称: pytest.ini tox.ini setup.cfg 这三个配置文件是放在项目的根目录下。
[pytest]
# 添加命令行参数
addopts = -s -v
# 文件搜索路径
testpaths = ./scripts
# 文件名称
python_files = test_*.py
# 类名称
python_classes = Test*
# 方法名称
python_functions = test_*
7.常用插件
1.测试报告插件
使用命令 pip3 install pytest-html 进行安装
[pytest]
# 添加命令行参数
addopts = -s -v --html=report.html
# 文件搜索路径
testpaths = ./
# 文件名称
python_files = demo*.py
# 类名称
python_classes = Test*
# 方法名称
python_functions = test_*
2.控制用例执行顺序
-
unittest测试用例执行顺序是根据测试方法名称的assicc码值的大小来的,值越小排在前面(a-z)
-
pytest 正常情况下是根据测试方法的从上到下的顺序来执行
可以通过 pytest-ordering 插件来控制pytest测试方法执行的顺序。
-
安装:
- 在线安装: pip install pytest-ordering
- 离线安装: 下载对应的离线安装包,解压后,并进入到对应的目录,执行 python setup.py install
- pycharm
-
使用
@pytest.mark.run(order=x) # x 表示的是整数,(既可以是负数也可以是正数)
-
全为负数或者正数时,值越小,优先级越高
-
既有正数,又有负数,那么正数优先级高
-
没有确定执行顺序的用例优先于负数
-
@pytest.mark.run(order=2) def test_add(self): assert 1==1 @pytest.mark.run(order=1) def test_add2(self): assert 1 == 1
3.失败重试插件
pytest-rerunfailures 安装
使用:
在addopts参数行中增加对应的参数项: --reruns 3
当重复执行成功时,就不会再重复执行。
-
8.高级方法
1.跳过用例
@pytest.mark.skipif(condition, reason=None)
condition 表示是跳过的条件
这里面reason参数名称必填。
@pytest.mark.skip(reason=None)
reason表示的是跳过的原因
可以在测试类和测试方法上使用
import pytest
def add(x, y):
return x+y
version = 21
class TestAdd:
# @pytest.mark.last # 设置用例最后执行
def test_add_01(self):
result = add(1, 2)
assert 3 == result
@pytest.mark.skipif(version > 20, reason="大于2.0的版本不需要再执行此用例")
# @pytest.mark.skip("版本已更新,不需要再进行测试")
@pytest.mark.run(order=0)
def test_add_02(self):
result = add(2, 2)
assert 4 == result
@pytest.mark.run(order=-2)
def test_add_03(self):
result = add(3, 2)
assert 5 == result
2.数据参数化
pytest参数化实现: @pytest.mark.parameterize(argnames, argvalues)
-
argnames 表示是 参数名字,是一串字符, 多个参数之间由逗号隔开 "username, password"
-
argvalues 表示的是参数化的数据 [("13700001111","123124"),("13800011111","123456")]
argname的参数个数要与argvalues里面的测试数据的个数要相同,否则会报错。
import pytest def add(x, y): return x+y class TestAdd: @pytest.mark.parametrize("x,y,expect", [(1, 2, 3), (2, 2, 4), (3, 2, 5)]) def test_add_01(self, x, y, expect): result = add(x, y) assert expect == result
8.3 PO模式
PO是page object的简称,核心思想是通过对界面元素的封装减少冗余代码,同时在后期维护中,若元素定位发生变化, 只
需要调整页面元素封装的代码,提高测试用例的可维护性、可读性。
PO模式可以把一个页面分为三层,对象库层、操作层、业务层。
对象库层:封装定位元素的方法。
操作层:封装对元素的操作。
业务层:将一个或多个操作组合起来完成一个业务功能。比如登录:需要输入帐号、密码、点
击登录三个操作。
注意点:
1、确认需要操作的页面
2、每个页面所需要用到的元素对象
8.4 日志的使用
1.logging的基本用法
-
通过logging模块来输出日志信息.
使用前需要导入logging模块
使用方法: logging.debug("这是一条调试级别的日志")
logging.info("这是一条信息级别的日志")
注意点:logging模块默认的级别是warring级别,默认的输出格式:" 级别: root : 日志信息"
如果设置了对应级别,那么会输出大于或等于对应级别的日志信息
# 导包 import logging # 调用logging输出日志 logging.debug("这是一条调试级别的日志") logging.info("这是一条信息级别的日志") logging.warning("这是一条警告级别的日志") logging.error("这是一条错误级别的日志") logging.critical("这是一条严重级别的日志")
2. logging的日志级别设置
日志级别的设置: logging.basicConfig(level=logging.DEBUG)
日志级别的选择:
开发环境和测试环境:可以使用DEBUG或者INFO级别都可以
生产环境:建议使用WARRING或者EEROR
3.logging的日志格式设置
logging.basicConfig(level=logging.DEBUG, format=fmt)
format常用格式说明:
%(levelno)s: 打印日志级别的数值
%(levelname)s: 打印日志级别名称
%(name)s :Logger的名字
%(pathname)s: 打印当前执行程序的路径,其实就是sys.argv[0]
%(filename)s: 打印当前执行程序名
%(funcName)s: 打印日志的当前函数
%(lineno)d: 打印日志的当前行号
%(asctime)s: 打印日志的时间
%(thread)d: 打印线程ID
%(threadName)s: 打印线程名称
%(process)d: 打印进程ID
%(message)s: 打印日志信息
# 导包
import logging
fmt ='%(asctime)s %(levelname)s [%(name)s] [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s'
logging.basicConfig(level=logging.ERROR,format=fmt)
# 调用logging输出日志
logging.debug("这是一条调试级别的日志")
logging.info("这是一条信息级别的日志")
logging.warning("这是一条警告级别的日志")
logging.error("这是一条错误级别的日志")
logging.critical("这是一条严重级别的日志")
4.日志的高级用法
四大组件
-
日志器 logger : 是程序的入口,主要用来记录日志的信息
-
处理器 handler : 决定了日志的输出目的地
-
格式器 formatter :决定了日志的输出格式
-
过滤器 filter: 决定哪些日志信息被输出,哪些被丢弃
总结: 日志器是程序的入口,最终通过处理器来决定日志器该往哪里输出对应的日志信息
1.日志器logger:
创建日志器: logger = logging.getLogger() 或者 logger = logging.getLogger("myLogger")
如果创建的日志器不带参数,默认的日志名称是root
如果创建的日志器带了参数,日志名称为对应的参数名
如果创建的日志器名称相同,实际上,这两个日志器使用的是同一个日志名称。
日志器常用方法:
-
输出日志信息: logger.debug(msg)
logger.info(msg)
logger.warring(msg)
-
设置日志的级别: logger.setLever(logging.INFO)
-
添加处理器: logger.addHandler(handler)
2.handler类
Handler对象的作用是将消息分发到handler指定的位置,比如:控制台、文件、网络、邮件等。 Logger对象可以通过addHandler()方法为自己添加多个handler对象。
如何创建Handler对象 在程序中不应该直接实例化和使用Handler实例,因为Handler是一个基类,它只定义了Handler应 该有的接口。 应该使用Handler实现类来创建对象。
-
处理器的作用:决定日志输出到目的地
-
常用处理器类:
- logging.StreamHandler 将日志信息输出到控制台
- logging.FileHandler 将日志消息发送到磁盘文件,默认情况下文 件大小会无限增长
- logging.hanlders.TimedRotatingFileHandler 将日志信息输出到文件,并支持按时间来切割
-
处理器类常用的方法
- 设置日志的级别 handler.setLevel()
- 在处理器当中添加格式器 handler.setFormatter()
- handler添加一个过滤器对象 handler.addFilter()
logger = logging.getLogger("myLogger") logger.setLevel(logging.WARNING) # 创建处理器 path = Path.joinpath(BASE_DIR,'log/tests.log') handler=logging.handlers.TimedRotatingFileHandler(path,when='S',interval=30,backupCount=2,encoding="utf-8") handler.setLevel(logging.DEBUG) # 将处理器添加到格式器中 logger.addHandler(handler)
3.formatter类
格式器: 用来格式化日志信息的格式
创建格式: formatter = logging.formatter(fmt=fmt) # fmt参数表示的是格式化的字符串
可以将格式器添加到处理器就可以了。
# 导包
import logging
import logging.handlers
from app import BASE_DIR
from pathlib import Path
logger = logging.getLogger("myLogger")
logger.setLevel(logging.WARNING)
# 创建处理器
path = Path.joinpath(BASE_DIR,'log/tests.log')
handler =logging.handlers.TimedRotatingFileHandler(path,when='S',interval=30,backupCount=2,encoding="utf-8")
handler.setLevel(logging.DEBUG)
fmt ='%(asctime)s %(levelname)s [%(name)s] [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s'
formatter = logging.Formatter(fmt=fmt)
# 将格式器添加到处理器中
handler.setFormatter(formatter)
# 将处理器添加到日志器中
logger.addHandler(handler)
4. 将日志输出到控制台和文件中
import logging
import logging.handlers
# 1、创建日志器
import time
logger = logging.getLogger()
# 2、设置日志器级别
logger.setLevel(logging.DEBUG)
# 3、创建两个处理器(输出到控制台以及输出到文件)
# 3.1 创建输出到控制台的处理器
sf = logging.StreamHandler()
# 3.2 创建输出到文件的处理器
hf = logging.handlers.TimedRotatingFileHandler("log/log.log", when='M', interval=1, backupCount=3)
# 4、设置级别
sf.setLevel(logging.INFO)
hf.setLevel(logging.INFO)
# 5、创建格式器
fmt = '%(asctime)s %(levelname)s [%(name)s] [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s'
formatter = logging.Formatter(fmt=fmt)
# 6、添加格式器到处理器当中
sf.setFormatter(formatter)
hf.setFormatter(formatter)
# 7、将处理器添加到日志器
logger.addHandler(sf)
logger.addHandler(hf)
# 8、输出日志信息
while True:
time.sleep(3)
logger.info("这是一条信息级别的日志")
logging.warning("这是一条警告级别的日志") # 前面的配置文件相当于给logging配置!!!这个logging和logger输出的日志格式相同
8.2 htmlTestRunner修改用例名称
博客连接:https://www.cnblogs.com/Simple-Small/p/9230382.html
9.redis简单使用
# redis安装路径
cd /usr/local/bin
# redis启动命令
cd /usr/local/src/redis-6.2.6;
redis-server redis.conf;
# redis命令行连接
cd /usr/local/bin;
redis-cli -a foobared ; -a表示的是密码
redis通用命令
select 5 切换到5数据库
keys * 查看所有的key
del key 删除一个key
exists key 判断一个key是否存在
expire key 20 给key添加过期时间,单位是秒。没有设置,时间为-1 ,默认代表永久
ttl key 查看key剩余有效时间


浙公网安备 33010602011771号