五子棋在线对战系统测试报告

一.项目背景

本项目是基于 Spring Boot 3 独立开发的五子棋在线对战平台,面向多用户实时对弈场景。系统涵盖用户注册登录、JWT 身份认证、在线匹配、实时对局、认输结算及积分流水查询等核心功能。持久层采用 MyBatis-Plus 操作 MySQL,匹配队列与对局状态借助 Redis 实现高效缓存与排序;对局过程通过 WebSocket(STOMP) 完成落子、心跳等实时消息推送,并结合 RabbitMQ 支持异步消息处理。同时集成 Spring Security、Bean Validation、Lombok 与 Actuator,保障接口安全、参数校验与系统可观测性。

主要技术栈:Spring、Spring MVC、Spring Boot 3.2、Spring Security、Spring WebSocket、MySQL、MyBatis-Plus、Redis、RabbitMQ、JWT、Maven、HTML、CSS、JavaScript

技术亮点

  • 统一使用 Result 返回体与全局错误码定义,保证前后端交互格式一致,提升联调与测试效率
  • 基于 @RestControllerAdvice + @ExceptionHandler 实现全局异常处理,按业务模块划分异常类型,规范化错误返回,便于问题定位
  • 采用 Spring Security + JWT 过滤器完成登录鉴权与接口防护,未授权请求自动拦截,保障用户与对局数据安全
  • 基于 Redis Sorted Set 按积分就近匹配,支持加入/取消匹配与手动触发,提升在线对战匹配效率与公平性
  • 通过 WebSocket(STOMP)实现落子、心跳、状态同步等实时通信,降低对局延迟,提升对战体验
  • 对局状态采用 Redis 缓存 + MySQL 持久化的双写策略,兼顾实时读写性能与数据可靠性
  • 积分流水支持分页查询,对局结果驱动积分变更,形成完整的对战---结算---统计业务闭环

二.测试模块

1.用户模块

  • 登录功能
  • 注册功能
  • 验证身份信息
  • 查询当前用户
  • 查询当前用户状态
  • 查询用户信息

2.游戏模块

  • 匹配功能
  • 取消匹配功能
  • 获取房间信息功能

3.积分系统模块

  • 查询用户积分流水

三.测试用例设计

四.自动化测试

1.工具类Utils

createDriver():配置并创建谷歌驱动

ScreenShot():屏幕截图

login():使用默认账号从登录页进行登录

java 复制代码
package common;

import io.github.bonigarcia.wdm.WebDriverManager;
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.Duration;

public class Utils {
    public static WebDriver driver;

    public Utils(String url) {
        driver = createDriver();
        driver.get(url);
    }

    /**
     * 获取驱动
     * 获取驱动对象
     *
     * @return
     */

    public static WebDriver createDriver() {
        if (driver == null) {
            //  WebDriverManager.chromedriver().setup();
            ChromeOptions options = new ChromeOptions();
            options.addArguments("--remote-allow-origins=*");
            driver = new ChromeDriver(options);
            //通用等待
            driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));
        }
        return driver;
    }

    /**
     * 获取屏幕截图
     */

    public void ScreenShot(String str) throws IOException {
        // .src/test/java/images/2026-07-23/test01_14392010.png
        SimpleDateFormat sim1 = new SimpleDateFormat("yyyy-MM-dd");
        SimpleDateFormat sim2 = new SimpleDateFormat("HHmmssSS");

        String dirTime = sim1.format(System.currentTimeMillis());
        String fileTime = sim2.format(System.currentTimeMillis());

        String fileName = ".src/test/java/images" + dirTime + "/" + str + "_" + fileTime + ".png";

        File scrFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
        FileUtils.copyFile(scrFile, new File(fileName));

    }

    public static void login() throws InterruptedException {
        driver.findElement(By.cssSelector("#username")).sendKeys("xiaoliao");
        driver.findElement(By.cssSelector("#password")).sendKeys("q12345678");
        Thread.sleep(1000);
        driver.findElement(By.cssSelector("#loginBtn")).click();
    }
}

2.注册页面RegisterPage

CheckPageRight():检查页面元素是否正确加载

RegisterFail():测试注册失败的情况

RegisterSuccess():测试注册成功的情况

java 复制代码
package tests;

import common.Utils;
import org.openqa.selenium.By;

import java.util.Random;

public class RegisterPage extends Utils {
    public static String url = "http://1.13.23.131:8080/register.html";

    public RegisterPage() {
        super(url);
    }

    /**
     * 检查页面元素是否存在
     */

    public void CheckPageRight(){
        driver.findElement(By.cssSelector("body > div > div > div.auth-header > div"));
        driver.findElement(By.cssSelector("body > div > div > div.auth-header > h1"));
        driver.findElement(By.cssSelector("#username"));
        driver.findElement(By.cssSelector("#password"));
        driver.findElement(By.cssSelector("#confirmPassword"));
        driver.findElement(By.cssSelector("#registerBtn"));
        driver.findElement(By.cssSelector("#registerForm > div.auth-footer > p > a"));
        driver.findElement(By.cssSelector("body > div > div > div.auth-footer > p:nth-child(1)"));
    }
    /**
     * 测试注册失败的场景
     * --账号与密码为空
     * --账号为空,密码不为空
     * --账号已存在
     * --密码为空
     * --密码不符合要求
     * --密码使用特殊字符
     */

