简朴博客系统测试报告

文章目录

  • [一. 项目简介](#一. 项目简介)
  • [二. 测试概要](#二. 测试概要)
  • [三. 测试环境](#三. 测试环境)
  • [四. 测试执行概况及功能测试](#四. 测试执行概况及功能测试)
    • [1. 手工测试](#1. 手工测试)
      • [1.1 手动测试用例编写](#1.1 手动测试用例编写)
      • [1.2 执行的部分测试用例](#1.2 执行的部分测试用例)
    • [2. 自动化测试Selenium](#2. 自动化测试Selenium)
      • [2.1 编写测试用例](#2.1 编写测试用例)
      • [2.2 自动化测试代码](#2.2 自动化测试代码)
    • [3. 测试结果](#3. 测试结果)
  • [五. 发现的问题](#五. 发现的问题)

一. 项目简介

简朴博客系统是采用前后端分离的方式来实现的,是基于 SpringFrameWork 和 MyBatis 框架实现的一个简易的博客发布网站,同时将其部署到了云服务器上。

目前博客系统主要实现了户的注册登录,文章的编写、发布,以及对自己文章的查看、修改、删除操作,个人文章列表及文章数统计这些;还可以分页显示所有作者汇总的文章列表,显示文章阅读量等。

使用 IDEA 开发,项目用到的技术有,SpringBoot, SpringMVC, MyBatis, MySQL, Redis, Lombok,HTML,CSS,JavaScript,jQuery 等。

二. 测试概要

测试对象:基于 SSM 项目的博客系统。

测试目的:校验博客项目功能是否符合自己的预期。

测试点:主要针对常用的主流程功能进行测试,如:注册、登录、汇总博客列表页、博客编辑页、个人博客列表页、导航栏组件等涉及到的功能。

测试方法和工具:主要是黑盒测试,自动化工具使用 Selenium 和 Junit。

三. 测试环境

硬件:Lenovo Yoga 14S 2021(R7-5800H/16GB/512GB/集显)。

浏览器:Google Chrome 版本 119.0.6045.160(正式版本) (64 位)。

操作系统:Windows 11。

测试工具:Selenium3 和 Junit5。

四. 测试执行概况及功能测试

1. 手工测试

1.1 手动测试用例编写

♨️注册页

♨️登录页

♨️个人博客列表页

♨️博客详情页

♨️博客编辑页

1.2 执行的部分测试用例

  1. 🍂登录页:界面能否正常加载,输入正确 / 错误的账号、密码是否能得到预期的响应。
    1️⃣界面能否正常加载。
    2️⃣账号正确,密码错误。
    预期结果:弹窗提示:"出错了: 登录失败, 请重新操作! 用户名或密码错误! "。
    实际结果如下:

    3️⃣账号正确,密码为空。
    预期结果:弹窗提示:"请输入密码! "。
    实际结果如下:

    4️⃣账号正确,密码正确。
    预期结果:页面跳转至个人博客列表页。
    实际结果如下:
  2. 个人博客列表页:检测界面是否符合预期,点击"查看全文"按钮是否能跳转到对应的博客详情页,点击"修改"按钮是否能跳转到博客编辑页并获取到待修改的标题和内容,点击"删除"按钮是否能成功删除文章,点击"注销"是否能退出登录。
    1️⃣界面显示符合预期。

    2️⃣点击"查看全文"按钮是否能跳转到对应的博客详情页。
    预期结果:进入到对应的博客详情页,且能够正确加载文章内容。
    实际结果如下:

    3️⃣点击"修改"。
    预期结果:点击修改后跳转到文章编辑页。
    实际结果如下:

    4️⃣点击"删除"。
    预期结果:点击删除后文章被删除。
    实际结果如下:
    5️⃣点击"注销"是否能退出登录。
    预期结果:点击注销后退出跳转到登录页面。
    实际结果如下:

2. 自动化测试Selenium

2.1 编写测试用例

2.2 自动化测试代码

🍂引入依赖:seleniumcommons-iojunitsuiteengine

java 复制代码
<dependencies>
    <!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
    <dependency>
        <groupId>org.seleniumhq.selenium</groupId>
        <artifactId>selenium-java</artifactId>
        <version>3.141.59</version>
    </dependency>
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.11.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.9.2</version>
        <scope>test</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-params -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-params</artifactId>
        <version>5.9.2</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.junit.platform/junit-platform-suite -->
    <dependency>
        <groupId>org.junit.platform</groupId>
        <artifactId>junit-platform-suite</artifactId>
        <version>1.9.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.9.1</version>
    </dependency>
</dependencies>

🍂初始化工具类InitAndEnd

java 复制代码
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;


import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;

public class InitAndEnd {
    static WebDriver webDriver;

    @BeforeAll
    static void SetUp() {
        // 创建 web 驱动
        webDriver = new ChromeDriver();
    }

    @AfterAll
    static void TearDown() {
        // 关闭 web 驱动
        webDriver.quit();
    }

    // 获取当前时间戳将截图按照时间保存
    public List<String> getTime() {
        // 文件夹以当天日期保存
        // 截图以当天日期-时间保存
        SimpleDateFormat sim1 = new SimpleDateFormat("yyyyMMdd");
        SimpleDateFormat sim2 = new SimpleDateFormat("yyyyMMdd-HHmmssSS");
        String dirname = sim1.format(System.currentTimeMillis());
        String filename = sim2.format(System.currentTimeMillis());
        List<String> list = new ArrayList<>();
        list.add(dirname);
        list.add(filename);
        return list;
    }

    // 获取屏幕截图,把所有的用例执行的结果保存下来
    public void getScreenShot(String str) throws IOException {
        List<String> list = getTime();
        String filename = "D:\\bit\\software_testing\\software-testing\\test-blog\\src\\main\\java\\com\\blog\\test" + list.get(0) + "\\" + str + "_" + list.get(1) + ".png";
        File srcfile = ((TakesScreenshot) webDriver).getScreenshotAs(OutputType.FILE);
        // 把屏幕截图生成的文件放到指定的路径
        FileUtils.copyFile(srcfile, new File(filename));
    }
}

🍂常用功能主流程测试

🍁LoginSuccess.csv

java 复制代码
admin, admin, http://47.113.217.156:8080/myblog_list.html

🍁RegCases

java 复制代码
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.openqa.selenium.By;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

import static java.lang.Thread.sleep;

public class RegCases extends InitAndEnd {
    @Order(1)
    @ParameterizedTest
    @CsvSource({"zhaoliu, 123, 123, http://47.113.217.156:8080/login.html"})
    void regSuccess(String username, String password, String againpassword, String login_url) throws InterruptedException, IOException {
        // 打开登录页
        webDriver.get(login_url);
        webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
        // 找到注册按钮并点击
        webDriver.findElement(By.cssSelector("body > div.nav > a:nth-child(5)")).click();
        webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
        // 输入注册的用户名和密码及确认密码
        webDriver.findElement(By.cssSelector("#username")).sendKeys(username);
        webDriver.findElement(By.cssSelector("#password")).sendKeys(password);
        webDriver.findElement(By.cssSelector("#password2")).sendKeys(againpassword);
        webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
        // 点击注册按钮
        webDriver.findElement(By.cssSelector("#submit")).click();
        sleep(3000);
        // 点击确认弹窗
        webDriver.switchTo().alert().accept();
        webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
        // 用新注册的账号进行登录
        // 输入账号 zhaoliu
        webDriver.findElement(By.cssSelector("#username")).sendKeys(username);
        webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
        // 输入密码 123
        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 butt = webDriver.findElement(By.cssSelector("#artListDiv > h3 > a")).getText();
        String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
        getScreenShot(methodName);
        Assertions.assertEquals("创作", butt);
    }
}

🍁BlogCases

java 复制代码
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.*;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;

import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;

import static java.lang.Thread.sleep;

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BlogCases extends InitAndEnd {
    /**
     * 登录页:输入正确的账号,错误的密码,登录失败
     */
    @Order(1)
    @ParameterizedTest
    @CsvSource({"admin, 123", "zhangsan, 666"})
    void LoginAbnormal(String username, String password) throws InterruptedException, IOException {
        // 打开登录页
        webDriver.get("http://47.113.217.156:8080/login.html");
        webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
        // 输入账号和密码
        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);
        sleep(300);
        //登录失败,出现弹窗
        //获取验证弹窗内容
        String text = webDriver.switchTo().alert().getText();
        String except = "出错了: 登录失败, 请重新操作! 用户名或密码错误!";
        webDriver.switchTo().alert().accept();
        String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
        getScreenShot(methodName);
        Assertions.assertEquals(except, text);
    }

    /**
     * 登录页:输入正确的账号,密码,登录成功
     */
    @Order(2)
    @ParameterizedTest
    @CsvFileSource(resources = "LoginSuccess.csv")
    void LoginSuccess(String username, String password, String blog_list_url) throws IOException, InterruptedException {
        System.out.println(username + " " +  " " +password + " " +  blog_list_url);
        // 打开登录页
        webDriver.get("http://47.113.217.156:8080/login.html");
        webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
        // 输入账号 admin
        webDriver.findElement(By.cssSelector("#username")).sendKeys(username);
        webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
        // 输入密码 admin
        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);
        sleep(3000);
        // 登录成功,跳转到个人列表页
        // 获取到当前页面 url
        String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
        getScreenShot(methodName);
        String cur_url = webDriver.getCurrentUrl();
        webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
        // 如果 url == http://47.113.217.156:8080/myblog_list.html,测试通过,否则测试不通过
        Assertions.assertEquals(blog_list_url, cur_url);
    }

    /**
     * 个人博客列表页:admin 账户登录后博客数量不为 0
     */
    @Order(3)
    @Test
    void BlogList() throws IOException {
        // 打开个人博客列表页
        webDriver.get("http://47.113.217.156:8080/myblog_list.html");
        // 获取页面上所有博客标题对应的元素
        webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
        int title_num = webDriver.findElements(By.cssSelector(".title")).size();
        // 如果元素数量不为 0,测试通过
        String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
        getScreenShot(methodName);
        Assertions.assertNotEquals(0 ,title_num);
    }

    /**
     * 个人博客列表页:查看全文
     * 博客详情页:
     * url
     * 博客标题
     * 页面 title 是 "博客详情"
     */
    public static Stream<Arguments> Generator() {
        return Stream.of(Arguments.arguments("http://47.113.217.156:8080/blog_content.html",
                "博客详情", "URL到页面: 探索网页加载的神秘过程"));
    }

    @Order(4)
    @ParameterizedTest
    @MethodSource("Generator")
    void BlogDetail(String expected_url, String expected_title, String expected_blog_title) throws IOException {
        // 打开个人博客列表页
        webDriver.get("http://47.113.217.156:8080/myblog_list.html");
        // 找到第一篇博客对应的查看全文按钮
        webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
        webDriver.findElement(By.cssSelector("#artListDiv > div:nth-child(1) > a:nth-child(4)")).click();
        // 获取当前页面 url
        webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
        String cur_url = webDriver.getCurrentUrl();
        webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
        // 获取当前页面 title
        String cur_title = webDriver.getTitle();
        webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
        // 获取博客标题
        String cur_blog_title = webDriver.findElement(By.cssSelector("#title")).getText();
        webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
        String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
        getScreenShot(methodName);
        Assertions.assertEquals(expected_title, cur_title);
        Assertions.assertEquals(expected_blog_title, cur_blog_title);
        Assertions.assertTrue(cur_url.contains(expected_url));
    }

    /**
     * 博客编辑页:发布文章
     */
    @Order(5)
    @Test
    void EditBlog() throws InterruptedException, IOException {
        // 打开个人博客列表页
        webDriver.get("http://47.113.217.156:8080/myblog_list.html");
        // 找到写博客按钮,点击
        webDriver.findElement(By.cssSelector("body > div.nav > a:nth-child(5)")).click();
        webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
        // 通过 Js 进行输入
        ((JavascriptExecutor) webDriver).executeScript("document.getElementById(\"title\").value=\"自动化测试\"");
        sleep(3000);
        // 点击发布文章按钮
        webDriver.findElement(By.cssSelector("body > div.blog-edit-container > div.title > button")).click();
        sleep(3000);
        // 验证发布成功后的弹窗内容
        String cur_text = webDriver.switchTo().alert().getText();
        String expect_text = "文章添加成功! 是否继续添加文章? ";
        // 点击取消弹窗
        webDriver.switchTo().alert().dismiss();
        String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
        getScreenShot(methodName);
        Assertions.assertEquals(expect_text, cur_text);
    }

    /**
     * 汇总列表页:验证博客成功发布
     * 校验第一篇博客标题
     * 校验第一篇博客时间
     */
    @Order(6)
    @Test
    void BlogInfoChecked() throws IOException {
        webDriver.get("http://47.113.217.156:8080/blog_list.html");
        // 获取第一篇博客标题
        String first_blog_title = webDriver.findElement(By.cssSelector("#artListDiv > div:nth-child(1) > div.title")).getText();
        // 获取第一篇博客发布时间
        String first_blog_time = webDriver.findElement(By.xpath("//*[@id=\"artListDiv\"]/div[1]/div[2]")).getText();
        String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
        getScreenShot(methodName);
        // 校验博客标题是不是自动化测试
        Assertions.assertEquals("自动化测试", first_blog_title);
        // 如果时间是 2023-11-18 年发布的,测试通过
        Assertions.assertTrue(first_blog_time.contains("2023-11-18"));
    }

    /**
     * 个人列表页:删除刚刚发布的博客
     */
    @Order(7)
    @Test
    void DeleteBlog() throws InterruptedException, IOException {
        // 打开个人博客列表页面
        webDriver.get("http://47.113.217.156:8080/myblog_list.html");
        // 点击删除按钮
        webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
        webDriver.findElement(By.cssSelector("#artListDiv > div:nth-child(1) > a:nth-child(6)")).click();
        webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
        sleep(3000);
        webDriver.switchTo().alert().accept();
        // 删除后博客列表页第一篇博客标题不是 "自动化测试"
        String first_blog_title = webDriver.findElement(By.xpath("//*[@id=\"artListDiv\"]/div[1]/div[1]")).getText();
        // 校验当前博客标题不等于 "自动化测试"
        webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
        String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
        getScreenShot(methodName);
        Assertions.assertNotEquals(first_blog_title, "自动化测试");
    }

    //注销
    @Order(8)
    @Test
    void Logout() throws IOException {
        // 打开个人博客列表页面
        //点击注销
        webDriver.get("http://47.113.217.156:8080/myblog_list.html");
        webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
        webDriver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)")).click();
        webDriver.switchTo().alert().accept();
        webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
        // 校验 url 注销后进入登录页
        String cur_url = webDriver.getCurrentUrl();
        String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
        getScreenShot(methodName);
        Assertions.assertEquals("http://47.113.217.156:8080/login.html", cur_url);
        webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
    }
}

🍂RunSuite,通过 class 运行测试用例。

java 复制代码
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;

@Suite
@SelectClasses({RegCases.class, BlogCases.class,})
public class RunSuite {
}

3. 测试结果

测试通过,整体的主流程业务操作是没有问题的。

测试截图如下:

五. 发现的问题

🎯手工测试过程中发现的问题

🍂问题描述:

博客汇总页在未登录的情况下,点击"我的"按钮,结果不符合预期。

预期结果:直接跳转到登录页。

实际结果:有时候会出现弹窗提示错误,关闭弹窗后也不能直接跳转到登录页,需要刷新页面才能成功跳转。

🍂原因分析:

问题的根本原因可能在于异步请求的特性和后端拦截器的重定向,异步请求是非阻塞的,即在请求发送的过程中,代码会继续往下执行而不会等待请求完成。

在拦截器中使用response.sendRedirect进行重定向时,实际上是在响应中设置了一个重定向的状态,但对于异步请求而言,这个重定向的状态可能无法被正确处理,导致浏览器不会直接跳转到登录页,因为异步请求的结果是在JavaScript中处理的,而不是在浏览器地址栏中执行的。

这就导致了在异步请求中执行重定向时,可能会产生不确定的行为,因为重定向的结果可能无法按照预期顺序执行。

🍂造成问题的代码定位:

🍂解决方案:

修改前端代码,通过 JS 在 success 回调中判断返回的 res 中的code,如果是未登录状态,则手动跳转到登录页,以此来规避异步请求中可能产生的问题,确保在未登录时能够及时跳转到登录页。

🎯自动化程序编写过程碰到的问题

一些自动化操作是不能在弹窗的情况下完成操作的(比如截图),如果在测试程序执行报unexpected alert open: {Alert text : ...}这种异常,那么就是你没有将弹窗关闭掉,可以使用 accept() 方法确认弹窗或者 dismiss() 取消弹窗后再执行相关操作。

相关推荐
夏天的味道٥2 小时前
使用 Java 执行 SQL 语句和存储过程
java·开发语言·sql
冰糖码奇朵4 小时前
大数据表高效导入导出解决方案,mysql数据库LOAD DATA命令和INTO OUTFILE命令详解
java·数据库·sql·mysql
好教员好4 小时前
【Spring】整合【SpringMVC】
java·spring
浪九天5 小时前
Java直通车系列13【Spring MVC】(Spring MVC常用注解)
java·后端·spring
堕落年代5 小时前
Maven匹配机制和仓库库设置
java·maven
功德+n5 小时前
Maven 使用指南:基础 + 进阶 + 高级用法
java·开发语言·maven
车载测试工程师6 小时前
车载以太网测试-3【Wireshark介绍】
经验分享·功能测试·网络协议·车载系统
SuperCreators6 小时前
DeepSeek与浏览器自动化AI Agent构建指南
人工智能·自动化
香精煎鱼香翅捞饭6 小时前
java通用自研接口限流组件
java·开发语言
Alan-Xia6 小时前
使用jest测试用例之入门篇
前端·javascript·学习·测试用例