目录
前言
之前实现了一个博客系统,今天我们就一起来测试以下这个博客系统的功能和性能如何吧~
博客系统——项目介绍
背景:当你在学习一项技能的时候,相信你一定有做笔记的经历,这是一个好习惯,方便你在遗忘或是复习的时候回顾知识,而接下来我要讲的这个程序,就可以方便你去写自己的学习笔记,并且可以发表出来,即方便自己学习,同时也方便别人~
概述:一个Web网站程序,你可以观看到其他用户博客也可以登录自己的账号发布博客;
相应技术栈:Servlet、HTML、CSS、JavaScript、Ajax、Json、MySQL
博客登录页:
博客列表页(用户“zhangsan”为例):
博客详情页:
博客博客编辑页:
1、测试计划
1.1、功能测试
1.1.1、编写测试用例
这里由于编写的是Web自动化测试用例,所以主要的测试的方面就是两点:功能测试和界面测试。
测试用例如下:
1.1.2、实际执行步骤
博客登录页:检测界面是否符合预期,输入正确或错误的账号和密码是否能得到预期的响应
a)界面
b)输入正确的账号和密码(以用户"zhangsan"为例):
预期结果:跳转到博客详情页。
实际结果如下:
c)输入错误的账号或密码
预期结果:提示用户输入错误。
实际结果如下:
博客列表页:检测界面是否符合预期,点击“查看全文”按钮是否能跳转到对应的博客详情页,点击注销是否能退出登录
a)界面
b)点击查看全文
预期结果:进入到对应的博客详情页。
实际结果:
c)点击注销
预期结果:删除Session会话信息,跳转到博客登录页。
实际结果:
博客详情页:检测界面是否符合预期,检测导航栏功能是否符合预期
a)界面
b)导航栏功能(以删除博客为例):删除他人博客
预期结果:不可已删除,并提示不是自己的博客无法删除。
实际结果:
博客编辑页:检测界面是否符合预期,是否能够发布博客,并再博客列表页显示。
a)界面:
b)输入标题和正文,然后发布博客
预期结果:发布博客并在博客列表页展示。
实际结果:
1.2、使用Selenium进行Web自动化测试
1.2.1、引入依赖
进行Web自动化测试,我们需要创建Maven项目引入如下依赖(selenium4、junit5、保存屏幕截图):
<dependencies>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.0.0</version>
</dependency>
<!-- 保存屏幕截图文件需要用到的包 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite</artifactId>
<version>1.8.2</version>
<scope>test</scope>
</dependency>
</dependencies>
1.2.2、提取共性,实现代码复用
在我们的Web自动化测试项目中,需要对博客登录页、博客列表页、博客编辑页、博客详情页,这几个页面分别进行测试,那么可以分别创建四个类,进行针对性测试。
不难发现,这几类中都少不了的是——创建驱动和使用驱动,如果每一个类中我们都去创建驱动,最后释放驱动,这样的过程是十分消耗资源的,那么我们便可以把创建驱动这个操作放在一个类中,并设置驱动对象为静态的,就可以让创建和销毁驱动的步骤执行一次,其他类如果需要驱动直接继承该类即可~
代码如下(这里自定义类名为AutoTestUtils):
import org.openqa.selenium.chrome.ChromeDriver;
import java.time.Duration;
public class AutoTestUtils {
private static ChromeDriver driver;
//创建驱动
public static ChromeDriver getDriver() {
if(driver == null) {
driver = new ChromeDriver();
//创建隐式等待,设置最长等待时间为10秒
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
}
return driver;
}
}
1.2.3、创建测试套件类
创建一个类(自定义名为RunSuite),通过 @Suite 注解标识该类为测试套件类(不是测试类),然后使用 @SelectClasses 注解来声明我们要指定的类(通过这个类来运行测试用例),这样优点如下:
1.相比于一个个函数调用来对测试用例进行测试就大大减少了开销和时间;
2.同时指定了类的测试顺序,即在注解@SelectClasses参数中的类测试顺序为从左向右;
代码如下:
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;
@Suite
@SelectClasses({})
public class RunSuite {
}
1.2.4、博客登录页自动化测试
创建一个类(自定义名为BlogLoginTest)继承AutoTestUtils类,得到驱动,然后再这个类下分别对打开网页是否正常,并使用参数化注解对登录正常模块、登录异常模块分别进行测试。
Ps:需要注意的点都在注释中
代码和详细注释如下:
import com.common.AutoTestUtils;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BlogLoginTest extends AutoTestUtils {
//获取驱动
private static ChromeDriver driver = getDriver();
/**
* 打开网页
* 此方法需要在该类的所有测试用例执行之前执行一次,所以使用注解 @BeforeAll
*/
@BeforeAll
private static void openWeb() {
driver.get("http://43.139.193.116:8080/blog_system/login.html");
}
/**
* 检测登录页面是否正常打开
* 检测:元素是否都存在
*/
@Test
@Order(1)
public void elementsAppear() {
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(4)"));
driver.findElement(By.cssSelector("#username"));
driver.findElement(By.xpath("//*[@id=\"login-btn\"]"));
}
/**
* 检测登录异常测试
*/
@ParameterizedTest
@CsvSource({"zhangsan, 1234", "lisi, 4456"})
@Order(2)
public void loginAbnormalTest(String username, String password) {
//先清除用户名和密码框
driver.findElement(By.cssSelector("#username")).clear();
driver.findElement(By.cssSelector("#password")).clear();
//输入账号和密码
driver.findElement(By.cssSelector("#username")).sendKeys(username);
driver.findElement(By.cssSelector("#password")).sendKeys(password);
driver.findElement(By.cssSelector("#login-btn")).click();
//期望结果和实际结果
String expect = "用户名或密码错误!";
String actual = driver.findElement(By.cssSelector("body")).getText();
Assertions.assertEquals(expect, actual);
driver.navigate().back();
}
/**
* 检测正常登录情况
*/
@ParameterizedTest
@CsvSource({"zhangsan, 123", "lisi, 123"})
@Order(3)
public void loginNormalTest(String username, String password) {
//先清除用户名和密码框
driver.findElement(By.cssSelector("#username")).clear();
driver.findElement(By.cssSelector("#password")).clear();
//输入用户名和密码并点击登录
driver.findElement(By.cssSelector("#username")).sendKeys(username);
driver.findElement(By.cssSelector("#password")).sendKeys(password);
driver.findElement(By.cssSelector("#login-btn")).click();
//由于使用参数化的注解,所以需要继续返回到登录页面
driver.navigate().back();
}
}
测试结果如下:
使用测试套件类RunSuite进行测试,结果如下:
1.2.5、博客列表页自动化测试
创建一个类(自定义名为BlogListTest)继承AutoTestUtils类,得到驱动,博客列表页这里需要测试打开网页正常显示,那么找几个典型的元素进行测试即可~
代码如下:
import com.common.AutoTestUtils;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;
public class BlogListTest extends AutoTestUtils {
//获取驱动
private static ChromeDriver driver = getDriver();
/**
* 打开网页
* 此方法需要在该类的所有测试用例执行之前执行一次,所以使用注解 @BeforeAll
*/
@BeforeAll
private static void openWeb() {
driver.get("http://43.139.193.116:8080/blog_system/blog_list.html");
}
/**
* 测试博客列表页正常显示
*/
@Test
public void elementsAppear() {
driver.findElement(By.cssSelector("body > div.container > div.container-left > div > img"));
driver.findElement(By.cssSelector("body > div.container > div.container-left > div > div:nth-child(4) > span:nth-child(1)"));
driver.findElement(By.xpath("/html/body/div[1]/a[2]"));
driver.findElement(By.xpath("/html/body/div[2]/div[2]/div[1]/a"));
}
}
测试结果如下:
使用测试套件类RunSuite进行测试,结果如下:
1.2.6、博客编辑页自动化测试
创建一个类(自定义名为BlogEditTest)继承AutoTestUtils类,得到驱动,博客编辑页这里需要测试打开网页正常显示,并且正确输入标题和正文并发布博客,若成功发布博客,通过列表页的相关信息检测是否符合预期:
代码如下:
import com.common.AutoTestUtils;
import org.junit.jupiter.api.*;
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BlogEditTest extends AutoTestUtils {
//获取驱动
private static ChromeDriver driver = getDriver();
/**
* 打开网页
* 此方法需要在该类的所有测试用例执行之前执行一次,所以使用注解 @BeforeAll
*/
@BeforeAll
public static void openWeb() {
driver.get("http://43.139.193.116:8080/blog_system/blog_edit.html");
}
/**
* 检测博客编辑页可以正常打开
*/
@Test
@Order(1)
public void elementAppear() {
driver.findElement(By.cssSelector("#blog-title"));
driver.findElement(By.cssSelector("#submit"));
}
/**
* 检测发布博客是否正常
* 具体操作:
* 1.输入标题
* 2.由于引入的使第三方库的编辑器,所以这里点击编辑器的几个按钮来输入一些特殊字符即可
* 3.点击发布按钮
* 4.再进入博客博客详情页点击删除博客
*/
@Test
@Order(2)
public void blogPost() {
//预期标题(成功发布博客后在博客列表页检测)
String expect = "Autotest";
driver.findElement(By.cssSelector("#blog-title")).sendKeys(expect);
driver.findElement(By.cssSelector("#editor > div.editormd-toolbar > div > ul > li:nth-child(21) > a > i")).click();
driver.findElement(By.cssSelector("#submit")).click();
//获取博客列表页第一篇博客,检测是否符合预期
String actual = driver.findElement(By.cssSelector("body > div.container > div.container-right > div:nth-child(1) > div.title")).getText();
Assertions.assertEquals(expect, actual);
}
}
测试结果如下:
使用测试套件类RunSuite进行测试,结果如下:
1.2.7、博客详情页自动化测试
创建一个类(自定义名为BlogEditTest)继承AutoTestUtils类,得到驱动,博客详情页这里需要测试打开网页正常显示。
如下代码:
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;
import static com.common.AutoTestUtils.getDriver;
public class BlogDetailTest {
//获取驱动
private static ChromeDriver driver = getDriver();
/**
* 打开网页
* 此方法需要在该类的所有测试用例执行之前执行一次,所以使用注解 @BeforeAll
*/
@BeforeAll
public static void openWeb() {
driver.get("http://43.139.193.116:8080/blog_system/blog_detail.html?blogId=1");
}
/**
* 检测博客详情页信息
*/
@Test
public void elementAppear() {
driver.findElement(By.cssSelector("body > div.container > div.container-right > div > h3"));
driver.findElement(By.cssSelector("#content > p"));
driver.findElement(By.cssSelector("body > div.container > div.container-right > div > div.date"));
}
}
测试结果如下:
使用测试套件类RunSuite进行测试,结果如下:
1.2.8、退出驱动
在所有自动化测试用例执行完后,需要进行退出浏览器,那么我们可以创建一个退出驱动类,放在测试套件类的最后一个测试类。
代码如下:
import com.common.AutoTestUtils;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.chrome.ChromeDriver;
public class DriverQuitTest extends AutoTestUtils {
//获取驱动
private static ChromeDriver driver = getDriver();
@Test
public void quitWeb() {
driver.quit();
}
}
1.2.9、屏幕截图
有的时候我们测试用例执行出错了,我们需要查看当时网页出现的情况,那么就需要使用屏幕截图来排查问题。
具体的,我们可以在每一个测试case执行完后进行一次屏幕截图,并将截图保存到一个路径下,文件名以当时时间进行组织(防止保存屏幕截图出现覆盖情况),那么就可以在AutoTestUtils类下加上屏幕截图的方法,方便其他类调用。
代码如下:
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.chrome.ChromeDriver;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
public class AutoTestUtils {
private static ChromeDriver driver;
//创建驱动
public static ChromeDriver getDriver() {
if(driver == null) {
driver = new ChromeDriver();
//创建隐式等待,设置最长等待时间为10秒
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
}
return driver;
}
/**
* 获取屏幕截图,将用例执行结果保留下来
* @param str
* @throws IOException
*/
public void getScreenShot(String str) throws IOException {
List<String> list = getTime();
String fileName = "./src/test/java/com/blogWebAutoTest/" + list.get(0) + "/" + str + "_" + list.get(1) + ".png";
File srcFile = driver.getScreenshotAs(OutputType.FILE);
//把生成的截图文件放入指定路径
FileUtils.copyFile(srcFile, new File(fileName));
}
/**
* 生成文件的保存时间
* @return
*/
private List<String> getTime() {
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyyMMdd-HHmmssSS");
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyyMMdd");
//当天的年月 + 具体时间
String fileName = sdf1.format(System.currentTimeMillis());
//当天的年月
String dirName = sdf2.format(System.currentTimeMillis());
List<String> list = new ArrayList<>();
list.add(dirName);
list.add(fileName);
return list;
}
}
在其他测试类中加上屏幕截图后,就可以观察到如下结果:
1.2.11、自动化测试的亮点
- 使用 junit5 中提供的注解,避免生成过多的对象,造成资源和时间上的浪费,提高了自动化执行的效率。
- 只创建一次驱动,避免每个用例重复创建驱动造成时间和资源的浪费。
- 使用参数化,保持用例的整洁,提高代码的可读性。
- 使用隐式等待,提高了自动化运行效率,提高了自动化的稳定性。
- 使用屏幕截图,方便问题的追溯以及解决。
1.2.10、自动化完整测试代码
以下是我的gitee码云:
自动化测试: automated-testing- - Gitee.com
1.3、性能测试
1.3.1、LoadRunner工具介绍
我们使用以上三个工具针对我们的项目进行性能测试。
a)Virtual User Generator(简称VUG):用来生成性能测试脚本。
b)Controller:创建测试场景,运行和监控场景。
c)Analysis:生成测试报告,分析性能测试结果。
1.3.1、 UI性能测试步骤
a)访问博客登录页;
b)执行登录;
c)进入首页。
1.3.2、编写性能测试脚本(VUG)
1、创建项目
由于我们测试的博客系统是一个Web项目,因此需要创建一个Web性能测试脚本,如下:
2、目录层次介绍
我们主要在Action里编写代码~
3、代码编写
主要的操作步骤为:1.打开网页,2.输入账号密码并登录;
在此期间,为了更好的让我们进行性能测试的数据收集,我们可以使用
- 事务:衡量性能的重要指标,通过观察每秒事务通过数来衡量性能;
- 集合点:让所有的虚拟用户执行到集合点时断在集合,满足条件后一起执行下一个步骤(保证每一个虚拟用户同时执行下一步);
- 检查点:可以用来检测当前页面的元素是否存在以及存在个数(检查点一般放在请求之前);
- 参数化:通过提供的数据源可以实现多个参数逐个执行;
如下代码:
Action()
{
//开始事务1
lr_start_transaction("index_trans");
//1.访问博客系统网页
web_url("web_url",
"URL=http://43.139.193.116:8080/blog_system/login.html",
"TargetFrame=",
"Resource=0",
"Referer=",
LAST);
//登录的集合点
lr_rendezvous("login_rendezvous");
//开始事务2
lr_start_transaction("login_trans");
//2.输入登录账号和密码
web_submit_form("web_submit_form",
ITEMDATA,
"Name=username", "Value={username}", ENDITEM,
"Name=password", "Value=123", ENDITEM,
LAST);
//结束事务2
lr_end_transaction("login_trans", LR_AUTO);
//结束事务1
lr_end_transaction("index_trans", LR_AUTO);
return 0;
}
执行结果和分析:
1.3.3、创建测试场景(Controller)
a)针对我们已经编写好的脚本打开controller工具,创建测试脚本,如下:
b)设置执行策略如下:
这个过程就像是舞台表演一样,如下:
c)场景运行结果
1.3.4、生成测试报告(Analysis)
在 controller 创建的场景中直接生成测试报告,如下:
测试报告如下:
测试报告图标如下:
1.虚拟用户运行图:
作用:通过显示的虚拟用户数量可以判断出哪个时间段服务器负载最大(上图00:40 ~ 01:40负载最大)。
2.点击数图(每秒点击率)
作用:通过点击率可以判断出某时间段内服务器的负载。
3.吞吐量图
问题一:为什么吞吐量图和点击数图相似,但是吞吐量图要滞后一点?
- 因为吞吐量表示的是响应返回的资源数量,肯定是现有请求再有返回。
问题二:如果请求变多但是吞吐量没变化,原因是什么?
- 服务器响应太慢,来不及反应;
- 压力没有到服务器;
- 服务器设计一定的阈值(到达阈值以后,虽然也收到了请求,但是服务器不会做任何处理),保证了服务器不会因为并发量过大而出现宕机的情况;
4.事务图
5.平均事务响应时间图
作用:可以观察到,虚拟用户在性能测试中,每秒在服务器上命中的次数,可以根据命中的次数评估虚拟用户生成的负载量。
小结
以上便是本次博客系统的测试报告~