欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 房产 > 建筑 > 网页五子棋——项目测试

网页五子棋——项目测试

2025/2/26 19:53:14 来源:https://blog.csdn.net/2301_76161469/article/details/145811122  浏览:    关键词:网页五子棋——项目测试

目录

测试用例设计

功能测试

注册功能测试

正常注册

异常注册

登录功能测试

正常登录

异常登录

匹配功能测试

对战功能测试

自动化测试

引入依赖

Utils

注册测试

登录测试

匹配测试

RunTest

界面测试

性能测试

总结


测试用例设计

在本篇文章中,主要进行功能测试、界面测试和性能测试

功能测试

注册功能测试

正常注册

我们以 用户名:李四 密码:123456 为例进行注册

输入用户名和密码,点击注册按钮后,页面成功跳转至登录页面

 

异常注册

我们首先来看 用户名为空 的情况:

仅输入密码点击注册:

我们可以发现,虽然弹出了提示信息,但是提示的异常信息过多,其中大多数信息并不是客户端所需要的,因此,我们需要对其进行修改

为什么会打印上述错误信息呢?

由于我们使用 @Validated 注解来校验参数,而这些参数未通过验证时,就抛出了 MethodArgumentNotValidException 异常,而 MethodArgumentNotValidException 被作为未知异常进行捕获:

在处理未知异常时直接将错误信息返回给了前端

因此,我们可以对 MethodArgumentNotValidException 异常进行处理,当捕获到  MethodArgumentNotValidException 异常时,表明传递的参数出现异常,此时我们仅返回我们之前指定的 message 信息即可

