随着互联网的飞速发展和普及,网络娱乐方式日益多样化。五子棋作为一种传统而经典的棋类游戏,深受广大棋迷的喜爱。然而,传统的五子棋游戏方式往往受限于地点和时间,无法满足人们随时随地进行游戏的需求。因此,开发一款网页版五子棋项目,旨在打破这种限制,让棋迷们能够随时随地通过网络享受五子棋的乐趣。
用户注册与登录:用户可以通过注册页面注册账号,填写用户名、密码等信息进行注册,注册成功后可在登录页面输入账号密码登录。
用户信息管理:系统能够记录用户的相关信息,如用户id、用户名、密码、天梯分数、排位总场次和胜场总场次等,并提供修改密码、查看个人信息等功能。
用户状态管理:通过session管理模块实现用户登录状态的维护,使用户在游戏过程中保持登录状态,直到用户主动退出或超时自动退出。
匹配对战:系统根据用户的天梯分数等信息进行匹配,为玩家找到合适的对手,进入游戏房间进行对战。
实时对战:在游戏房间内,玩家可以进行实时的五子棋对战,双方轮流落子,通过点击棋盘上的空位放置棋子。
胜负判断:系统能够实时检测当前落子位置在横、纵、斜四个方向是否有连续五个相同的棋子,以判断胜负。
全部的测试用例:
注册已经存在的用户:
与预期结果一致,注册失败!并显示用户名以及被占用。
输入的账号或密码超出规定的长度范围:
drop database if exists gobang;
create database if not exists gobang;
use gobang;
create table if not exists user(
id int primary key auto_increment,
username varchar(32) unique key not null,
password varchar(128) not null,
score int,
total_count int,
win_count int
);
根据MySQL定义的数据表中账号的最多长度是32位,而密码是128位,当我输入的账号超出32位时,系统就会报错,与预期结果一致!
异常注册的情况:
账号为空时:
跟预期结果一致,注册失败!
密码为空时:
我们发现当密码为空时居然注册成功了!这明显跟我们预期不符合的,并且我们之前在定义数据表的时候,password明显是有not null属性的,这是为什么呢?!
账号密码都为空时
我勒个豆,竟然也注册成功了!但是我们已经注册账号为空的时候,下一次再注册账号为空会有用户名已经存在的现象。
问题是在于前端给后端传的数据是 空字符串, jsonvalue的 isNull函数判断有点问题。目前是把条件修改了,判断前端传递过来的是不是一个空串。 这个是后端在处理的时候,提前就要判断处理掉的。
有一个疑问是密码为什么是空,数据库还能插入成功。
是因为前端给后端的是一个空字符串, 并且密码经过md5加密后,并不为空,所以插入是没问题的。 其次是name为何不写,也能注册成功。是因为不写,向后端传的是空字符串,而不是null。
所以,第一个要解决的是,我们用户不写用户名和密码的时候,后端要及时能够检测出来。所以修改了判断条件,得以解决。 第二个疑问是为何不写,sql语句还能执行成功,是由于 空字符串 和 null是有区别的!
当我们在数据库中插入空字符串的账号和密码的时候,插入成功!这也就验证了空字符串和null的区别,也解释了之前账号和密码为空的时候能注册成功的原因。
当我们更改判断条件之后,再来用空账号,密码注册一下(此时已经将数据库中的空账号的用户删除了)
此时,就不能用空账号进行注册了!bug解决完毕!
正常注册:
发现成功跳转到登录页面
与预期结果一致!
通过对注册页面的界面测试和功能测试,得出以下结论:
注册页面的背景图片显示正常,页面中的文字样式,和注册框均能正常显示。除此之外,注册框中的字样、输入框和“提交”按钮也能够正常显示。
正常注册情况,在注册一个并没有注册过的用户名时,可以正常注册成功,并且页面会提示用户“注册成功! 当注册已经注册过的用户时,页面会显示用户名被占用,让用户重新注册!
但是当密码为空时,用户也注册成功了!发现了这个bug。
正确的用户名和密码登录:
登录成功,与预期结果一致!并且账号和密码都能复制粘贴,密码也是隐藏了
异常的用户名和密码登录:
界面会显示用户名密码错误,与预期结果一致!
当跳过登录界面,直接进入游戏房间
预期:找不到用户信息,请重新登录,并跳转到登录界面
通过对登录页面的界面测试和功能测试,得出以下结论:
登录页面的背景图片显示正常,页面中的文字样式,和登录框均能正常显示。
建议:
但当登录失败时,可以具体向用户展示到底是账号错误还是密码错误,便于用户修改
这里匹配和落子测试和界面一起测试
当用户名字很长的时候 界面就会出现这种重回覆盖的情况
解决措施: 用户名过长最少有两种方式,第一种就是直接截取,比如你的用户名有19位,我只展示10位就行了。剩下的不展示 第二种就是,19位,只展示一部分,当鼠标放上去之后,展示完整的用户名
这里实现了第二种
通过测试游戏大厅页面的界面显示和匹配功能,得出以下结论:
游戏大厅页面的左上角“五子棋对战”字样正常显示,背景图片正常显示
玩家信息(用户名、分数、比赛场次、获胜场次)正常显示,
匹配功能正常,两个同级别玩家点击匹配按钮后,会匹配在一起。
并且当有一方五星连珠时,界面会显示游戏胜利/失败
通过对游戏大厅页面的界面测试和功能测试,并未在游戏大厅页面发现bug。
下面我来使用 JMeter 对五子棋项目的登录接口和获取用户信息接口进行简单的性能测试,下面就来介绍五子棋项目性能测试的一个测试流程。
因为用户在登录之后,我们需要从info接口读取消息,所以需要自动保存用户登录的cookie信息
这里直接选择standard即可
我们为了更加真实的模拟用户登录场景,我们在CSV文件中添加了用户信息以供模拟真实的用户登录场景。
下面的是配置,用username和password表示账号密码,后续直接在body中引用即可。
如下面的登录接口就调用了CSV文件中的账号和密码。
我们为了保证线程是并发执行的,我们添加了同步定时器。
这里我们创建一个梯度压测线程组(Stepping Thread Group),来慢慢增大我们对这两个接口的并发请求的数量,以达到性能测试的目的。
我们发现有一小部分测试用例出现的bug,进行具体分析:
发现都是网络超时,这跟网络情况相关,或者我的服务器稳定性比较差~
JMeter从CSV读取数据并不只适用于数据存放在URL参数里面。
我们现在做的就是将数据存放在body中。
在common这个软件包中主要存放的是utils这个工具类,这个工具类里面具体实现了创建浏览器对象和截图工具,避免每次测试都需要写一份工具,避免了代码的冗余。
在images这个文件夹中存放的是每次截图生成的图片,并且以天为单位生成小的文件夹,每份文件夹又有用时间进行格式化和生成该图片的类名。
在tests这个软件包中存放的是各个页面的自动化测试代码,并且RunTest文件进行总体的一个测试。
import os
import sys
import datetime
from selenium import webdriver
from selenium.webdriver.ie.service import Service
from webdriver_manager.chrome import ChromeDriverManager
#创建一个浏览器对象
class Driver:
driver = ""
def __init__(self):
options = webdriver.ChromeOptions()
self.driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()),options=options)
def getScreeShot(self):
# 创建屏幕截图
# 图片文件名称:./2024-05-08-173456.png
dirname = datetime.datetime.now().strftime("%Y-%m-%d")
# 判断dirname文件夹是否已经存在,若不存在则创建文件夹
# ../images/2024-05-08
if not os.path.exists("../images/" + dirname):
os.mkdir("../images/" + dirname)
# 2024-05-08-173456.png
# 图片路径:../images/调用方法-2024-05-08/2024-05-08-173456.png
# 图片路径:../images/LoginSucTest-2024-05-08/2024-05-08-173456.png
# 图片路径:../images/LoginFailTest-2024-05-08/2024-05-08-173456.png
filename = sys._getframe().f_back.f_code.co_name + "-" + datetime.datetime.now().strftime(
"%Y-%m-%d-%H%M%S") + ".png"
self.driver.save_screenshot("../images/" + dirname + "/" + filename)
BlogDriver = Driver()
其中截图的工具类别出心裁:
sys._getframe().f_back.f_code.co_name
通过调用栈获取调用该代码的函数名,作为文件名的一部分。这样可以动态地根据函数名为截图命名。
datetime.datetime.now().strftime("%Y-%m-%d-%H%M%S")
获取当前时间,并将其格式化为“年-月-日-时分秒”的形式,作为文件名中的时间戳部分。
filename
将函数名和时间戳拼接成一个完整的文件名,后缀为“.png”。
self.driver.save_screenshot("../images/" + dirname + "/" + filename)
使用 Selenium 的 save_screenshot
方法,将当前浏览器窗口的截图保存到指定路径中。路径由 ../images/
、dirname
(某个子目录名)以及生成的文件名组成。
代码:
#测试注册界面
import time
from distutils.command.register import register
from selenium.webdriver.common.by import By
from common.Utils import BlogDriver
class GobangRegister:
url = ""
driver = ""
def __init__(self):
self.url = "http://123.249.125.60:8085/register.html"
self.driver = BlogDriver.driver
self.driver.get(self.url)
#成功注册的测试用例
def RegisterSucTest(self):
self.driver.find_element(By.CSS_SELECTOR,"#user_name").send_keys("lisi9")
self.driver.find_element(By.CSS_SELECTOR,"#password").send_keys("123456")
self.driver.find_element(By.CSS_SELECTOR,"#submit").click()
#强制等待
time.sleep(3)
#此时需要点击弹窗里面的确定,才能跳转页面
alert = self.driver.switch_to.alert
alert.accept()
#能够找到登录界面中的登录二字,说明注册成功,否则注册失败
self.driver.find_element(By.CSS_SELECTOR,"body > div.login-container > div > h3")
#异常注册的测试用例
def RegisterFailTest(self):
#若连续多次的send_keys会出现关键词的拼接,而不是替换。若要替换需要先clear
self.driver.find_element(By.CSS_SELECTOR,"#user_name").clear()
self.driver.find_element(By.CSS_SELECTOR,"#password").clear()
self.driver.find_element(By.CSS_SELECTOR,"#user_name").send_keys("lisi")
self.driver.find_element(By.CSS_SELECTOR,"#password").send_keys("123456")
self.driver.find_element(By.CSS_SELECTOR,"#submit").click()
time.sleep(3)
#self.driver.quit()
_register = GobangRegister()
_register.RegisterSucTest()
#_register.RegisterFailTest()
#测试登录页面
import time
from common.Utils import BlogDriver
from selenium.webdriver.common.by import By
class GobangLogin:
url = ''
driver = ''
def __init__(self):
self.url = "http://123.249.125.60:8085/login.html"
self.driver = BlogDriver.driver
self.driver.get(self.url)
#登录失败的测试用例
def LoginFailTest(self):
#隐式等待
#self.driver.implicitly_wait(3)
self.driver.find_element(By.CSS_SELECTOR,'#user_name').send_keys('lisi8')
self.driver.find_element(By.CSS_SELECTOR,'#password').send_keys('123456')
self.driver.find_element(By.CSS_SELECTOR,'#submit').click()
time.sleep(3)
#此时需要点击弹窗里面的确定,才能跳转页面
alert = self.driver.switch_to.alert
alert.accept()
time.sleep(3)
#成功登录的测试用例
def LoginSucTest(self):
self.driver.find_element(By.CSS_SELECTOR,'#user_name').send_keys('lisi')
self.driver.find_element(By.CSS_SELECTOR,'#password').send_keys('123456')
self.driver.find_element(By.CSS_SELECTOR,'#submit').click()
time.sleep(3)
#此时需要点击弹窗里面的确定,才能跳转页面
alert = self.driver.switch_to.alert
alert.accept()
time.sleep(3)
# self.driver.find_element(By.CSS_SELECTOR,'#match-button')
# #截图
# BlogDriver.getScreeShot()
# #self.driver.quit()
# #强制等待
# time.sleep(3)
# #此时需要点击弹窗里面的确定,才能跳转页面
# alert = self.driver.switch_to.alert
# alert.accept()
# time.sleep(3)
#time.sleep(3)
#此时需要点击弹窗里面的确定,才能跳转页面
alert = self.driver.switch_to.alert
alert.accept()
time.sleep(3)
# _login = GobangLogin()
# # _login.LoginFailTest()
# _login.LoginSucTest()
#测试游戏大厅页面
import time
from common.Utils import BlogDriver
from selenium.webdriver.common.by import By
class GobangHall:
url = ' '
driver = ' '
def __init__(self):
self.url = "http://123.249.125.60:8085/game_hall.html"
self.driver = BlogDriver.driver
self.driver.get(self.url)
#测试游戏大厅匹配和停止匹配按钮
def HallTest(self):
time.sleep(3)
#此时需要点击弹窗里面的确定,才能跳转页面
alert = self.driver.switch_to.alert
alert.accept()
#点击开始匹配
self.driver.find_element(By.CSS_SELECTOR,'#match-button').click()
#点击停止匹配
self.driver.find_element(By.CSS_SELECTOR,'#match-button').click()
time.sleep(3)
#重新匹配
self.driver.find_element(By.CSS_SELECTOR,'#match-button').click()
#截图
BlogDriver.getScreeShot()
#from tests import GobangRegister
from tests import GobangLogin
from tests import GobangHall
from common.Utils import BlogDriver
if __name__ == "__main__":
#游戏注册页面
#GobangRegister.GobangRegister().RegisterSucTest()
#GobangLogin.GobangLogin().LoginFailTest()
#登陆成功之后就可以调用博客首页测试首页的用例(登陆状态)
#最终一定是登录状态
GobangLogin.GobangLogin().LoginSucTest()
#游戏大厅界面
GobangHall.GobangHall().HallTest()
#指定浏览器的退出
BlogDriver.driver.quit()
如上图运行过程所示,我们的自动化测试用例全部通过,当我们执行失败的时候需要截图进行纠错。
为什么?
在Selenium自动化测试中,当遇到弹窗时,通常使用的等待方式主要是显式等待。这是因为弹窗的出现往往是由某些用户操作或页面事件触发的,而这些事件可能不是立即发生的,因此需要等待弹窗确实出现后再进行相应的操作。
注意在前面文件中不需要我们自己退出,我们只用在最后的RunTest一次退出即可,不然前面程序已经退出,将会运行失败
解答:
#点击完成之后出现页面的跳转,页面跳转需要加载时间,可能会出现代码执行的速度比页面渲染的速度要快,导致元素查找不到,因此可以添加等待
#添加隐式等待和显示等待都可以,任选择一个
#隐式等待:创建浏览器对象之后就可以加上,因为隐式等待的作用域在driver整个生命周期
#显示等待:可以作用在当前代码中
我们一共进行了功能测试、自动化测试以及性能测试这三种测试方式,在功能测试阶段,我们着重点是测试玩家双方进行五子棋对弈时的过程;在自动化测试阶段,我们主要测试各个页面的相关信息,着重测试了登录与注册的功能;在性能测试阶段,我们着重测试了用户登录与获取用户信息的接口性能,并生成了性能测试报告。
在这期间,发现了不少bug,例如用户注册时,没有账号和密码也能注册成功;当用户的名称过长时,会对前端界面按钮造成覆盖等等。但最终都解决了,不得不说,测试真的很有意思~~~