1. 自动化测试
1.1 selenium
selenium是用来做web自动化测试的框架
1.2 selenium特点
支持各种浏览器,支持各种平台支持各种语言,有丰富的API
1.3 selenium工作原理
1.3.1 webdriver API
-
元素的定位
css选择语法
- id选择器
#id - 类选择器
.classname - 标签选择器
标签名 - 后代选择器
标签名 后代标签
- id选择器
-
name定位
-
XPath定位
- 绝对路径
/html/head/title(不常用) - 相对路径
-
相对路径+索引
//form/span[1]/input
注意:这里的索引是从1开始的 -
相对路径+属性值
//input[@class=“s_ipt”] -
相对路径+通配符
//* [ @ * = " s_ipt "] -
相对路径+文本匹配
//a[text()=“新闻”]
-
- 绝对路径
操作测试对象
webdriver中比较常用的操作对象的方法有下面几个
- click点击对象
- sendKeys在对象上模拟按键输入
- clear清除对象输入的文本内容
- submit提交
如果点击的元素放在form标签中,此时使用submit实现的效果和click是一样的;
如果点击的元素放在非form标签中,此时使用submit会报错 - text用于获取元素的文本信息
- getAttribute获取元素的属性
添加等待
- sleep休眠
import time
time.sleep(3)
- 隐式等待
webDriver.manage().timeouts().implicitlyWait(3,TimeUnit.Days);//等待三天
假设等待三天时间
隐式等待最长等待3天时间,如果三天内获取到了页面上的元素,就执行下面的代码
如果等待三天时间还是没有找到这个元素此时报错
- 显示等待
private static void test() {
ChromeOptions options = new ChromeOptions();
options.addArguments("--romote-allow-origins=*");//允许所有的请求
WebDriver webDriver = new ChromeDriver(options);
//打开百度首页
webDriver.get("https://www.baidu.com");
//判断元素是否可以被点击
WebDriverWait wait = new WebDriverWait(webDriver,1);
wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//*[@id=\"bottom_layer\"]/div/p[9]/span")));//等待1s括号里的值还是为假就报错
}
获取信息
打印title
- getTitle();
- getCurrentUrl()
- getAttribute()
浏览器的的操作
- 浏览器最大化
browser.maxmize_window()
- 设置浏览器宽、高
browser.set_windows_size(width,height)
- 浏览器的前进、后退、刷新
webDriver.navigate().forward();
webDriver.navigate().back();
webDriver.navigate().refresh();
- 控制浏览器滚动条
将浏览器滚动条滑倒最顶端
document.documentElement.scrollTop=0
将浏览器滚动条滑倒最底端
document.documentElement.scrollTop=10000
键盘事件
//control+A
webDriver.findElement(By.cssSelector("#kw")).sendKeys(Keys.CONTROL,"A");
sleep(2000);
//control+X
webDriver.findElement(By.cssSelector("#kw")).sendKeys(Keys.CONTROL,"x");
sleep(2000);
//control+V
webDriver.findElement(By.cssSelector("#kw")).sendKeys(Keys.CONTROL,"v");
鼠标事件
ActionChains(driver)
生成用户的行为。所有的行动都存储在actionchains对象。通过perform()存储的行为。
perform()
执行所有存储的行为
ActionChains类
- contextClick()
- doubleClick()
- moveToElement()
private static void test09() throws InterruptedException {
ChromeOptions options = new ChromeOptions();
options.addArguments("--remote-allow-origins=*");
WebDriver webDriver = new ChromeDriver(options);
webDriver.get("https://www.baidu.com");
webDriver.findElement(By.cssSelector("#kw")).sendKeys("520");
webDriver.findElement(By.cssSelector("#su")).click();
sleep(2000);
//找到图片按钮
WebElement webElement = webDriver.findElement(By.cssSelector("#s_tab > div > a.s-tab-item.s-tab-pic"));
Actions actions =new Actions(webDriver);
sleep(2000);
//点击
webDriver.findElement(By.cssSelector("#s_tab > div > a.s-tab-item.s-tab-pic")).click();
// actions.moveToElement(webElement).click().perform();
右击
// actions.moveToElement(webElement).contextClick().perform();
双击
// actions.moveToElement(webElement).doubleClick().perform();
//拖动
// actions.moveToElement(webElement).doubleClick().dragAndDrop();
}
多层框架/窗口定位
- 定位一个frame: webDriver.switchTo().frame(name_or_id_or_frame_element);
- 定位一个窗口window:webDriver.switchTo().window(name_or_id_or_frame_element)
多层框架
private static void page01() {
WebDriver webDriver = new ChromeDriver();
webDriver.get("http://127.0.0.1:3000/page01.html");
List<WebElement> webElements = webDriver.findElements(By.cssSelector("input"));
for(int i = 0;i<webElements.size();i++)
{
if(webElements.get(i).getAttribute("type").equals("checkbox")){
webElements.get(i).click();
}
else {
System.out.println(webElements.get(i).getAttribute("type"));
}
}
}
private static void page02() {
WebDriver webDriver = new ChromeDriver();
webDriver.get("file:///C:/Users/ASUS/Desktop/page02.html");
//把当前定位的主体切换到了frame里
webDriver.switchTo().frame("f2");
webDriver.findElement(By.cssSelector("body > div > div > a")).click();
}
下拉框处理
private static void page03() throws InterruptedException {
WebDriver webDriver = new ChromeDriver();
webDriver.get("file:///C:/Users/ASUS/Desktop/page03.html");
WebElement webElement =webDriver.findElement(By.cssSelector("#number"));
Select select = new Select(webElement);
sleep(2000);
select.selectByIndex(2);
// select.selectByValue("2");
}
运行结果:可以看到最后index是从0开始的
alert、confirm、prompt的处理
private static void page04() throws InterruptedException {
WebDriver webDriver= new ChromeDriver();
webDriver.get("file:///C:/Users/ASUS/Desktop/page04.html");
webDriver.findElement(By.cssSelector("button")).click();
sleep(2000);
//alert弹窗取消
webDriver.switchTo().alert().dismiss();
sleep(2000);
//点击按钮
webDriver.findElement(By.cssSelector("button")).click();
sleep(2000);
//输入
webDriver.switchTo().alert().sendKeys("Maria");
sleep(2000);
//alert弹窗的确认
webDriver.switchTo().alert().accept();
}
上传文件处理
1、定位上传按钮
2、通过sendKeys()添加文件的本地路径就可以了
private static void page05() throws InterruptedException {
WebDriver webDriver= new ChromeDriver();
webDriver.get("file:///C:/Users/ASUS/Desktop/page04.html");
webDriver.findElement(By.cssSelector("input")).sendKeys("F:\\Four_year\\培养计划.xlsx");
}
关闭浏览器
-
webDriver.quit()
-
webDriver.close()
quit和close之间的区别
- quit关闭整个浏览器所有标签页,close关闭当前页面句柄对应的页面
- quit清空缓存,close不会清空缓存
切换页面
- getWindowHandles获取所有的窗口句柄
- getWindowHandle获取get打开的页面窗口句柄
private static void test11() throws InterruptedException{
WebDriver webDriver = new ChromeDriver();
webDriver.get("https://www.baidu.com/");
webDriver.findElement(By.cssSelector("#s-top-left > a:nth-child(1)")).click();
sleep(2000);
// getWindowHandles获取所有的窗口句柄
// getWindowHandle获取get打开的页面窗口句柄
Set<String> handles = webDriver.getWindowHandles();
String targetHandle= "";
for(String handle:handles)
{
targetHandle = handle;
}
// String targetHandle = webDriver.getWindowHandle();
webDriver.switchTo().window(targetHandle);
sleep(2000);
webDriver.findElement(By.cssSelector("#ww")).sendKeys("新闻联播");
sleep(2000);
webDriver.findElement(By.cssSelector("#s_btn_wr")).click();
}
截图
private static void test12() throws InterruptedException, IOException {
WebDriver webDriver = new ChromeDriver();
webDriver.get("https://www.baidu.com/");
webDriver.findElement(By.cssSelector("#kw")).sendKeys("测试");
webDriver.findElement(By.cssSelector("#su")).click();
sleep(2000);
File file = ((TakesScreenshot)webDriver).getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(file,new File("C:\\Users\\ASUS\\Pictures\\Screenshots\\20240526.png"));
}
2.性能测试
2.1 什么是性能测试
2.1.1 生活中遇到的软件的性能问题
1,资源泄漏,包括内存泄漏。
2,CPU使用率达到100%,系统被锁定等。
3,线程死锁,阻塞等造成系统越来越慢。
4,查询速度慢,或者列表的效率低。
5,受外部系统影响越来越大
2.1.2 性能测试的定义
测试人员借助性能测试工具,模拟系统在不同场景下,对应的性能指标是否达到预期
2.1.3 性能测试和功能测试有什么区别
功能测试 | 性能测试 |
---|---|
依靠人工完成 | 依靠工具完成 |
不管在什么场景下,只要能够正常运行即可 | 软件在极端情况下,是否能够正常运行 |
2.1.4 常见性能指标
- 确定了系统的关键业务之后,我们进行性能测试要关注这些业务功能的哪一些指标呢?
- 系统/事务的平均响应时间
- 事务处理效率TPS(Transaction Per Second)
- 吞吐率
- 每秒点击次数(Hits Per Second)
- 服务器资源占用情况,内存和CPU使用率
- 软、硬件配置是否合适(容量规划/硬件选型)
2.1.5 影响一个软件性能因素有哪些
软件:算法、编程语言
硬件:服务器CPU利用率、内存、磁盘操作频率、CPU核心数
用户:并发数、使用时长、使用频率
2.2 为什么要做性能测试
- 获取系统性能的指标,作为性能指标的基准
- 验证系统的性能指标是否达到要求(性能需求)
- 应用程序是否能够满足系统要求的各中性能指标
- 应用程序是否能处理预期的用户负载并有盈余能力
- 应用程序是否能处理业务所需要的事务数量
- 在预期和非预期的用户负载下,应用程序是否稳定
- 是否能确保用户在真正使用软件时获得舒服的体验
- 发现系统的性能瓶颈,内存泄漏等问题。
- 系统正常工作的情况下的最大容量。
- 帮助系统运维部门能更好的规划硬件配置
2.3 性能测试常见术语以及衡量指标
-
并发用户数
并发用户会对系统造成压力,首先对系统用户数,在线用户数,并发用户数做一个区分。
系统用户数:简单地说就是该系统的注册用户数。例如,BestTest论坛里存在6666个注册用户,他们可以是活跃的,也可以是僵尸的。
业务层面的并发用户数:指的是同时向服务器发送请求的用户数量。
后端服务器层面的并发用户数:指的是同时向服务器发送请求的请求数量。 -
响应时间/平均响应时间
响应时间分为前端展示时间和系统响应时间两部分。
前端展示时间指的是客户端收到服务器返回的数据后渲染前端页面,所耗费的时间。
系统的响应时间,分为web服务器,应用服务器,数据库服务器,等各种服务器之间通信和处理请求的时间。
前端展示时间(用户响应时间):服务器发起请求到收到响应这段时间
N1+A1+N2+A2+N3+A3+N4
系统响应时间(请求响应时间):服务器收到请求到发出响应这段时间
A1+N2+A2+N3+A3
影响一个软件响应时间的因素:网络带宽、数据库性能、服务器处理性能、软件算法逻辑 -
事务响应时间
这里的一个事务是一个业务度量单位,是指一组密切相关的子操作的组合。比如,一笔电子支付操作,后台处理的时候可能需要经过会员系统,账务系统,支付系统,银行系统等,这就是是一个关于支付事务里面包含的操作。而对于用户,往往也只关注整个支付花费了多长时间。 -
每秒事务通过数
TPS 是指每秒系统能够处理的事务数。它是衡量系统处理能力的重要指标。
当压力加大时,TPS曲线如果变化缓慢或者有平坦的趋势,很有可能是服务器开始出现瓶颈了。如果环境没有发生大的变化,对于同一系统会存在一个最大处理事务能力,它并不随着并发用户的增减而改变。
YB地铁检票机:
只有10台进站检票的机器,1台机器1秒能进1个人
并发用户数为5,则TPS为5
并发用户数为10,则TPS为10
并发用户数为100,则TPS仍为10
- 点击率(Hit Per Second)
每秒点击数代表用户每秒向Web 服务器提交的HTTP请求数。点击率越大,服务器压力越大。
这里的点击并不是鼠标的一次点击,一次点击可能有多次HTTP请求。 - 吞吐量(Throughput)
单位时间内系统处理的客户请求的数量,直接体现软件系统的性能承载能力,一般来说用
Requests/second,Pages/Second,Bytes/Second,从业务的角度,也可以用访问人数/天或是处理的业务数/小时来衡量,从网络设置的的角度来说,也可以用字节数/天来衡量。 - 思考时间(Think Time)
- 指模拟正式用户在实际操作时的停顿间隔时间,从业务的角度来讲,思考时间指的是用户在进行操作时,每个请求之间的间隔时间。
- 资源利用率
不同系统资源的使用情况。包含CPU,内存,硬盘,网络等。
2.4性能测试方法
- 基准性能测试
让系统在正常情况下运行,需要进行性能摸底,观察软件性能指标 - 负载性能测试
验证软件在一定的压力情况下运行,观察性能指标是否出现拐点 - 压力性能测试
系统处于饱和情况下,观察系统性能指标,往往会把系统搞崩溃 - 可靠性测试
验证系统在一个持续的时间段内运行,在这个运行时间段,观察各项性能指标是否正常
2.5 性能测试执行流程
功能测试执行流程
- 需求分析
- 测试计划
- 测试设计
- 测试执行
- 测试评估
性能测试执行流程
- 需求分析
摸底page接口可以容纳的用户数据
项目名称 | XX官网性能摸底测试 |
---|---|
测试人员 | Mary |
测试时间 | 2024-5-29 |
测试环境 | 线上环境 |
性能指标截图 | |
结论 | page接口容纳的人太少了测试不通过 |
解决方案 | page查询接口加上缓存 |
- 测试计划
- 选择性能测试工具
- 性能测试脚本编写
- 执行性能测试脚本
- 产出性能测试报告
性能测试中出现了不符合预期的情况,我们不叫bug,他叫性能瓶颈
在性能测试中,出现了性能瓶颈,开发修复的过程,叫它优化
3.性能测试工具–LoadRunner
3.1 loadrunner三大组件之间的关系
VUG-录制脚本(编写脚本)
Controller–设计场景、运行场景
Analysis-产生性能测试报告
3.2 开发测试脚本
下面以Loadrunner安装时附带的样例程序Web Tours进行讲解。
C:\Program Files (x86)\HP\LoadRunner\WebTours,选择StartServer.bat启动服务。
录制基本的用户脚本
-
启动 Visual User Generator 后,选择新建脚本,因为要测试的是web项目,所以选择协议WebHTTP/HTML,点击创建后,进入主窗体
-
在解决方案资源管理器中可以看到该脚本的组成部分。
VuGen 中的脚本分为三部分:
- vuser_init用于用户初始化
- vuser_end用于用户清理工作
- Action用于具体的需要测试的操作
在重复执行测试脚本时,vuser_init 和vuser_end 中的内容只会执行一次,重复执行的只是Action 中的部分。
- 选择录制操作,可以开始一次录制操作,在录制中需要填写URL,这里以http://10.201.218.178:1080/WebTours/为例。录制到操作说明是将脚本放置到哪里。在录制中也可以修改脚本放置的地方。 已注册的用户名和密码查看地址:xxx\HP\LoadRunner\WebTours\cgi-bin\users 开始录制中,“立即”默认情况下是选中的,说明应用程序一旦启动,VuGen 就会开始录制脚本;如果没有选中,应用程序启动后,VuGen 出现对话框,待确认后才开始录制。一般默认即可。
- 基于HTML的脚本:这种方式录制的代码只生成了一个函数,所有请求全放在这个函数里面,即:
一个操作(可能包含多个请求)会生成一个函数,这种方式看起来比较简洁
基于HTML的脚本模式下–>高级选项两种方式的区别:
描述用户操作的脚本(e.g. web_link,web_submit_form):上下文相关的,依赖上下文才能提交,
比较符合人们的操作习惯。
仅包含明确URL的脚本(e.g. web_url, web_submit_data):上下文不相关,每个函数都指定了具体
的url地址,可以直接提交成功,如果只关注协议,不需要关注页面,可使用这种方式录制。 - 基于URL的脚本:这种方式会生成很多函数,它将每个请求都单独成一个函数,这种方式更接近请
求-响应的本质
选择哪种方式录制,有以下参考原则:
- 基于浏览器的应用程序推荐使用HTML-based Script
- 不是基于浏览器的应用程序推荐使用 URL-based Script
- 如果基于浏览器的应用程序中包含了 JavaScript 并且该脚本向服务器产生了请求,比如DataGrid
的分页按钮等,也要使用URL-based 方式录制 - 基于浏览器的应用程序中使用了 HTTPS 安全协议,使用URL-based 方式录制
- 点击开始录制,loadrunner会自动调用IE,并开始录制操作。这里以注册为例进行录制,录制完毕后,点击停止,录制停止,返回到脚本界面,可以看到已录制的脚本
插入事务
事务(Transaction):为了衡量服务器的性能,我们需要定义事务。比如:我们在脚本中有一个数据
查询操作,为了衡量服务器执行查询操作的性能,我们把这个操作定义为一个事务,这样在运行测试脚
本时,LoadRunner 运行到该事务的开始点时,LoadRunner 就会开始计时,直到运行到该事务的结束
点,计时结束。这个事务的运行时间在结果中会有反映。插入事务操作可以在录制过程中进行,也可以
在录制结束后进行。LoadRunner 可以在脚本中插入不限数量的事务
lr_start_transaction(“register”);
lr_end_transaction(“register”, LR_AUTO);
插入集合点
插入集合点是为了衡量在加重负载的情况下服务器的性能情况。在测试计划中,可能会要求系统能够承
受1000 人同时提交数据,在LoadRunner 中可以通过在提交数据操作前面加入集合点,这样当虚拟用
户运行到提交数据的集合点时,LoadRunner 就会检查同时有多少用户运行到集合点,如果不到1000
人,LoadRunner 就会命令已经到集合点的用户在此等待,当在集合点等待的用户达到1000 人时,
LoadRunner 命令1000 人同时去提交数据,从而达到测试计划中的需求
注意:集合点经常和事务结合起来使用
lr_rendezvous(“index”);
参数化输入
插入函数
VuGen 中可以使用C 语言中比较标准的函数和数据类型,语法和C 语言相同。下面简单介绍一下比较常
用的函数和数据类型。
在脚本页面,通过右键-插入-新建步骤可以查看函数列表
- 控制脚本流程
if { } else { }
for{ }
while{ }
- 字符串函数
由于在 VuGen 脚本中使用最多的还是字符串,所以字符串函数在脚本中使用非常频繁。具体的语法请
参考帮助说明。
strcmp 比较两个字符串
strcat 连接两个字符串
strcpy 拷贝字符串
注意:在VuGen 中,以char声明的字符串是只读的,如果试图给char类型的字符串赋值的话,编译会通过,但在运行时会产生“Access Violation”的错误。解决这类问题,就是把字符串声明为字符数组,比如char[100]。
3. 输出函数
输出函数在调试脚本时非常有用。
lr_output_message 输出一条消息
- LoadRunner 提供的标准函数
lr_eval_string 该函数功能是得到参数(参数化输入中)当前的值
exg: lr_output_message(“temp = %s”, lr_eval_string(“{WCSParam2}”));
lr_save_string 该函数功能是把一个字符串保存到参数中
exg: lr_save_string(“439”,“WCSParam3”);
//如果当前登录的用户是Maria01,此时输出字符串,Hello Maria01
if(strcmp(“Maria01”,lr_eval_string(“{username}”))==0){
lr_output_message(“hello %s”,lr_eval_string(“{username}”));
}
插入检查点
在进行压力测试时,为了检查Web 服务器返回的网页是否正确,VuGen 允许我们插入Text 检查点,这些检查点验证网页上是否存在指定的Text,还可以测试在比较大的压力测试环境中,被测的网站功能是否保持正确。检查点的含义和unittest中的断言功能基本上一致。
web_reg_find(“Fail=NotFound”,
“Search=All”,
“SaveCount=”,
“Text=Welcome”,
LAST);
运行时设置
当完善了测试脚本后,需要对VuGen 的“运行时设置” 进行配置。
在“解决方案资源管理器”视图中选择“运行时设置”,常用的设置内容如下:
1、常规-其他-错误处理:
一般不需要改动,但是在Controller运行时,可以设置“出现错误时仍继续”,来统计错误率
2、常规-其他-自动事务
当我们手工设置了事务时,建议取消该项,以免Controller运行时事务太多
3、常规-运行逻辑-迭代数
根据需要变动,一般在调试脚本时可以设置为多次迭代
4、常规-日志
在调试阶段可勾选“启动日志记录”,脚本稳定时可取消该项
5、常规-思考时间
忽略时会对服务器造成更大的压力,而增加思考时间可以更好的模拟用户使用
6、internet协议-首选项-启用图像或文本检查不勾选“启用图像或文本检查”,web_find和web_image_check设置的检查点在运行时无效
7、工具-选项-脚本-回放
如果勾选“回放期间显示运行时查看器”,则在运行时会启动浏览器
单机运行测试脚本
经过以上的各个步骤后,脚本就可以运行了。运行脚本可以通过菜单或者工具栏来操作。
执行“运行”命令后,VuGen 先编译脚本,检查是否有语法等错误。如果有错误,VuGen将会提示错误。
双击错误提示,VuGen 能够定位到出现错误的那一行。为了验证脚本的正确性,我们还可以调试脚
本,比如在脚本中加断点等。
如果编译通过,就会开始运行。然后会出现运行结果
3.3 创建运行场景
运行场景描述在测试活动中发生的各种事件。一个运行场景包括一个运行虚拟用户活动的Load
Generator 机器列表,一个测试脚本的列表以及大量的虚拟用户和虚拟用户组。创建运行场景使用 Controller。
1、 面向目标的场景: 在测试计划中,一般都包括性能测试要达到的目标。选择该项后,LoadRunner
基于这个目标,自动为你创建一个场景。在场景中,我们只要定义好我们的目标即可
2、手动场景:该项要完全手动的设置场景。
2.1 使用百分比模式在脚本间分配Vuser:该项只有在“手动场景”选中的情况下才能选择。选择该项后,
在场景中我们需要定义要使用的虚拟用户的总数,然后我们为每一个脚本分配要运行的虚拟用户的百分
比。如果不按百分比的话每一个脚本分配的用户按数量分配。
确保RPC和remote register服务处于开启状态
4. Junit5
Junit是Java里单元测试的框架
- 提供用例组织与执行
- 提供丰富的比较方法
- 提供丰富的日志
4.1 注解
- @Test
表示当前的代码是一个测试用例
public class JunitTest {
@Test
void test01()
{
System.out.println("这是Junit里面的Test01");
}
@Test
void test02()
{
System.out.println("这是Junit里面的Test02");
}
}
- @Disable
忽略方法
- @BeforeAll、@AfterAll、@BeforeEach、@AfterEach
如果有2个测试用例,BeforeEach跑2次,BeforeAll跑1一次
如果有2个测试用例,AfterEach跑2次,AfterAll跑1一次
BeforeEach在每个测试用例之前跑对应的方法,BeforeAll在所有测试用例之前跑对应的方法。
AfterEach在每个测试用例之后跑对应的方法,AfterAll在所有测试用例之后跑对应的方法。
参数化
- 单参数
@ParameterizedTest
@ValueSource(argument list)
@ArgumentsSource(ValueArgumentsProvider.class)
public @interface ValueSource {
short[] shorts() default {};
byte[] bytes() default {};
int[] ints() default {};
long[] longs() default {};
float[] floats() default {};
double[] doubles() default {};
char[] chars() default {};
boolean[] booleans() default {};
String[] strings() default {};
Class<?>[] classes() default {};
}
-
多参数
-
CSV获取参数
-
方法获取参数
测试用例执行顺序
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class JunitTest {
@Order(2)
@Test
void test01()
{
System.out.println("这是Junit里面的Test01");
}
@Order(1)
@Test
void test02()
{
System.out.println("这是Junit里面的Test02");
}
}
断言
- Assertions.assertNull(str)
- Assertions.assertNotNull(str)
- Assertions.assertEquals(a,b)
- Assertions.assertNotEquals(a,b)
测试套件
- 通过class运行测试用例
@Suite
@SelectClasses({JunitTest01.class,JunitTest02.class}) - 通过包运行测试用例
@Suite
@@SelectPackages(value={“Test01”,“Test02”})