    public void RegisterFail(){
        //账号与密码为空
        driver.findElement(By.cssSelector("#registerBtn")).click();
        //账号为空
        driver.findElement(By.cssSelector("#password")).sendKeys("q12345678");
        driver.findElement(By.cssSelector("#confirmPassword")).sendKeys("q12345678");
        driver.findElement(By.cssSelector("#registerBtn")).click();
        //账号已存在
        driver.findElement(By.cssSelector("#username")).sendKeys("xiaoliao");
        driver.findElement(By.cssSelector("#registerBtn")).click();
        //密码为空
        driver.findElement(By.cssSelector("#username")).clear();
        driver.findElement(By.cssSelector("#username")).sendKeys("xiaoliao1");
        driver.findElement(By.cssSelector("#password")).clear();
        driver.findElement(By.cssSelector("#confirmPassword")).clear();
        driver.findElement(By.cssSelector("#registerBtn")).click();

        //密码不符合规则
        driver.findElement(By.cssSelector("#password")).sendKeys("12345678");
        driver.findElement(By.cssSelector("#confirmPassword")).sendKeys("12345678");
        driver.findElement(By.cssSelector("#registerBtn")).click();

        //密码使用特殊字符
        driver.findElement(By.cssSelector("#password")).sendKeys("_@");
        driver.findElement(By.cssSelector("#confirmPassword")).sendKeys("_@");
        driver.findElement(By.cssSelector("#registerBtn")).click();

    }

    public void RegisterSuccess() throws InterruptedException {
        driver.findElement(By.cssSelector("#username")).clear();
        driver.findElement(By.cssSelector("#password")).clear();
        driver.findElement(By.cssSelector("#confirmPassword")).clear();
        driver.findElement(By.cssSelector("#username")).sendKeys("xiaoliao1");
        driver.findElement(By.cssSelector("#password")).sendKeys("q12345678");
        driver.findElement(By.cssSelector("#confirmPassword")).sendKeys("q12345678");
        Thread.sleep(1000);
        driver.findElement(By.cssSelector("#registerBtn")).click();

    }

}

3.登录页面LoginPage

CheckPageRight():检查页面元素是否正确加载

LoginFail():测试登录失败的情况

LoginSuccess():测试登录成功的情况

java 复制代码
package tests;

import common.Utils;
import org.openqa.selenium.By;

import java.time.Duration;

public class LoginPage extends Utils {
    public static String url = "http://1.13.23.131:8080/login.html";
    public LoginPage() {
        super(url);
    }

    /**
     * 检查页面元素是否加载
     */
    public void checkPageRight(){
        driver.findElement(By.cssSelector("body > div > div > div.auth-header > div"));
        driver.findElement(By.cssSelector("body > div > div > div.auth-header > h1"));
        driver.findElement(By.cssSelector("#username"));
        driver.findElement(By.cssSelector("#password"));
        driver.findElement(By.cssSelector("#loginBtn"));
        driver.findElement(By.cssSelector("#loginForm > div.auth-footer > p > a"));
    }

    /**
     * 测试登录失败的场景
     *--账户与密码为空
     *--账户为空
     *--密码为空
     *--密码错误
     */
    public void loginFail(){
        //账户与密码为空
        driver.findElement(By.cssSelector("#loginBtn")).click();
        //账户为空
        driver.findElement(By.cssSelector("#password")).sendKeys("q12345678");
        driver.findElement(By.cssSelector("#loginBtn")).click();
        //密码为空
        driver.findElement(By.cssSelector("#password")).clear();
        driver.findElement(By.cssSelector("#username")).sendKeys("xiaoliao");
        driver.findElement(By.cssSelector("#loginBtn")).click();
        //密码错误
        driver.findElement(By.cssSelector("#password")).sendKeys("q123456789");
        driver.findElement(By.cssSelector("#loginBtn")).click();
    }

    /**
     * 测试登录成功场景
     */
    public void loginSuccess() throws InterruptedException {
        driver.findElement(By.cssSelector("#password")).clear();
        driver.findElement(By.cssSelector("#password")).sendKeys("q12345678");
        driver.findElement(By.cssSelector("#loginBtn")).click();
    }
}

4.大厅页面HallPage

checkPageRight():检查页面元素是否正确加载

Match():测试匹配功是否正常

java 复制代码
package tests;

import common.Utils;
import org.openqa.selenium.By;

import java.io.IOException;

public class HallPage extends Utils {

    public static String url = "http://1.13.23.131:8080/login.html";
    public HallPage() {
        super(url);
    }

