文章目录
标题一、项目背景
校园表白墙网站系统采用前后端分离的方法来实现,同时使用了数据库来存储相关的数据。前端主要有六个页面构成:注册页、登录页、表白帖列表页、表白帖详情页、表白帖编辑页、表白帖修改页,以上模拟实现了最简单的校园表白墙网站系统。其结合后端实现了以下的主要功能:用户注册、登录、注销、编辑表白帖、修改表白帖、以及强制登录等、删除表白帖功能。
项目源代码:https://gitee.com/qklzzb/blog-system
标题二、项目功能
校园表白墙网站系统主要实现了以下几个功能:用户注册、登录、注销、发表表白帖以及修改表白帖、删除表白帖等功能。
-
注册功能:用户通过输入用户名、密码以及学校名称进行注册,如果点击注册按钮则跳转到登录页面;如果输入的用户名、密码、学校名称三者至少有一个为空,则提示用户注册失败。
-
登录功能:用户输入用户名和密码,如果输入正确的用户名和密码则登录成功并跳转到表白墙列表页面;如果输入错误,则提示用户输入的信息有误,登录失败。
-
展示表白墙列表功能:表白墙系统主页可展示该系统上所有用户发表的表白帖。
-
发表表白功能:用户在登录的状态下可进行发表表白,对于发表的表白需要验证表白的标题和内容,两者均不能为空内容,如果两者都不为空内容则点击发表表白,表白帖发表成功,反之发表失败。
-
展示表白帖详情功能:展示表白帖的正文所有内容以及标题,并且在表白帖详情页会显示出该表白帖的作者信息。
-
修改表白帖功能:用户可以修改自己已发表的表白内容,进入表白帖详情页面,点击编辑进入修改页面,修改完成之后,点击更新则修改成功,反之修改失败。修改完成之后会跳转到表白墙列表页面。
-
删除表白帖功能:用户可以删除自己已发表的表白内容,进入表白帖详情页面,点击删除则表白帖删除成功(使用逻辑删除),反之删除失败。删除完成之后会跳转到表白墙列表页面。
-
注销登录:用户进行正常登录后,如若需要退出登录,点击右上角的注销按钮,则退出登录并且跳转到登录页面。
标题三、自动化测试
1. 编写测试用例
看不清的话可以进行下载,或者通过链接进行查看:表白墙系统测试用例
2.编写自动化测试代码
在我们的Web自动化测试项目中,需要对登录页、表白墙列表页、表白帖编辑页、表白帖详情页,这几个页面分别进行针对性测试。
对功能主流程进行自动化测试:
- 公共类
创建驱动、保存现场截图、关闭驱动
在进行测试时都需要创建驱动和使用驱动,如果每一个类中我们都去创建驱动,最后释放驱动,这样的过程是十分消耗资源的,那么我们便可以把创建驱动这个操作放在一个类中,并设置驱动对象为静态的,就可以让创建和销毁驱动的步骤执行一次,其他类如果需要驱动直接继承该类即可
public class InitAndEnd {
//浏览器驱动
static WebDriver webDriver;
/**
* 创建驱动
*/
@BeforeAll
static void init() {
//创建一个Chrome浏览器驱动
webDriver = new ChromeDriver();
}
/**
* 动态生成时间
*/
public List<String> getTime() {
//文件夹按照天的维度进行保存
//文件格式 20240109-123030
SimpleDateFormat simpleDateFormat1 = new SimpleDateFormat("yyyyMMdd-HHmmssSS");
SimpleDateFormat simpleDateFormat2 = new SimpleDateFormat("yyyyMMdd");
String filename = simpleDateFormat1.format(System.currentTimeMillis());
String dirname = simpleDateFormat2.format(System.currentTimeMillis());
List<String> list = new ArrayList<>();
list.add(dirname);
list.add(filename);
return list;
}
/**
* 获取屏幕截图,把所有的测试用例执行的结果保存下来
*/
public void getScreenShor(String str) throws IOException {
List<String> list = getTime();
//dir+filename
// ./src/test/java/com/blogWebAutoTest/dirname/filename
String fileName = "./src/test/java/" + list.get(0) + "/" + str + "-" + list.get(1) + ".png";
File screenshotAs = ((TakesScreenshot) webDriver).getScreenshotAs(OutputType.FILE);
//把屏幕截图生成的文件放到指定的路径
FileUtils.copyFile(screenshotAs, new File(fileName));
}
/**
* 关闭驱动
*/
@AfterAll
static void close() {
//关闭浏览器驱动
webDriver.quit();
}
}
- 测试登录功能
测试登录,输入正确的账号密码,跳转到表白墙列表页面
① 驱动已经创建好则不需再次创建驱动,打开登录页面;
② 测试正常登录:用户名/密码正确的情况下,是否跳转到表白墙列表页面;
③ 表白墙列表页面的用户是否为登录用户
注意测试的顺序,使用Order注解指定,否则可能会因为执行顺序不对导致测试失败;
public static Stream<Arguments> generate() {
return Stream.of(Arguments.arguments("zhangsan", "123456", "http://127.0.0.1:8080/blog_list.html"));
}
/**
* 测试登录,输入正确的账号密码,跳转到表白墙列表页面
*/
@Order(1)
@ParameterizedTest
@MethodSource(value = "generate")
void LoginSuccess(String userName, String password, String expect_url) throws InterruptedException {
//进入登陆页面
webDriver.get("http://127.0.0.1:8080/blog_login.html");
//输入账号
webDriver.findElement(By.cssSelector("#username")).sendKeys(userName);
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
//输入密码
webDriver.findElement(By.cssSelector("#password")).sendKeys(password);
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
//点击提交按钮
webDriver.findElement(By.cssSelector("#submit")).click();
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
//判断页面是否跳转到表白墙列表页面
String cur_url = webDriver.getCurrentUrl();
Assertions.assertEquals(expect_url, cur_url);
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
//判断登录用户是否正确
String cur_userName = webDriver.findElement(By.cssSelector("body > div.container > div.left > div > h3")).getText();
Assertions.assertEquals(userName, cur_userName);
}
- 表白墙列表页面博客信息
/**
* 表白墙列表页面,表白帖的数目不为0
*/
@Order(2)
@Test
public void list_num() {
webDriver.get("http://127.0.0.1:8080/blog_list.html");
//获取列表页表白帖的数量
int blog_num = webDriver.findElements(By.cssSelector(".title")).size();
//判断表白帖的数量是否不为0
Assertions.assertNotEquals(0, blog_num);
}
- 发布表白内容
表白内容发布之后,跳转到表白墙列表页面
① 驱动已经创建好则不需再次创建驱动,打开写表白页面;
② 写表白内容之后及进行发布,检验是否跳转到表白墙列表页面;
③ 表白墙列表页面的第一条表白内容是否为刚刚发布的表白信息(表白帖的标题和时间是否正确)
/**
* 写表白并且发布表白内容
*/
@Order(3)
@ParameterizedTest
@CsvFileSource(resources = "title.csv")
void writeAndPublish(String title) throws InterruptedException {
//进入写表白页面
webDriver.get("http://127.0.0.1:8080/blog_edit.html");
//写表白内容
webDriver.findElement(By.cssSelector("#title")).sendKeys(title);
//使用JS的方式写表白信息的标题
// ((JavascriptExecutor)webDriver).executeScript("document.getElementById(\"title\").value=\"自动化测试\""); //使用JS时不能使用变量
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
// webDriver.findElement(By.cssSelector("#content")).sendKeys("测试");
//提交表白的内容
webDriver.findElement(By.cssSelector("#submit")).click();
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
sleep(3000);
//判断页面是否进行跳转
String cur_url = webDriver.getCurrentUrl();
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
Assertions.assertEquals("http://127.0.0.1:8080/blog_list.html", cur_url);
}
/**
* 校验已发布表白帖的标题,同时校验已发布表白帖的时间
*/
@Order(4)
@ParameterizedTest
@CsvFileSource(resources = "title.csv")
void BlogInfoChecked(String title) throws InterruptedException {
//进入表白墙列表页面
webDriver.get("http://127.0.0.1:8080/blog_list.html");
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
//校验第一个表白帖的标题
String cur_title = webDriver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > div.title")).getText();
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
//判断第一篇表白的title和csv文件当中的字符串是否相同
Assertions.assertEquals(title, cur_title);
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
//校验第一篇表白的时间是否和预期时间相同
String cur_time = webDriver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > div.date")).getText();
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
if (cur_time.contains("2024-04-06")) {
System.out.println("测试通过");
} else {
System.out.println("测试不通过");
}
}
- 表白详情页面测试
① 驱动已经创建好则不需再次创建驱动,打开表白墙列表页面;
② 点击查看详情,是否跳转到表白帖详情页面;
③ 查看表白帖信息是否为列表第一条表白的信息(查看表白帖的标题和时间是否正确)
/**
* 检验表白帖的详情
*/
public static Stream<Arguments> generate2() {
return Stream.of(Arguments.arguments("表白帖详情页", "自动化测试", "http://127.0.0.1:8080/blog_detail.html?blogId="));
}
@Order(5)
@ParameterizedTest
@MethodSource(value = "generate2")
void testDetail(String expect_title, String expect_blog_title, String expect_url) {
webDriver.get("http://127.0.0.1:8080/blog_list.html");
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
//点击查看全文
webDriver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > a")).click();
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
//判断页面标题
String cur_title = webDriver.getTitle();
Assertions.assertEquals(expect_title, cur_title);
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
//判断表白帖title是否为自动化测试
String cur_blog_title = webDriver.findElement(By.cssSelector("body > div.container > div.right > div > div.title")).getText();
Assertions.assertEquals(expect_blog_title, cur_blog_title);
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
//判断是否跳转表白帖详情页面
String cur_url = webDriver.getCurrentUrl();
if (cur_url.contains(expect_url)) {
System.out.println("测试通过");
} else {
System.out.println("测试不通过");
}
}
- 删除表白帖测试
① 驱动已经创建好则不需再次创建驱动,打开表白帖详情页面;
② 点击删除按钮,查看是否跳转到表白墙列表页面;
③ 表白墙列表页面的第一条表白帖是否为刚刚删除的表白帖信息
/**
* 删除刚才写的表白信息
*/
@Order(6)
@ParameterizedTest
@CsvFileSource(resources = "title.csv")
void testDelete(String title) throws InterruptedException {
webDriver.get("http://127.0.0.1:8080/blog_list.html");
webDriver.manage().timeouts().implicitlyWait(3,TimeUnit.SECONDS);
//点击查看全文
webDriver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > a")).click();
webDriver.manage().timeouts().implicitlyWait(3,TimeUnit.SECONDS);
//点击删除按钮
webDriver.findElement(By.cssSelector("body > div.container > div.right > div > div.operating > button:nth-child(2)")).click();
webDriver.manage().timeouts().implicitlyWait(3,TimeUnit.SECONDS);
//第一条表白的标题不是自动化测试
String cur_title = webDriver.findElement(By.cssSelector("body > div.container > div.right > div > div.title")).getText();
webDriver.manage().timeouts().implicitlyWait(3,TimeUnit.SECONDS);
Assertions.assertNotEquals(title,cur_title);
}
3.使用测试套件进行代码测试
采用测试套件,可以降低测试人员的工作量,通过套件一次执行所有要运行的测试用例。
@Suite
@SelectClasses({BlogCases.class})
public class RunSuite {
}
所有测试用例通过:
标题四、web自动化测试总结
-
提高测试效率:自动化测试可以快速执行测试用例,比手动测试更高效。
-
只创建一次驱动对象,避免每个用例重复创建驱动对象造成时间和资源的浪费。
-
使用参数化:保持用例的简洁,提高代码的可读性。
-
使用测试套件:降低了测试人员的工作量,通过套件一次执行所有要运行的测试用例。
-
使用了等待:提高了自动化的运行效率,提高了自动化的稳定性,减小误报的可能性。
-
使用了Junit5中提供的注解:避免生成过多的对象,造成资源和时间的浪费,提高了自动化的执行效率。
-
增加测试覆盖范围:自动化测试可以覆盖更多的测试场景和用例,提高软件质量。
-
重复执行测试:自动化测试可以反复执行相同的测试用例,确保软件在不同环境下的稳定性。
标题五、性能测试
5.1 loadRunner介绍
我们使用以上三个工具针对我们的项目进行性能测试。
a)Virtual User Generator(简称VUG):用于录制用户操作脚本,可以模拟用户在应用程序上执行的操作。就是通过录制或编写脚本来模拟用户的行为,同时会打印出日志信息,方便调试脚本;VuGen也是一个集成开发调试环境,在这里完成脚本开发并调试通过后就可以放到Controller中创建场景。
b)Controller:创建测试场景,运行和监控场景。用于管理并协调多个虚拟用户同时访问应用程序,以模拟真实的用户负载。
c)Analysis:生成测试报告,分析性能测试结果。用于分析性能测试结果,生成报告并提供性能指标,如响应时间、吞吐量等。
5.2 编写测试脚本(Virtual User Generator)
主要的操作步骤为:
-
录制脚本 :a)访问登录页;b)输入用户名密码进行登录;c)进入表白墙列表页面
-
脚本加强
在此期间,为了更好的让我们进行性能测试的数据收集,我们可以使用下面这些来加强脚本- 事务:衡量性能的重要指标,通过观察每秒事务通过数来衡量性能;
- 集合点:让所有的虚拟用户执行到集合点时断在集合,满足条件后一起执行下一个步骤(保证每一个虚拟用户同时执行下一步);
- 检查点:可以用来检测当前页面的元素是否存在以及存在个数(检查点一般放在请求之前);
- 参数化:通过提供的数据源可以实现多个参数逐个执行;
Action()
{
//开始事务
lr_start_transaction("login");
//插入集合点
lr_rendezvous("rendezous");
web_url("blog_login.html",
"URL=http://192.168.193.128:8080/blog_login.html",
"Resource=0",
"Referer=",
"Snapshot=t1.inf",
"Mode=HTML",
EXTRARES,
"Url=/css/common.css", ENDITEM,
"Url=/css/login.css", ENDITEM,
"Url=/pic/logo2.jpg", ENDITEM,
"Url=/js/jquery.min.js", ENDITEM,
"Url=/pic/back.jpg", "Referer=http://192.168.193.128:8080/css/common.css", ENDITEM,
"Url=/user/login?userName={userName}&password={password}", ENDITEM, //参数化
LAST);
//结束事务
lr_end_transaction("login", LR_AUTO);
return 0;
}
在Parameter中添加登陆的用户和密码(添加数据源)
4. 设置迭代次数
设置脚本执行次数,如果设置1,那么只有Parameters中的第一个用户登录(这里我设置为3,因为我添加了三个用户参数)
在日志设置中开启打印参数日志(便于观察当前测试的用户)
5. 运行脚本
测试通过
观察日志,查看用户登录的详情,进行结果分析。
5.3 创建测试场景(Controller)
针对我们已经编写好的脚本打开controller工具,创建测试场景,如下:
-
设置并发数,设置10个虚拟用户,防止电脑崩溃
-
在Controller中设计场景,具体设置如下:
初始化虚拟用户
开始运行虚拟用户
虚拟用户的运行时间
结束虚拟用户的过程
-
开始进行运行,性能测试开始(运行中+结束截图)
运行中截图
运行结束截图
5.4 生成测试报告(Analysis)
作用:通过显示的虚拟用户数量可以判断出哪个时间段服务器负载最大(上图1:05~ 02:05负载最大)。
作用:通过点击率可以判断出某时间段内服务器的负载。
问题一:为什么吞吐量图和点击数图相似,但是吞吐量图要滞后一点?
当用户发起点击请求时,点击数图记录了用户的操作次数,而吞吐量图表示系统在单位时间内处理的请求数量或事务数量。系统处理请求需要一定的时间,包括网络传输、服务器处理、数据库查询等过程,这些过程会导致吞吐量图相对于点击数图出现一定的滞后。
问题二:如果请求变多但是吞吐量没变化,原因是什么?
- CPU资源达到极限:处理请求的计算量过大,导致CPU负载很高,无法及时处理更多请求。
- 内存不足:请求处理过程中需要大量内存,系统内存不足会导致请求被拒绝或处理缓慢。
- 网络带宽受限:系统的网络带宽达到极限,无法承载更多的请求数据传输。
- 服务器响应太慢,来不及反应; 压力没有到服务器;
- 服务器设计一定的阈值(到达阈值以后,虽然也收到了请求,但是服务器不会做任何处理),保证了服务器不会因为并发量过大而出现宕机的情况;
作用:可以观察到,虚拟用户在性能测试中,每秒在服务器上命中的次数,可以根据命中的次数评估虚拟用户生成的负载量。