GlobalErrorCodeConstants 中添加全局错误码:

    ErrorCode BAD_REQUEST = new ErrorCode(400, "客户端请求错误");

 在 GlobalExceptionHandler 中添加: 

    /*** 捕获 @Valid / @Validated 注解校验异常* @param e* @return*/@ExceptionHandler(value = MethodArgumentNotValidException.class)public CommonResult<?> validationException(MethodArgumentNotValidException e) {// 打印错误日志log.info("MethodArgumentNotValidException: ", e);// 获取所有字段验证错误String errors = e.getBindingResult().getFieldErrors().stream().map(error -> error.getDefaultMessage()).collect(Collectors.joining(", "));// 构造异常情况下的返回结果return CommonResult.fail(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), errors);}

调用 e.getBindingResult().getFieldErrors() 可以获取所有错误列表,再遍历这些错误,获取每个错误的错误消息,最后,再使用 Collectors.joining(", ") ,将所有的错误消息连接成一个字符串,以 , 作为分隔符

此时的提示信息就简洁清晰了许多:

此外,我们也可以在客户端发送请求之前对用户名和密码进行校验, 从而提升用户体验并减少不必要的服务器请求:

    <script>let btn = document.querySelector('#submit');btn.onclick = function() {let name =  $("#name").val();let password = $("#password").val();if (name == "") {alert("用户名不能为空");return;}if (null == "") {alert("用户密码不能为空");return;}$.ajax({url: "/register",type: "POST",contentType: 'application/json',data: JSON.stringify({name: name,password: password,}),success: function(result) {if(result.code == 200) {// 注册成功,跳转至登录页面location.assign("login.html");}else {alert(result.errorMessage);}}});}</script>

我们继续看注册已存在用户的情况:

再次注册 用户名:李四 密码:123456:

此时直接抛出了 SQLIntegrityConstraintViolationException 异常,提示插入一条新记录时,发生了违反唯一性约束的错误

这是因为我们在数据入库之前并未对用户名进行校验

添加用户名校验:

    @Overridepublic UserRegisterResultDTO register(UserRegisterParam param) {// 参数校验if (userMapper.selectByUserName(param.getName()) != null) {throw new ServiceException(ServiceErrorCodeConstants.USER_INFO_EXISTS);}if (!checkPassword(param.getPassword())) {throw new ServiceException(ServiceErrorCodeConstants.PASSWORD_CHECK_ERROR);}// 数据入库UserDO userDO = new UserDO();userDO.setUserName(param.getName());userDO.setPassword(SecurityUtil.encipherPassword(param.getPassword()));userMapper.insert(userDO);// 构造响应并返回UserRegisterResultDTO registerResultDTO = new UserRegisterResultDTO();registerResultDTO.setUserId(userDO.getId());return registerResultDTO;}

 添加 service 层错误码:

ErrorCode USER_INFO_EXISTS = new ErrorCode(102, "用户已存在");

此时再次尝试注册: 

接着,我们继续看 用户密码为空的情况:

提示信息正确

密码长度过长或过短:

此时的提示信息并不准确:

在密码校验失败时,抛出自定义的 PASSWORD_CHECK_ERROR 异常:

因此,我们对异常信息进行修改,分别定义注册和登录密码校验异常信息

 此外,在对密码进行校验时,我们仅对密码长度进行了校验

但除了对长度进行校验,我们还应该对其中的字符进行校验,防止其中出现中文或其他非 ASCII 字符的情况,从而出现编码问题,影响用户体验或导致登录失败

因此,我们校验密码为 6 - 12 位,且仅能使用数字或字母:

    private boolean checkPassword(String password) {if (!StringUtils.hasText(password)) {return false;}// 使用正则表达式校验密码长度应为 6-12 位,且只包含数字和字母String regex= "^[0-9A-Za-z]{6,12}$";return Pattern.matches(regex, password);}

 

登录功能测试

同样的,我们在客户端发送请求之前对用户名和密码进行校验:

正常登录

使用注册的 用户名和密码进行登录:

输入用户名和密码,点击登录按钮后,页面成功跳转至游戏大厅页面

异常登录

用户名为空或用户名错误:

密码为空或密码错误:

用户多开:

 再次登录 李四 账号:

点击确定后跳转至登录页面:

匹配功能测试

两个天梯分数相近的玩家进行匹配:

匹配成功,并显示对手信息

天梯分数较高玩家匹配天梯分数较低玩家:

 

此时,两名玩家分别加入了不同的匹配队列,因此,并不能进行匹配

玩家在匹配过程中取消匹配:

点击取消匹配后,将玩家从对应队列中移除

异常情况:

玩家在匹配过程中退出游戏房间:

将玩家从对应匹配队列中移除

对战功能测试

玩家1进入游戏房间后,等待对手进入房间:

两名玩家均进入游戏房间后,开始游戏:

玩家轮流落子:

胜负判定: 

异常情况:

一方玩家中途退出游戏房间:

匹配成功后,玩家1在等待玩家2过程中退出游戏房间: 

可以看到,玩家2成功进入游戏房间,但此时玩家1已退出游戏房间

因此,我们需要对对应逻辑进行修改:

在将玩家2加入游戏房间时,我们并未对玩家1的在线状态进行判断,此时,就导致了玩家1已经离开游戏房间,但玩家2仍进入游戏房间,且开始了游戏

修改后端对应逻辑:

修改删除逻辑:

添加错误码:

ErrorCode GET_RIVAL_ERROR = new ErrorCode(302, "获取对手信息失败");

 

匹配成功后,玩家1和玩家2均未加入游戏房间:

若匹配成功后,玩家1 和 玩家2 均为加入游戏房间,此时,就会存在空房间

在两个玩家匹配成功时,我们为其创建了游戏房间:

若两个玩家都不加入当前游戏房间,此时空的游戏房间就会一直存在,但这些空房间并不会再次被使用,因此,我们需要对这些空房间进行处理

我们使用定时器定期对这些空房间进行处理:

启用定时任务:

为了保证不误删刚创建的新房间,我们还需要对游戏房间的创建时间进行记录,若一个空房间存活时间超过 1h,则表明当前空房间已不会再使用

我们可以将创建时间添加至房间 ID 中:

RoomManager 中添加定时任务:

由于匹配成功后,双方玩家均未进入房间这种异常情况出现概率较低,因此,我们仅需要一天执行一次定时任务即可

自动化测试

使用 selenium 对五子棋的 注册、登录和匹配功能进行自动化测试,由于对战模块需要模拟真人对战,且落子下标不好定位,因此,就不进行自动化测试了

引入依赖

<!--        selenium --><dependency><groupId>org.seleniumhq.selenium</groupId><artifactId>selenium-java</artifactId><version>4.0.0</version></dependency>
<!--        驱动管理--><dependency><groupId>io.github.bonigarcia</groupId><artifactId>webdrivermanager</artifactId><version>5.5.3</version></dependency>
<!--        屏幕截图--><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.6</version></dependency>

 

Utils

接着,我们创建 Utils 类,用于存放自动化代码中的通用方法

public class Utils {public static WebDriver webDriver;/*** 创建 webDriver 并 访问指定 url* @param url*/public Utils(String url) {if (null == webDriver) {WebDriverManager.edgedriver().setup();EdgeOptions options = new EdgeOptions();// 允许访问所有连接options.addArguments("--remote-allow-origins=*");this.webDriver = new EdgeDriver(options);// 隐式等待 3 秒this.webDriver.manage().timeouts().implicitlyWait(java.time.Duration.ofSeconds(3));}webDriver.get(url);}/*** 关闭 WebDriver 和浏览器*/public void closeBrowser() {if (webDriver != null) {webDriver.quit();}}/*** 进行屏幕截图并将其保存到自定路径* 路径: ./src/tests/image/2025-2-24/LoginPage-17548130.png* @param str*/public void getScreenShot(String str) {try {DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HHmmssSS");String dirTime = dateFormatter.format(LocalDateTime.now());String fileTime = timeFormatter.format(LocalDateTime.now());// 使用 File.separator 来适应不同操作系统上的路径分隔符String filename = "src" + File.separator + "test" + File.separator + "image" + File.separator + dirTime + File.separator + str + "-" + fileTime + ".png";// 创建目录Path path = Paths.get(filename).getParent();if (path != null && !Files.exists(path)) {Files.createDirectories(path);}// 将截图存放到指定位置File srcFile = ((TakesScreenshot)webDriver).getScreenshotAs(OutputType.FILE);File destFile = new File(filename);FileUtils.copyFile(srcFile, destFile);} catch (IOException e) {// 更好的错误处理或日志记录System.err.println("截图失败: " + e.getMessage());e.printStackTrace();}}
}

 

注册测试

对注册功能进行测试:

public class RegisterPage extends Utils {private static String url = "http://49.108.48.236:8081/register.html";public RegisterPage() {super(url);}/*** 检查页面是否加载成功*/public void registerPageRight() {// 查看页面元素是否存在webDriver.findElement(By.cssSelector("body > div.register-container > div > div:nth-child(3) > span")); // 密码提示信息webDriver.findElement(By.cssSelector("body > div.nav")); // 导航栏webDriver.findElement(By.cssSelector("body > div.register-container > div > h3")); // 注册标题}/*** 注册成功测试*/public void registerSuc() {// 清除输入框内文本webDriver.findElement(By.cssSelector("#name")).clear();webDriver.findElement(By.cssSelector("#password")).clear();// 输入用户名和密码webDriver.findElement(By.cssSelector("#name")).sendKeys("zhaoliu");webDriver.findElement(By.cssSelector("#password")).sendKeys("123456");// 点击登录按钮webDriver.findElement(By.cssSelector("#submit")).click();// 通过页面标题检查是否登录成功String expect = webDriver.getTitle();assert expect.equals("登录");// 退回到注册页面webDriver.navigate().back();}/*** 注册失败 —— 账号错误 测试* 1. 账号为空* 2. 用户名已存在*/public void registerNameFail() {// 1. 未输入账号// 清除输入框内文本webDriver.findElement(By.cssSelector("#name")).clear();webDriver.findElement(By.cssSelector("#password")).clear();webDriver.findElement(By.cssSelector("#password")).sendKeys("123456");webDriver.findElement(By.cssSelector("#submit")).click();// 处理弹窗WebDriverWait wait = new WebDriverWait(webDriver, Duration.ofSeconds(5));wait.until(ExpectedConditions.alertIsPresent());// 等待弹窗出现Alert alert = webDriver.switchTo().alert();alert.accept();// 2. 账号已存在// 清除输入框内文本webDriver.findElement(By.cssSelector("#name")).clear();webDriver.findElement(By.cssSelector("#password")).clear();webDriver.findElement(By.cssSelector("#name")).sendKeys("zhangsan");webDriver.findElement(By.cssSelector("#password")).sendKeys("123456");webDriver.findElement(By.cssSelector("#submit")).click();// 处理弹窗wait = new WebDriverWait(webDriver, Duration.ofSeconds(5));wait.until(ExpectedConditions.alertIsPresent());// 等待弹窗出现alert = webDriver.switchTo().alert();alert.accept();}/*** 注册失败 —— 密码错误 测试* 1. 密码为空* 2. 密码过长或过短* 3. 密码中包含特殊字符*/public void registerPasswordFail() {// 1. 未输入密码// 清除输入框内文本webDriver.findElement(By.cssSelector("#name")).clear();webDriver.findElement(By.cssSelector("#password")).clear();webDriver.findElement(By.cssSelector("#name")).sendKeys("admin");webDriver.findElement(By.cssSelector("#submit")).click();// 处理弹窗WebDriverWait wait = new WebDriverWait(webDriver, Duration.ofSeconds(5));wait.until(ExpectedConditions.alertIsPresent());// 等待弹窗出现Alert alert = webDriver.switchTo().alert();alert.accept();// 2. 密码过长// 清除输入框内文本webDriver.findElement(By.cssSelector("#name")).clear();webDriver.findElement(By.cssSelector("#password")).clear();webDriver.findElement(By.cssSelector("#name")).sendKeys("admin");webDriver.findElement(By.cssSelector("#password")).sendKeys("1234591111111");webDriver.findElement(By.cssSelector("#submit")).click();// 处理弹窗wait = new WebDriverWait(webDriver, Duration.ofSeconds(5));wait.until(ExpectedConditions.alertIsPresent());// 等待弹窗出现alert = webDriver.switchTo().alert();alert.accept();// 3. 密码过短// 清除输入框内文本webDriver.findElement(By.cssSelector("#name")).clear();webDriver.findElement(By.cssSelector("#password")).clear();webDriver.findElement(By.cssSelector("#name")).sendKeys("admin");webDriver.findElement(By.cssSelector("#password")).sendKeys("12345");webDriver.findElement(By.cssSelector("#submit")).click();// 处理弹窗wait = new WebDriverWait(webDriver, Duration.ofSeconds(5));wait.until(ExpectedConditions.alertIsPresent());// 等待弹窗出现alert = webDriver.switchTo().alert();alert.accept();// 4. 密码中包含特殊字符// 清除输入框内文本webDriver.findElement(By.cssSelector("#name")).clear();webDriver.findElement(By.cssSelector("#password")).clear();webDriver.findElement(By.cssSelector("#name")).sendKeys("admin");webDriver.findElement(By.cssSelector("#password")).sendKeys("12345@");webDriver.findElement(By.cssSelector("#submit")).click();// 处理弹窗wait = new WebDriverWait(webDriver, Duration.ofSeconds(5));wait.until(ExpectedConditions.alertIsPresent());// 等待弹窗出现alert = webDriver.switchTo().alert();alert.accept();}
}

 

登录测试

public class LoginPage extends Utils {private static String url = "http://49.108.48.236:8081/login.html";public LoginPage() {super(url);}/*** 检查页面是否加载成功*/public void loginPageRight() {// 查看页面元素是否存在webDriver.findElement(By.cssSelector("body > div.login-container > div > div:nth-child(2) > span")); // 用户名提示信息webDriver.findElement(By.cssSelector("body > div.nav")); // 导航栏webDriver.findElement(By.cssSelector("body > div.login-container > div > div.register > a")); // 注册链接}/*** 登录成功测试*/public void loginSuc() {// 清除输入框内文本webDriver.findElement(By.cssSelector("#name")).clear();webDriver.findElement(By.cssSelector("#password")).clear();// 输入用户名和密码webDriver.findElement(By.cssSelector("#name")).sendKeys("zhangsan");webDriver.findElement(By.cssSelector("#password")).sendKeys("123456");// 点击登录按钮webDriver.findElement(By.cssSelector("#submit")).click();// 通过页面标题检查是否登录成功String expect = webDriver.getTitle();assert expect.equals("游戏大厅");getScreenShot(getClass().getName());// 退回到登录页面webDriver.navigate().back();}/*** 登录失败 —— 账号错误 测试* 1. 账号为空* 2. 用户名错误*/public void loginNameFail() {// 1. 未输入账号// 清除输入框内文本webDriver.findElement(By.cssSelector("#name")).clear();webDriver.findElement(By.cssSelector("#password")).clear();webDriver.findElement(By.cssSelector("#password")).sendKeys("123456");webDriver.findElement(By.cssSelector("#submit")).click();// 处理弹窗WebDriverWait wait = new WebDriverWait(webDriver, Duration.ofSeconds(5));wait.until(ExpectedConditions.alertIsPresent());// 等待弹窗出现Alert alert = webDriver.switchTo().alert();alert.accept();// 2. 账号错误// 清除输入框内文本webDriver.findElement(By.cssSelector("#name")).clear();webDriver.findElement(By.cssSelector("#password")).clear();webDriver.findElement(By.cssSelector("#name")).sendKeys("admin");webDriver.findElement(By.cssSelector("#password")).sendKeys("123456");webDriver.findElement(By.cssSelector("#submit")).click();// 处理弹窗wait = new WebDriverWait(webDriver, Duration.ofSeconds(5));wait.until(ExpectedConditions.alertIsPresent());// 等待弹窗出现alert = webDriver.switchTo().alert();alert.accept();}/*** 登录失败 —— 密码错误 测试* 1. 密码为空* 2. 用户名正确,密码错误*/public void loginPasswordFail() {// 1. 未输入密码// 清除输入框内文本webDriver.findElement(By.cssSelector("#name")).clear();webDriver.findElement(By.cssSelector("#password")).clear();webDriver.findElement(By.cssSelector("#name")).sendKeys("zhangsan");webDriver.findElement(By.cssSelector("#submit")).click();// 处理弹窗WebDriverWait wait = new WebDriverWait(webDriver, Duration.ofSeconds(5));wait.until(ExpectedConditions.alertIsPresent());// 等待弹窗出现Alert alert = webDriver.switchTo().alert();alert.accept();// 2. 密码错误// 清除输入框内文本webDriver.findElement(By.cssSelector("#name")).clear();webDriver.findElement(By.cssSelector("#password")).clear();webDriver.findElement(By.cssSelector("#name")).sendKeys("zhangsan");webDriver.findElement(By.cssSelector("#password")).sendKeys("123459");webDriver.findElement(By.cssSelector("#submit")).click();// 处理弹窗wait = new WebDriverWait(webDriver, Duration.ofSeconds(5));wait.until(ExpectedConditions.alertIsPresent());// 等待弹窗出现alert = webDriver.switchTo().alert();alert.accept();}
}

 

匹配测试

public class MatchPage extends Utils {private static String url = "http://49.108.48.236:8081/game_hall.html";public MatchPage() {super(url);}/*** 未登录状态下进入游戏大厅*/public void noLoginToMatch() {getScreenShot(getClass().getName());// 通过页面标题检查是否跳转到登录页面String expect = webDriver.getTitle();assert expect.equals("登录");}public void match() {// 清除输入框内文本webDriver.findElement(By.cssSelector("#name")).clear();webDriver.findElement(By.cssSelector("#password")).clear();// 1. 进行登录webDriver.findElement(By.cssSelector("#name")).sendKeys("zhangsan");webDriver.findElement(By.cssSelector("#password")).sendKeys("123456");webDriver.findElement(By.cssSelector("#submit")).click();// 2. 等待页面加载WebDriverWait wait = new WebDriverWait(webDriver, Duration.ofSeconds(5));wait.until(ExpectedConditions.elementToBeClickable(By.cssSelector("#match-button")));// 3. 开始匹配webDriver.findElement(By.cssSelector("#match-button")).click();wait.until(ExpectedConditions.textToBe(By.cssSelector("#match-button"), "匹配中...(点击停止)"));WebElement element = webDriver.findElement(By.cssSelector("#match-button"));String content = element.getText();assert content.equals("匹配中...(点击停止)");// 4. 停止匹配webDriver.findElement(By.cssSelector("#match-button")).click();wait.until(ExpectedConditions.textToBe(By.cssSelector("#match-button"), "开始匹配"));element = webDriver.findElement(By.cssSelector("#match-button"));content = element.getText();assert content.equals("开始匹配");}
}

 

RunTest

运行上述接口:

public class RunTest {public static void main(String[] args) {// 未登录状态下访问游戏大厅页面MatchPage matchPage = new MatchPage();matchPage.noLoginToMatch();// 注册测试RegisterPage registerPage = new RegisterPage();registerPage.registerPageRight();registerPage.registerNameFail();registerPage.registerPasswordFail();registerPage.registerSuc();// 登录测试LoginPage loginPage = new LoginPage();loginPage.loginPageRight();loginPage.loginNameFail();loginPage.loginPasswordFail();loginPage.loginSuc();// 进行匹配测试matchPage.match();matchPage.closeBrowser();}
}

 测试通过:

 

界面测试

注册登录页面正确显示:

游戏大厅页面正确显示:

对战页面:

可以看到,其中棋盘下方和右侧边缘并不能包含最后一个格子,因此,我们对棋盘进行修改:

此时就能保证正确棋盘的边框被绘制出来:

绘制棋子:

游戏结束:

返回大厅,更新对应信息:

 

 

性能测试

使用 JMeter 对五子棋的登录接口进行性能测试: 

创建梯度压测线程组(Stepping Thread Group),慢慢增大我们对接口的并发请求量:

创建 csv 文件,存放用户名和密码:

导入 csv 文件:

 

设置请求头:

 

添加请求:

查看结果:

聚合报告:

测试过程中并未发生异常情况,且 99% 的请求响应时间在 220ms 及以内

最大响应时间达到了1156 ms,这表明在某些时刻系统处理请求的速度显著下降

每秒处理事务数: 

 事务数在测试开始时逐渐增加,并在一段时间内保持相对稳定

响应时间:

 

响应时间在测试过程中有较大的波动,尤其是在某些时间段内出现了较高的峰值。这可能表明系统在高负载下存在性能瓶颈

总结

功能测试:

1. 五子棋游戏的基本功能正常运行,正常流程能够正确执行

2. 异常情况处理具有缺陷,对异常注册、异常登录 以及 异常进入游戏房间缺陷进行了修改

界面测试:
1. 所有按钮点击响应及时,页面显示良好,无遮挡或显示错误情况

2. 棋盘边框显示不完整,对棋盘进行了修改

性能测试:

1. 响应时间在测试过程中有较大的波动,尤其是在某些时间段内出现了较高的峰值

2. 可调整测试配置,增加线程数或调整循环次数,以测试更高并发用户数下性能

后续改进:

1. 针对响应时间的波动,进一步分析系统日志和监控数据 ,找出导致性能瓶颈的具体原因

2. 增加禁手规则更好地平衡游戏,减少先手玩家的优势

3. 限制玩家思考时间,从而增加游戏的挑战性和紧张感

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com