    /**
     * 检查元素是否存在
     */
    public void checkPageRight() throws InterruptedException {
        login();
        driver.findElement(By.cssSelector("body > div.top-nav > div > div.nav-logo"));
        driver.findElement(By.cssSelector("#navUsername"));
        driver.findElement(By.cssSelector("body > div.top-nav > div > div.nav-user > button"));
        driver.findElement(By.cssSelector("body > div.container > div > div.user-panel > h2"));
        driver.findElement(By.cssSelector("body > div.container > div > div.user-panel"));
        driver.findElement(By.cssSelector("#matchBtn"));
        driver.findElement(By.cssSelector("body > div.container > div > div.history-panel > h2"));
        driver.findElement(By.cssSelector("body > div.container > div > div.history-panel"));
    }

    /**
     * 检查匹配功能能否使用
     */

    public void Match() throws IOException, InterruptedException {
        //已启动其他客户端进行匹配,检查能否匹配成功
        driver.findElement(By.cssSelector("#matchBtn")).click();
        //匹配成功后进行截图
        Thread.sleep(5000);
        ScreenShot(getClass().getName());
    }
}

五.接口测试

1.用户接口

2.游戏接口

3.积分接口

六.性能测试

1.TPS监听器

2.汇总报告

3.响应超时时间(Response Times Over Time)

4.测试报告

七.Bug说明

Bug-01【严重】匹配接口高失败率(约 64%~66%)

  • 现象:加入匹配大量失败,Apdex 最低(约 0.338)
  • 影响:无法正常进入对局,核心业务不可用
  • 可能原因:并发下 Redis 队列竞态、用户重复入队、鉴权 userId 不一致、匹配状态机异常、连接/锁竞争

Bug-02【严重】取消匹配与加入匹配同比例失败(约 64%~66%)

  • 现象:取消失败样本数与匹配失败几乎一致
  • 影响:用户可能卡在匹配中,存在体验问题与资源泄漏风险
  • 可能原因:join 失败后仍调用 cancel、队列中无记录却取消、状态未同步

Bug-03【严重】积分流水查询高失败率(约 46%)

  • 现象:读接口接近一半失败
  • 影响:战绩 / 积分页不可用
  • 可能原因:数据库压力、分页 SQL 问题、用户无数据时异常未兜底、连接池耗尽

Bug-04【高】获取房间信息失败率高 + 超长响应

  • 现象:错误率约 34%~36%,Max 可达约 14s~30s
  • 影响:进房 / 刷新房间失败,对局流程中断
  • 可能原因:roomId 无效(匹配未成功)、Redis 无缓存回源慢、锁等待、超时配置过长

Bug-05【高】登录接口零失败但极端超时

  • 现象:错误率 0%,Max ≈ 29s,99 分位可达约 7s;末段响应时间图垂直拉升
  • 影响:高峰登录卡住,易被用户感知为"系统挂掉"
  • 可能原因:DB / Redis 变慢、密码校验耗 CPU、线程池打满后排队

Bug-06【中】鉴权类读接口偶发失败(约 11%~16%)

  • 涉及:当前用户、用户当前状态、查询用户信息
  • 可能原因:Token 失效 / 并发覆盖、压测脚本 Token 复用不当、Security 过滤器异常、下游超时

Bug-07【中】整体稳定性不足(总失败约 28%)

  • 现象:TPS 抖动、多接口同步下跌、末期性能塌陷
  • 说明:不仅是单接口逻辑问题,还存在系统级瓶颈(连接池、线程、Redis / MySQL、锁)

八.总结

认证链路(登录、Token 验证)基本可用;匹配---房间---积分主链路在当前压力下不可接受。总失败率约 28%,匹配相关失败超过六成,末期出现近 30s 超时,性能与稳定性均不达标。

后续改进建议:

  • 优先修复匹配 / 取消匹配的并发与状态一致性问题(Bug-01 / Bug-02)
  • 排查积分流水与房间信息失败原因及慢查询(Bug-03 / Bug-04)
  • 针对登录峰值优化耗时、连接池与超时配置(Bug-05)
相关推荐
小林ixn3 天前
大模型的“高考成绩单”:读懂Benchmark,选对真·生产力模型
人工智能·llm·测试
ClouGence3 天前
哪些场景适合做自动化测试?这 4 类最值得优先投入
selenium·测试
ClouGence3 天前
Selenium vs CueCast:自动化测试一定要写代码吗?
selenium·测试
花椒技术4 天前
原本要 2 天的服务端冒烟前置审查,为什么 3 分半就能出报告?|QA 质量交付实践(三)
后端·ai编程·测试
ClouGence5 天前
自动化测试,如何让每个用例都保持登录状态?
selenium·测试
ClouGence5 天前
手动测试工程师有必要学自动化测试吗?手工测试还有前途吗?
selenium·测试·求职
货拉拉技术5 天前
AI 生成接口自动化:从“随机抽奖”到“确定性交付”的 工程实践
测试