4 GUI的测试代码
4.1为什么要使用GUI的测试代码
在TestRegister.py使用
self.assertIn("showerror(\"1\")",response.text)验证注册成功;
self.assertIn("showerror(\"username\")",response.text)验证用户已经被注册过;
self.assertIn("showerror(\"phone\")",response.text)验证手机号已经被注册过;
self.assertIn("showerror(\"email\")",response.text)验证Email已经被注册过。
原因是,这些信息是通过javascript函数showerror(info)实现的,另外也发现在index.js中类似
const passwordPattern = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{5,30}$/;
if (!passwordPattern.test(password)) {
document.getElementById('passwordError').innerText = '密码必须包含大小写字母、数字和特殊字符';
isValid = false;
}
也没有用过接口测试脚本。这是因为接口测试只负责后端逻辑,而前端逻辑必须由基于GUI的测试代码来生成。
4.2 基于ChatGPT生成GUI代码
输入
对下面用例书写基于playwright+pytest的测试脚本。
前端代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用户注册界面</title>
<link rel="stylesheet" type="text/css" href="css/index.css"/>
<script src="js/index.js" type="text/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/crypto-js@4.1.1/core.js"></script>
<script src="https://cdn.jsdelivr.net/npm/crypto-js@4.1.1/sha256.js"></script>
</head>
<body>
<div class="container">
<h2>用户注册</h2>
<form name="myForm" method="POST" action="jsp/register.jsp" onsubmit="return validateForm();">
<label for="username">账号 (5-20位字母或数字):</label>
<input type="text" id="username" name="username" required>
<div id="usernameError" class="error"></div>
<label for="password">密码 (5-30位,包含大小写字母、数字和特殊字符):</label>
<input type="password" id="password" name="password" required>
<div id="passwordError" class="error"></div>
<label for="confirmPassword">密码确认:</label>
<input type="password" id="confirmPassword" name="confirmPassword" required>
<div id="confirmPasswordError" class="error"></div>
<label for="phone">手机号 (中国):</label>
<input type="tel" id="phone" name="phone" required>
<div id="phoneError" class="error"></div>
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required>
<div id="emailError" class="error"></div>
<button type="submit">注册</button>
</form>
</div>
</body>
</html>
TC1 - 账号有效性测试:
输入有效的账号(5-20位字母或数字),预期结果:注册成功。
TC2 - 密码长度不足测试:
密码包含数字大小写字母,但是不包含特殊字符,预期结果:显示错误信息"密码必须是5-30位,包含大小写字母、数字和特殊字符"。
TC3 - 手机号格式错误测试:
输入非法的手机号码格式,预期结果:显示错误信息"手机号格式错误"。
TC4 – 一个用户重复注册两次:
一个用户重复注册两次,预期结果:用户名已经被注册过!"。
输出
Test_register_GUI.py
import pytest
from playwright.sync_api import sync_playwright
# 测试类
class TestUserRegistration:
@pytest.fixture(scope="module")
def setup(self):
with sync_playwright() as p:
self.browser = p.chromium.launch(headless=False) # 设为 True 可以在后台运行
self.page = self.browser.new_page()
self.page.goto('file:///path/to/your/html/file.html') # 请替换为你的 HTML 文件路径
yield
self.browser.close()
def test_valid_username(self, setup):
self.page.fill('input[name="username"]', 'validUser123')
self.page.fill('input[name="password"]', 'ValidPass123!')
self.page.fill('input[name="confirmPassword"]', 'ValidPass123!')
self.page.fill('input[name="phone"]', '13800138000')
self.page.fill('input[name="email"]', 'test@example.com')
self.page.click('button[type="submit"]')
# 检查注册是否成功(这里需要根据实际页面反馈调整)
assert self.page.locator('text=注册成功').is_visible()
def test_password_length_error(self, setup):
self.page.fill('input[name="username"]', 'testUser')
self.page.fill('input[name="password"]', 'NoSpecialChar1')
self.page.fill('input[name="confirmPassword"]', 'NoSpecialChar1')
self.page.fill('input[name="phone"]', '13800138000')
self.page.fill('input[name="email"]', 'test@example.com')
self.page.click('button[type="submit"]')
# 检查错误信息
assert self.page.locator('text=密码必须是5-30位,包含大小写字母、数字和特殊字符').is_visible()
def test_invalid_phone_format(self, setup):
self.page.fill('input[name="username"]', 'testUser')
self.page.fill('input[name="password"]', 'ValidPass123!')
self.page.fill('input[name="confirmPassword"]', 'ValidPass123!')
self.page.fill('input[name="phone"]', '12345') # 非法手机号
self.page.fill('input[name="email"]', 'test@example.com')
self.page.click('button[type="submit"]')
# 检查错误信息
assert self.page.locator('text=手机号格式错误').is_visible()
def test_duplicate_registration(self, setup):
# 第一次注册
self.page.fill('input[name="username"]', 'duplicateUser')
self.page.fill('input[name="password"]', 'ValidPass123!')
self.page.fill('input[name="confirmPassword"]', 'ValidPass123!')
self.page.fill('input[name="phone"]', '13800138000')
self.page.fill('input[name="email"]', 'duplicate@example.com')
self.page.click('button[type="submit"]')
# 检查注册成功
assert self.page.locator('text=注册成功').is_visible()
# 第二次注册相同用户
self.page.fill('input[name="username"]', 'duplicateUser')
self.page.fill('input[name="password"]', 'ValidPass123!')
self.page.fill('input[name="confirmPassword"]', 'ValidPass123!')
self.page.fill('input[name="phone"]', '13800138000')
self.page.fill('input[name="email"]', 'duplicate@example.com')
self.page.click('button[type="submit"]')
# 检查错误信息
assert self.page.locator('text=用户名已经被注册过!').is_visible()
说明:
l路径替换:请确保在 self.page.goto() 中替换为您的 HTML 文件的正确路径。
l错误信息检查:根据您的实际页面反馈,可能需要调整断言中的文本。
l浏览器设置:可以根据需要设置 headless 参数来选择是否在后台运行浏览器。
l运行测试:确保已安装 pytest 和 playwright,可以通过 pytest 命令运行测试。
请根据您的实际需求调整测试脚本。
4.3 调整
4.3.1 调整GUI的测试代码
修改
self.page.goto('file:///path/to/your/html/file.html')
为
self.page.goto('http://127.0.0.1:8080/CharGPTEbusiness/register.jsp')
在测试用例
test_duplicate_registration中,由于self.page.fill('input[name="phone"]', '13800138000')
在测试用例test_valid_username也用的是这个手机号,所以改为
self.page.fill('input[name="phone"]', '13800138001')
…
# 第二次注册相同用户
…
page.fill('input[name="phone"]', '13800138001')
…
另外抽出去掉stup()方法,抽出REGISTER_URL作为全局变量
REGISTER_URL = 'http://127.0.0.1:8080/CharGPTEbusiness/register.html'
改造每个几个方法
# 测试类
class TestUserRegistration:
def test_valid_username(self,page: Page):
page.goto(REGISTER_URL)
…
def test_password_length_error(self,page: Page):
page.goto(REGISTER_URL)
最后与接口测试用例一样,为每个程序结束增加一个情况数据库的方法
def init_db():# 设置数据库连接参数
host = 'localhost'
user = 'root'
password = '123456'
database = 'ebusiness'
# 创建数据库连接
connection = pymysql.connect(host=host, user=user, password=password, database=database)
try:
with connection.cursor() as cursor:
# SQL语句,用于清空user表
sql = "TRUNCATE TABLE user"
# 执行SQL语句
cursor.execute(sql)
# 提交事务
connection.commit()
finally:
# 关闭数据库连接
connection.close()
通过
pytest -sv .\Test_register_GUI.py –headed
命令运行
4.3.2 调整合并的测试代码
在接口代码和GUI代码都是用热清空数据库的操作,现在来进行统一的封装合并。
建立Util.py
import pymysql
class ClassDB:
def init_db():# 设置数据库连接参数
host = 'localhost'
user = 'root'
password = '123456'
database = 'ebusiness'
# 创建数据库连接
connection = pymysql.connect(host=host, user=user, password=password, database=database)
try:
with connection.cursor() as cursor:
# SQL语句,用于清空user表
sql = "TRUNCATE TABLE user"
# 执行SQL语句
cursor.execute(sql)
# 提交事务
connection.commit()
finally:
# 关闭数据库连接
connection.close()
修改Test_register_GUI.py
from Util import ClassDB
REGISTER_URL = 'http://127.0.0.1:8080/CharGPTEbusiness/register.html'
# 测试类
class TestUserRegistration:
def teardown_class(self):
ClassDB.init_db()
…
修改TestRegister.py
from Util import ClassDB
…
def teardown_class(self):
ClassDB.init_db()
5 单元测试
到目前位置存在以下几个后端java文件
validateUser.java、DBuser.java、User.java
我们针对这些代码书写单元测试用例
5.1单元测试代码的申请
输入
请针对以下几个java文件形成单元测试用例
validateUser.java
package ebusiness;
import java.util.regex.Pattern;
public class validateUser {
public String validate(User user) {
String usernamePattern = "^[a-zA-Z0-9]{5,20}$";
String phonePattern = "^1[3-9]\\d{9}$";
String emailPattern = "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$";
if (!Pattern.matches(usernamePattern, user.getUsername())) {
return "账号必须是5-20位字母或数字";
}
if (!Pattern.matches(phonePattern, user.getPhone())) {
return "手机号必须符合中国手机号码格式";
}
if (!Pattern.matches(emailPattern, user.getEmail())) {
return "Email格式不正确";
}
return "验证成功";
}
}
DBuser.java
package ebusiness;
import java.sql.*;
public class DBuser {
private static final String url = "jdbc:mysql://localhost:3306/ebusiness";
private static final String user = "root";
private static final String password = "123456";
private static Statement stmt = null;
private static ResultSet rs;
public Connection connect() throws SQLException {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection(url, user, password);
stmt = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
return conn;
} catch (ClassNotFoundException e) {
System.out.println("JDBC Driver not found: " + e.getMessage());
} catch (SQLException e) {
System.out.println("Database connection failed: " + e.getMessage());
}
return null;
}
public void disconnect(Connection conn) {
try {
if (rs != null) {
rs.close();
}
stmt.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public String insert(Connection conn, User user) throws SQLException {
if (! userExists(conn, user.getUsername(), user.getEmail(), user.getPhone()).equals("0")) {
return userExists(conn, user.getUsername(), user.getEmail(), user.getPhone());
}
String insertString = "INSERT INTO user (username, password, phone, email) VALUES (?, ?, ?, ?)";
try {
PreparedStatement ps = conn.prepareStatement(insertString);
ps.setString(1, user.getUsername());
ps.setString(2, user.getPassword());
ps.setString(3, user.getPhone());
ps.setString(4, user.getEmail());
int affectedRows = ps.executeUpdate();
return String.valueOf(affectedRows);
} catch (SQLException e) {
e.printStackTrace();
}
return "-1";
}
public String userExists(Connection conn, String username, String email, String phone) throws SQLException {
String queryString = "SELECT * FROM user WHERE username = ?";
try {
PreparedStatement ps = conn.prepareStatement(queryString);
ps.setString(1, username);
ResultSet rs = ps.executeQuery();
if(rs.next())
return "username";
} catch (SQLException e) {
e.printStackTrace();
}
queryString = "SELECT * FROM user WHERE email = ?";
try {
PreparedStatement ps = conn.prepareStatement(queryString);
ps.setString(1, email);
ResultSet rs = ps.executeQuery();
if(rs.next())
return "email";
} catch (SQLException e) {
e.printStackTrace();
}
queryString = "SELECT * FROM user WHERE phone = ?";
try {
PreparedStatement ps = conn.prepareStatement(queryString);
ps.setString(1, phone);
ResultSet rs = ps.executeQuery();
if(rs.next())
return "phone";
} catch (SQLException e) {
e.printStackTrace();
}
return "0";
}
}
User.java
package ebusiness;
public class User {
private int id;
private String username;
private String password;
private String phone;
private String email;
// Getters and Setters
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
5.2ChatGPT给出单元测试代码
为你的 Java 类 validateUser, DBuser, 和 User 编写单元测试用例,可以使用 JUnit 框架。以下是如何为这些类创建基本的单元测试的示例。
1. validateUser 类的单元测试
首先,我们需要测试 validateUser 类中的 validate 方法。我们将创建一个测试类 ValidateUserTest。
package ebusiness;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ValidateUserTest {
@Test
public void testValidUser() {
validateUser validator = new validateUser();
User user = new User();
user.setUsername("validUser");
user.setPhone("13812345678");
user.setEmail("test@example.com");
String result = validator.validate(user);
assertEquals("验证成功", result);
}
@Test
public void testInvalidUsername() {
validateUser validator = new validateUser();
User user = new User();
user.setUsername("usr"); // Invalid username
user.setPhone("13812345678");
user.setEmail("test@example.com");
String result = validator.validate(user);
assertEquals("账号必须是5-20位字母或数字", result);
}
@Test
public void testInvalidPhone() {
validateUser validator = new validateUser();
User user = new User();
user.setUsername("validUser");
user.setPhone("1234567890"); // Invalid phone
user.setEmail("test@example.com");
String result = validator.validate(user);
assertEquals("手机号必须符合中国手机号码格式", result);
}
@Test
public void testInvalidEmail() {
validateUser validator = new validateUser();
User user = new User();
user.setUsername("validUser");
user.setPhone("13812345678");
user.setEmail("invalid-email"); // Invalid email
String result = validator.validate(user);
assertEquals("Email格式不正确", result);
}
}
2. DBuser 类的单元测试
DBuser 类的测试需要模拟数据库连接。可以使用 Mockito 来模拟数据库操作。以下是一个简单的测试示例。
package ebusiness;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;
public class DBuserTest {
private DBuser dbUser;
private Connection mockConnection;
@BeforeEach
public void setUp() {
dbUser = new DBuser();
mockConnection = Mockito.mock(Connection.class);
}
@Test
public void testInsertUser() throws SQLException {
User user = new User();
user.setUsername("newUser");
user.setPassword("password123");
user.setPhone("13812345678");
user.setEmail("newuser@example.com");
// Mocking userExists to return "0" (meaning user does not exist)
when(dbUser.userExists(mockConnection, user.getUsername(), user.getEmail(), user.getPhone())).thenReturn("0");
// Mocking PreparedStatement and ResultSet
PreparedStatement mockStatement = Mockito.mock(PreparedStatement.class);
when(mockConnection.prepareStatement(anyString())).thenReturn(mockStatement);
when(mockStatement.executeUpdate()).thenReturn(1); // Simulate successful insert
String result = dbUser.insert(mockConnection, user);
assertEquals("1", result); // Expecting 1 row affected
}
@Test
public void testUserExists() throws SQLException {
User user = new User();
user.setUsername("existingUser");
user.setEmail("existing@example.com");
user.setPhone("13812345678");
// Mocking ResultSet
ResultSet mockResultSet = Mockito.mock(ResultSet.class);
when(mockResultSet.next()).thenReturn(true); // Simulate user found
when(mockConnection.prepareStatement(anyString())).thenReturn(mockStatement);
when(mockStatement.executeQuery()).thenReturn(mockResultSet);
String result = dbUser.userExists(mockConnection, user.getUsername(), user.getEmail(), user.getPhone());
assertEquals("username", result); // Expecting "username" since user exists
}
}
3. User 类的单元测试
User 类主要是一个数据容器,通常不需要单元测试,除非你希望测试其 getter 和 setter 方法。以下是一个简单的示例:
package ebusiness;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class UserTest {
@Test
public void testUserGettersAndSetters() {
User user = new User();
user.setId(1);
user.setUsername("testUser");
user.setPassword("password");
user.setPhone("13812345678");
user.setEmail("test@example.com");
assertEquals(1, user.getId());
assertEquals("testUser", user.getUsername());
assertEquals("password", user.getPassword());
assertEquals("13812345678", user.getPhone());
assertEquals("test@example.com", user.getEmail());
}
}
4. 运行测试
确保在你的项目中添加 JUnit 和 Mockito 的依赖(如果你使用的是 Maven,可以在 pom.xml 中添加相应的依赖)。然后使用 IDE 或命令行工具运行测试。
以上是为你的 Java 类编写单元测试的基本示例,具体的实现可能需要根据你的项目结构和需求进行调整。
单元测试代码的运行
5.3运行单元测试代码
5.3.1运行ValidateUserTest.java
运行ValidateUserTest.java测试通过;
5.3.2 运行DBuserTest.java
运行DBuserTest.java测试通过;
在pom.xml中加上
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.9.0</version>
<scope>test</scope>
</dependency>
告知
mockStatement cannot be resolved to a variable DBuserTest.java /ChatGPTEbusiness/src/test/java/ebusiness line 56 Java Problem
询问ChatGPT给出修正后的代码
@Test
public void testUserExists() throws SQLException {
User user = new User();
user.setUsername("existingUser");
user.setEmail("existing@example.com");
user.setPhone("13812345678");
// Mocking ResultSet
ResultSet mockResultSet = Mockito.mock(ResultSet.class);
when(mockResultSet.next()).thenReturn(true); // Simulate user found
// Mocking PreparedStatement
PreparedStatement mockStatement = Mockito.mock(PreparedStatement.class);
when(mockConnection.prepareStatement(anyString())).thenReturn(mockStatement);
when(mockStatement.executeQuery()).thenReturn(mockResultSet);
String result = dbUser.userExists(mockConnection, user.getUsername(), user.getEmail(), user.getPhone());
assertEquals("username", result); // Expecting "username" since user exists
}
运行testUserExists(),但是testInsertUser()报空指针异常,没有找到问题。(有知道原因的朋友请告诉我)
5.3.2 运行UserTest.java
运行UserTest.java测试通过;
6 代码评审
到目前位置存在以下几个文件
前端代码
register.html、index.js、showerror.js、index.css
后端代码
register.jsp、validateUser.java、DBuser.java、User.java
API测试代码
TestRegister.py
GUI测试代码
Test_register_GUI.py
单元测试代码
ValidateUserTest.java、DBuserTest.java、UserTest.java
6.1 前端代码
6.1.1提出前端代码评审请求
请对前端代码进行评审
register.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,
initial-scale=1.0">
<title>用户注册界面</title>
<link
rel="stylesheet" type="text/css"
href="css/index.css"/>
<script
src="js/index.js" type="text/javascript"></script>
<script
src="https://cdn.jsdelivr.net/npm/crypto-js@4.1.1/core.js"></script>
<script
src="https://cdn.jsdelivr.net/npm/crypto-js@4.1.1/sha256.js"></script>
</head>
<body>
<div class="container">
<h2>用户注册</h2>
<form name="myForm" method="POST"
action="jsp/register.jsp" onsubmit="return
validateForm();">
<label for="username">账号 (5-20位字母或数字):</label>
<input type="text" id="username"
name="username" required>
<div id="usernameError"
class="error"></div>
<label for="password">密码 (5-30位,包含大小写字母、数字和特殊字符):</label>
<input type="password" id="password"
name="password" required>
<div id="passwordError"
class="error"></div>
<label for="confirmPassword">密码确认:</label>
<input type="password" id="confirmPassword"
name="confirmPassword" required>
<div id="confirmPasswordError"
class="error"></div>
<label for="phone">手机号 (中国):</label>
<input type="tel" id="phone"
name="phone" required>
<div id="phoneError"
class="error"></div>
<label for="email">邮箱:</label>
<input type="email" id="email"
name="email" required>
<div id="emailError"
class="error"></div>
<button type="submit">注册</button>
</form>
</div>
</body>
</html>
index.css
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
background-color: #f4f4f4;
}
.container {
max-width: 400px;
margin: auto;
padding: 20px;
background-color: #fff;
border-radius: 10px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
}
input[type="text"], input[type="password"], input[type="email"], input[type="tel"] {
width: 100%;
padding: 10px;
margin: 10px 0;
border: 1px solid #ccc;
border-radius: 5px;
}
button {
width: 100%;
padding: 10px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
font-size: 16px;
}
button:hover {
background-color: #45a049;
}
.error {
color: red;
font-size: 12px;
}
</style>
index.js
function validateForm() {
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
const confirmPassword = document.getElementById('confirmPassword').value;
const phone = document.getElementById('phone').value;
const email = document.getElementById('email').value;
let isValid = true;
document.getElementById('usernameError').innerText = '';
document.getElementById('passwordError').innerText = '';
document.getElementById('confirmPasswordError').innerText = '';
document.getElementById('phoneError').innerText = '';
document.getElementById('emailError').innerText = '';
const usernamePattern = /^[a-zA-Z0-9]{5,20}$/;
if (!usernamePattern.test(username)) {
document.getElementById('usernameError').innerText = '账号必须是5-20位的字母或数字';
isValid = false;
}
const passwordPattern = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{5,30}$/;
if (!passwordPattern.test(password)) {
document.getElementById('passwordError').innerText = '密码必须包含大小写字母、数字和特殊字符';
isValid = false;
}
if (password !== confirmPassword) {
document.getElementById('confirmPasswordError').innerText = '密码确认不一致';
isValid = false;
}
const phonePattern = /^1[3-9]\d{9}$/;
if (!phonePattern.test(phone)) {
document.getElementById('phoneError').innerText = '请输入有效的中国手机号';
isValid = false;
}
const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
if (!emailPattern.test(email)) {
document.getElementById('emailError').innerText = '请输入有效的Email地址';
isValid = false;
}
if (isValid) {
// 获取密码并进行SHA-256哈希
let passwordField = document.forms["myForm"]["password"];
let hashedPassword = CryptoJS.SHA256(password).toString(CryptoJS.enc.Base64);
// 将哈希后的密码重新赋值给表单的密码字段
passwordField.value = hashedPassword;
console.log('加密后的密码:', hashedPassword);
alert('注册成功!');
}
return isValid;
}
5.4 showerror.js
function showerror(info) {
switch(info){
case("username"):
document.getElementById('usernameError').innerText = '用户名已经被注册过!';
break;
case("email"):
document.getElementById('emailError').innerText = '电子邮件已经被注册过!';
break;
case("phone"):
document.getElementById('phoneError').innerText = '手机已经被注册过!';
break;
case("1"):
document.writeln("注册成功!");
break;
default:
document.writeln('注册成功!');
}
}
6.1.2前端代码评审回复
你的前端代码整体结构良好,包含了用户注册所需的基本功能,但仍有一些地方可以改进。以下是对 register.html、index.css、index.js 和 showerror.js 的评审和建议:
1. HTML (register.html)
优点:
l清晰的表单结构,标签和输入框的配对良好。
l使用了 required 属性来确保用户输入。
改进建议:
l表单的 action 属性:建议使用更现代的方式处理表单提交,比如使用 AJAX,而不是直接提交到 jsp/register.jsp。这样可以提高用户体验。
l错误信息显示:目前错误信息是通过 div 显示的,可以考虑在表单提交后通过 AJAX 获取并更新这些信息,而不是在页面上直接输出。
l安全性:考虑使用 HTTPS 来保护用户数据。
2. CSS (index.css)
优点:
l代码结构清晰,使用了 JSP 标签。
改进建议:
lCSS 文件命名:文件名 index.css 可能不够具体,建议使用 register.css 或类似名称以提高可读性。改为register.css
l分离逻辑和样式:尽量将 CSS 和 JSP 逻辑分开,保持 CSS 文件只包含样式,而不包含 Java 逻辑。CSS中没有包含逻辑
3. JavaScript (index.js)
优点:
l使用正则表达式进行输入验证,确保用户输入符合要求。
l在输入不合法时,及时给出反馈。
改进建议:
l密码哈希:建议使用 HTTPS 进行数据传输,虽然你在客户端进行了 SHA-256 哈希,但这并不能替代后端的安全措施。
l用户体验:注册成功的提示使用了 alert,可以考虑使用更友好的方式,比如在页面上显示成功消息,而不是弹出框。(取消alert,改为console.log)
l代码重复:在validateForm中的错误信息显示逻辑可以提取成一个函数,以减少重复代码。
4. 响应处理 (showerror.js)
优点:
l使用 switch 语句处理不同的错误消息,逻辑清晰。
改进建议:
lDOM 操作:使用 document.writeln 不是最佳实践,建议使用 innerText 或 innerHTML 来更新页面内容。(以后改为重定向)
l函数的通用性:可以考虑将错误信息的处理逻辑封装成一个更通用的函数,接受错误类型和目标元素作为参数。(目前错误信息比较少,暂不考虑)
5. 其他建议
输入验证:可以在后端再次进行输入验证,确保数据的安全性和有效性。(的确如此处理)
用户体验:在注册成功或失败后,可以考虑重定向用户到登录页面或其他页面,而不是停留在注册页面。(下面考虑)
代码注释:在 JavaScript 和 JSP 中添加适当的注释,帮助其他开发者理解代码逻辑。
总结
你的前端代码已经有了良好的基础,经过一些改进后可以提升用户体验和代码的可维护性。希望这些建议对你有所帮助!
6.1.3 封装validateForm()函数
输入
对于validateForm 中的错误信息显示逻辑可以提取成一个函数,以减少重复代码。请帮助实现下。
输出
当然可以!为了减少重复代码,我们可以创建一个辅助函数来处理错误信息的显示。在 validateForm 函数中,当验证失败时,我们调用这个新函数来更新对应的错误信息。以下是修改后的代码示例:
修改后的 index.js
function validateForm() {
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
const confirmPassword = document.getElementById('confirmPassword').value;
const phone = document.getElementById('phone').value;
const email = document.getElementById('email').value;
let isValid = true;
clearErrorMessages(); // 清空所有错误信息
const usernamePattern = /^[a-zA-Z0-9]{5,20}$/;
if (!usernamePattern.test(username)) {
showError('usernameError', '账号必须是5-20位的字母或数字');
isValid = false;
}
const passwordPattern = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{5,30}$/;
if (!passwordPattern.test(password)) {
showError('passwordError', '密码必须包含大小写字母、数字和特殊字符');
isValid = false;
}
if (password !== confirmPassword) {
showError('confirmPasswordError', '密码确认不一致');
isValid = false;
}
const phonePattern = /^1[3-9]\d{9}$/;
if (!phonePattern.test(phone)) {
showError('phoneError', '请输入有效的中国手机号');
isValid = false;
}
const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
if (!emailPattern.test(email)) {
showError('emailError', '请输入有效的Email地址');
isValid = false;
}
if (isValid) {
// 获取密码并进行SHA-256哈希
let passwordField = document.forms["myForm"]["password"];
let hashedPassword = CryptoJS.SHA256(password).toString(CryptoJS.enc.Base64);
// 将哈希后的密码重新赋值给表单的密码字段
passwordField.value = hashedPassword;
console.log('加密后的密码:', hashedPassword);
console.log ('注册成功!');
}
return isValid;
}
// 新增的函数,用于显示错误信息
function showError(elementId, message) {
document.getElementById(elementId).innerText = message;
}
// 新增的函数,用于清空所有错误信息
function clearErrorMessages() {
document.getElementById('usernameError').innerText = '';
document.getElementById('passwordError').innerText = '';
document.getElementById('confirmPasswordError').innerText = '';
document.getElementById('phoneError').innerText = '';
document.getElementById('emailError').innerText = '';
}
说明
showError 函数:此函数接收两个参数:elementId(要显示错误信息的元素的 ID)和 message(要显示的错误信息)。它会将指定元素的文本设置为传入的错误信息。
clearErrorMessages 函数:此函数用于清空所有错误信息,确保在每次验证之前,之前的错误信息都被清除。
在 validateForm 中的调用:
在每次验证失败时,调用 showError 函数来显示对应的错误信息。
在函数开始时调用 clearErrorMessages,以确保每次验证时错误信息都是干净的。
通过这种方式,我们减少了重复代码,提高了代码的可读性和可维护性。希望这对你有帮助!
修改后,运行Test_register_GUI.py。保证没有引入缺陷
6.2 后端代码
6.2.1提出后端代码评审请求
请对后端代码进行评审
register.jsp
<%@ page contentType="text/html; charset=utf-8" %>
<%@ page import="java.sql.*, javax.sql.*" %>
<%@ page import="ebusiness.*" %>
<%
String username = request.getParameter("username");
String password = request.getParameter("password");
String phone = request.getParameter("phone");
String email = request.getParameter("email");
User user = new User();
user.setUsername(username);
user.setPassword(password);
user.setPhone(phone);
user.setEmail(email);
validateUser vu = new validateUser();
if(vu.validate(user).equals("验证成功")){
try {
DBuser mysql = new DBuser();
Connection conn = mysql.connect();
String info = mysql.insert(conn,user);
if (info.equals("1"))
out.println("注册成功");
%>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用户注册界面</title>
<link rel="stylesheet" type="text/css" href="../css/register.css"/>
<script src="../js/index.js" type="text/javascript"></script>
<script src="../js/showerror.js" type="text/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/crypto-js@4.1.1/core.js"></script>
<script src="https://cdn.jsdelivr.net/npm/crypto-js@4.1.1/sha256.js"></script>
</head>
<body onload='showerror("<%=info%>");'>
<div class="container">
<h2>用户注册</h2>
<form name="myForm" method="POST" action="jsp/register.jsp" onsubmit="return validateForm();">
<label for="username">账号 (5-20位字母或数字):</label>
<input type="text" id="username" name="username" required>
<div id="usernameError" class="error"></div>
<label for="password">密码 (5-30位,包含大小写字母、数字和特殊字符):</label>
<input type="password" id="password" name="password" required>
<div id="passwordError" class="error"></div>
<label for="confirmPassword">密码确认:</label>
<input type="password" id="confirmPassword" name="confirmPassword" required>
<div id="confirmPasswordError" class="error"></div>
<label for="phone">手机号 (中国):</label>
<input type="tel" id="phone" name="phone" required>
<div id="phoneError" class="error"></div>
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required>
<div id="emailError" class="error"></div>
<button type="submit">注册</button>
</form>
</div>
</body>
</html>
<%
mysql.disconnect(conn);
} catch (Exception e) {
out.println(e.getMessage());
}
}else{
out.print(vu.validate(user));
}
%>
validateUser.java
package ebusiness;
import java.util.regex.Pattern;
public class validateUser {
public String validate(User user) {
String usernamePattern = "^[a-zA-Z0-9]{5,20}$";
String phonePattern = "^1[3-9]\\d{9}$";
String emailPattern = "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$";
if (!Pattern.matches(usernamePattern, user.getUsername())) {
return "账号必须是5-20位字母或数字";
}
if (!Pattern.matches(phonePattern, user.getPhone())) {
return "手机号必须符合中国手机号码格式";
}
if (!Pattern.matches(emailPattern, user.getEmail())) {
return "Email格式不正确";
}
return "验证成功";
}
}
DBuser.java
package ebusiness;
import java.sql.*;
public class DBuser {
private static final String url = "jdbc:mysql://localhost:3306/ebusiness";
private static final String user = "root";
private static final String password = "123456";
private static Statement stmt = null;
private static ResultSet rs;
public Connection connect() throws SQLException {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection(url, user, password);
stmt = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
return conn;
} catch (ClassNotFoundException e) {
System.out.println("JDBC Driver not found: " + e.getMessage());
} catch (SQLException e) {
System.out.println("Database connection failed: " + e.getMessage());
}
return null;
}
public void disconnect(Connection conn) {
try {
if (rs != null) {
rs.close();
}
stmt.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public String insert(Connection conn, User user) throws SQLException {
if (! userExists(conn, user.getUsername(), user.getEmail(), user.getPhone()).equals("0")) {
return userExists(conn, user.getUsername(), user.getEmail(), user.getPhone());
}
String insertString = "INSERT INTO user (username, password, phone, email) VALUES (?, ?, ?, ?)";
try {
PreparedStatement ps = conn.prepareStatement(insertString);
ps.setString(1, user.getUsername());
ps.setString(2, user.getPassword());
ps.setString(3, user.getPhone());
ps.setString(4, user.getEmail());
int affectedRows = ps.executeUpdate();
return String.valueOf(affectedRows);
} catch (SQLException e) {
e.printStackTrace();
}
return "-1";
}
public String userExists(Connection conn, String username, String email, String phone) throws SQLException {
String queryString = "SELECT * FROM user WHERE username = ?";
try {
PreparedStatement ps = conn.prepareStatement(queryString);
ps.setString(1, username);
ResultSet rs = ps.executeQuery();
if(rs.next())
return "username";
} catch (SQLException e) {
e.printStackTrace();
}
queryString = "SELECT * FROM user WHERE email = ?";
try {
PreparedStatement ps = conn.prepareStatement(queryString);
ps.setString(1, email);
ResultSet rs = ps.executeQuery();
if(rs.next())
return "email";
} catch (SQLException e) {
e.printStackTrace();
}
queryString = "SELECT * FROM user WHERE phone = ?";
try {
PreparedStatement ps = conn.prepareStatement(queryString);
ps.setString(1, phone);
ResultSet rs = ps.executeQuery();
if(rs.next())
return "phone";
} catch (SQLException e) {
e.printStackTrace();
}
return "0";
}
}
User.java
package ebusiness;
public class User {
private int id;
private String username;
private String password;
private String phone;
private String email;
// Getters and Setters
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
6.2.2后端代码评审回复
你的后端代码整体结构清晰,功能实现较为完整,但仍有一些地方可以改进。以下是对 register.jsp、validateUser.java、DBuser.java 和 User.java 的评审和建议:
1. register.jsp
优点:
l表单结构清晰,能够处理用户注册逻辑。
l使用了validateUser 类进行用户输入的验证。
改进建议:
l业务逻辑与视图分离:建议将业务逻辑(如数据库操作和验证)移到 Servlet 或其他控制器中,而不是直接在 JSP 中处理。这样可以更好地遵循 MVC 模式。
l错误处理:在发生异常时,建议返回用户友好的错误信息,而不是直接打印异常信息。可以考虑使用日志记录异常。
l安全性:在处理用户密码时,确保密码在存储前进行加密处理,而不是在数据库中以明文形式存储。
lHTML 输出:在 body 标签的 onload 中直接调用 showerror 函数并传递 info,这可能会导致 XSS 攻击。建议使用 HTML 实体编码来避免潜在的安全问题(考虑避免)。
2. validateUser.java
优点:
l使用正则表达式进行输入验证,逻辑清晰。
改进建议:
l返回值设计:返回字符串作为验证结果的方式不够灵活,建议使用一个枚举类型或自定义异常来处理不同的验证错误,以提高代码的可读性和可维护性。
l扩展性:如果将来需要添加更多的验证规则,可能会导致代码膨胀。可以考虑将验证逻辑抽象成接口,使用策略模式来处理不同的验证规则。
3. DBuser.java
优点:
l使用了 PreparedStatement,有效防止 SQL 注入。
l连接和断开数据库的逻辑封装得当。
改进建议:
l连接管理:建议使用连接池来管理数据库连接,以提高性能和资源利用率。
l异常处理:在数据库操作时,建议抛出自定义异常而不是直接打印堆栈跟踪,这样可以更好地处理错误并提供用户友好的反馈。
l代码重复:在userExists 方法中,查询用户的逻辑可以提取成一个通用的方法,以减少代码重复。
4. User.java
优点:
l简单明了的用户模型。
改进建议:
l数据验证:可以在 User 类中添加数据验证方法,以确保在设置属性时符合要求,进一步增强代码的健壮性。
l构造函数:可以考虑添加构造函数,以便在创建 User 对象时直接传入参数。
总结
你的后端代码已经具备了基本的功能,但通过一些结构上的调整和安全性的增强,可以使其更加健壮和易于维护。希望这些建议对你有所帮助!
6.3测试代码
测试代码包括API测试代码和GUI测试代码
6.3.1提出测试代码评审请求
请对测试代码进行评审
TestRegister.py
import requests
import unittest
import hashlib
import re
from Util import ClassDB
def hash_password(password):
"""对密码进行SHA256散列"""
return hashlib.sha256(password.encode('utf-8')).hexdigest()
class TestUserRegistration(unittest.TestCase):
# 注册接口的URL
def setUp(self):
self.REGISTER_URL = "http://127.0.0.1:8080/CharGPTEbusiness/jsp/register.jsp"
self.login_url ="http://127.0.0.1:8080/CharGPTEbusiness/register.jsp"
self.session = requests.Session()
response = self.session.get(self.login_url)
self.token = "".join(re.findall('name=\"csrfToken\" value=\"(.*?)\">',response.text))
def teardown_class(self):
ClassDB.init_db()
def test_valid_username(self):
"""TC1: 测试有效的账号(5-20位字母或数字)"""
print("TC1")
data = {
'username': "validUser123",
'password': hash_password("Valid123!"),
'phone': "13812345671",
'email': "test1@example.com",
}
response = self.session.post(self.REGISTER_URL, data=data)
self.assertEqual(response.status_code, 200)
self.assertIn("showerror(\"1\")",response.text)
def test_username_too_short(self):
"""TC2: 测试账号长度不足(少于5位)"""
print("TC2")
data = {
"username": "usr2",
"password": hash_password("Valid123!"),
"phone": "13812345672",
"email": "test2@example.com"
}
response = self.session.post(self.REGISTER_URL, data=data)
self.assertEqual(response.status_code, 200)
self.assertIn("账号必须是5-20位字母或数字", response.text)
def test_username_too_long(self):
"""TC3: 测试账号长度超出(超过20位)"""
print("TC3")
data = {
"username": "user12345678901234567890",
"password": hash_password("Valid123!"),
"phone": "13812345673",
"email": "test3@example.com"
}
response = self.session.post(self.REGISTER_URL, data=data)
self.assertEqual(response.status_code, 200)
self.assertIn("账号必须是5-20位字母或数字", response.text)
def test_valid_password_registration(self):
"""TC4: 输入符合要求的密码,预期结果:注册成功"""
print("TC4")
data = {
"username": "validUser4",
"password": hash_password("ValidPassword123!"),
"phone": "13812345674",
"email": "valid4@example.com"
}
response = self.session.post(self.REGISTER_URL, data=data)
self.assertEqual(response.status_code, 200)
self.assertIn("showerror(\"1\")", response.text)
def test_valid_mobile_number(self):
"""TC8: 输入有效的中国手机号码,预期结果:注册成功"""
print("TC8")
data = {
"username": "validUser8",
"password": hash_password("ValidPassword123!"),
"phone": "13812345677",
"email": "valid8@example.com"
}
response = self.session.post(self.REGISTER_URL, data=data)
self.assertEqual(response.status_code, 200)
self.assertIn("showerror(\"1\")", response.text)
def test_invalid_mobile_number_format(self):
"""TC9: 输入非法的手机号码格式,预期结果:显示错误信息'手机号格式错误'"""
print("TC9")
data = {
"username": "invalidPhoneUser9",
"password": hash_password("ValidPassword123!"),
"phone": "12345", # 故意输入错误格式的手机号
"email": "invalid9@example.com"
}
response = self.session.post(self.REGISTER_URL, data=data)
self.assertEqual(response.status_code, 200)
self.assertIn("手机号必须符合中国手机号码格式", response.text)
def test_valid_email(self):
"""TC10: 输入有效的邮箱地址,预期结果:注册成功"""
print("TC10")
data = {
"username": "newUser10",
"password": hash_password("SecurePassword123!"),
"phone": "13912345678",
"email": "use10r@example.com"
}
response = self.session.post(self.REGISTER_URL, data=data)
self.assertEqual(response.status_code, 200)
self.assertIn("showerror(\"1\")", response.text)
def test_invalid_email_format(self):
"""TC11: 输入非法的邮箱格式,预期结果:显示错误信息'邮箱格式错误'"""
print("TC11")
data = {
"username": "userWithBadEmail",
"password": hash_password("AnotherSecure123!"),
"phone": "13987654321",
"email": "bademail.com" # 故意输入错误格式的邮箱
}
response = self.session.post(self.REGISTER_URL, data=data)
self.assertEqual(response.status_code, 200)
self.assertIn("Email格式不正确", response.text)
def test_duplicate_username_registration(self):
print("TC16")
"""TC16: 同名用户注册测试"""
data = {
"username": "testUser",
"password": hash_password("Password123!"),
"phone": "13912345679",
"email": "test@example.com"
}
# 第一次注册
response1 = self.session.post(self.REGISTER_URL,data=data)
# 第二次注册
response2 = self.session.post(self.REGISTER_URL,data=data)
self.assertIn("showerror(\"username\")",response2.text)
def test_duplicate_email_registration(self):
"""TC17: 重复邮箱注册测试"""
print("TC17")
data1 = {
"username": "user1",
"password": hash_password("Password123!"),
"phone": "13912345680",
"email": "jerry@example.com"
}
data2 = {
"username": "user2",
"password": hash_password("Password123!"),
"phone": "13912345681",
"email": "jerry@example.com"
}
# 第一次注册
response = self.session.post(self.REGISTER_URL,data=data1)
# 第二次注册
response = self.session.post(self.REGISTER_URL,data=data2)
self.assertIn("showerror(\"email\")",response.text)
def test_duplicate_phone_registration(self):
"""TC18: 重复手机号注册测试"""
print("TC18")
data1 = {
"username": "user3",
"password": hash_password("Password123!"),
"phone": "13912345682",
"email": "user3@example.com"
}
data2 = {
"username": "user4",
"password": hash_password("Password123!"),
"phone": "13912345682",
"email": "user4@example.com"
}
# 第一次注册
self.session.post(self.REGISTER_URL,data=data1)
# 第二次注册
response = self.session.post(self.REGISTER_URL,data=data2)
self.assertIn("showerror(\"phone\")",response.text)
if __name__ == "__main__":
unittest.main()
Test_register_GUI.py
import pytest
from playwright.sync_api import Page
from Util import ClassDB
REGISTER_URL = 'http://127.0.0.1:8080/CharGPTEbusiness/register.html'
# 测试类
class TestUserRegistration:
def teardown_class(self):
ClassDB.init_db()
def test_valid_username(self,page: Page):
page.goto(REGISTER_URL)
page.fill('input[name="username"]', 'validUser123')
page.fill('input[name="password"]', 'ValidPass123!')
page.fill('input[name="confirmPassword"]', 'ValidPass123!')
page.fill('input[name="phone"]', '13800138000')
page.fill('input[name="email"]', 'test@example.com')
page.click('button[type="submit"]')
# 检查注册是否成功(这里需要根据实际页面反馈调整)
assert page.locator('text=注册成功').is_visible()
def test_password_length_error(self,page: Page):
page.goto(REGISTER_URL)
page.fill('input[name="username"]', 'testUser')
page.fill('input[name="password"]', 'NoSpecialChar1')
page.fill('input[name="confirmPassword"]', 'NoSpecialChar1')
page.fill('input[name="phone"]', '13800138000')
page.fill('input[name="email"]', 'test@example.com')
page.click('button[type="submit"]')
# 检查错误信息
assert page.locator('text=密码必须包含大小写字母、数字和特殊字符').is_visible()
def test_invalid_phone_format(self,page: Page):
page.goto(REGISTER_URL)
page.fill('input[name="username"]', 'testUser')
page.fill('input[name="password"]', 'ValidPass123!')
page.fill('input[name="confirmPassword"]', 'ValidPass123!')
page.fill('input[name="phone"]', '12345') # 非法手机号
page.fill('input[name="email"]', 'test@example.com')
page.click('button[type="submit"]')
# 检查错误信息
assert page.locator('text=请输入有效的中国手机号').is_visible()
def test_duplicate_registration(self,page: Page):
page.goto(REGISTER_URL)
# 第一次注册
page.fill('input[name="username"]', 'duplicateUser')
page.fill('input[name="password"]', 'ValidPass123!')
page.fill('input[name="confirmPassword"]', 'ValidPass123!')
page.fill('input[name="phone"]', '13800138001')
page.fill('input[name="email"]', 'duplicate@example.com')
page.click('button[type="submit"]')
# 检查注册成功
assert page.locator('text=注册成功').is_visible()
# 第二次注册相同用户
page.goto(REGISTER_URL)
page.fill('input[name="username"]', 'duplicateUser')
page.fill('input[name="password"]', 'ValidPass123!')
page.fill('input[name="confirmPassword"]', 'ValidPass123!')
page.fill('input[name="phone"]', '13800138001')
page.fill('input[name="email"]', 'duplicate@example.com')
page.click('button[type="submit"]')
# 检查错误信息
assert page.locator('text=用户名已经被注册过!').is_visible()
if __name__ == '__main__':
pytest.main(["-sv", "Test_register_GUI.py"])
6.2.2测试代码评审回复
你的测试代码结构清晰,功能覆盖全面,能够有效测试用户注册功能。以下是对 TestRegister.py 和 Test_register_GUI.py 的评审和改进建议:
1. TestRegister.py
优点:
l结构清晰:使用 unittest 框架,测试用例分明,易于理解。
l覆盖全面:涵盖了有效注册、无效输入、重复用户等多种场景。
l使用了会话:通过 requests.Session() 维护会话状态,适合于需要保持登录状态的测试。
改进建议:
lteardown_class 方法命名:根据 unittest 的命名约定,建议将 teardown_class 改为 tearDownClass,以便于识别和遵循 Python 的命名规范。(改为def tearDown(self))
l错误信息检查:在检查错误信息时,建议使用更具体的断言,例如 self.assertIn("手机号格式错误", response.text),以确保捕获到的错误信息是准确的。
l使用常量:将 URL 和其他常量提取为类属性或常量,以便于维护和修改。
l测试用例命名:可以考虑更清晰的命名,例如 test_username_length_too_short,以便于快速理解每个测试用例的意图。(修改)
l数据库状态管理:在每个测试后重置数据库状态,以确保测试之间不会相互影响。
2. Test_register_GUI.py
优点:
l使用 Playwright:通过 Playwright 进行 GUI 测试,能够模拟用户操作,适合前端功能测试。
l覆盖全面:涵盖了有效注册、密码长度错误、手机号格式错误等场景。
改进建议:
l错误信息检查:在检查错误信息时,可以考虑使用 assert page.locator('text=...').is_visible() 来确保错误信息的可见性,增强测试的可靠性。
l页面加载等待:在进行页面交互之前,可以考虑添加等待逻辑,以确保页面完全加载。这可以通过 page.wait_for_load_state('networkidle') 或者其他适合的方法实现。
l测试用例命名:同样,建议使用更具描述性的测试用例名称,例如 test_valid_username_registration,以便于快速理解每个测试用例的目的。
l代码重复:在多个测试用例中可能会重复使用相同的输入数据,建议将这些数据提取到类属性或方法中,以减少重复代码。
总体建议
l使用 Mock 或 Fixture:在进行数据库操作时,可以考虑使用 Mock 或 Fixture,以避免对实际数据库的依赖,确保测试的独立性和稳定性。
l测试报告:可以考虑集成测试报告工具,记录测试结果,便于后续分析和改进。
l代码风格:确保遵循 PEP 8 风格指南,保持代码的一致性和可读性。
总体来说,你的测试代码已经相当不错,通过以上的改进建议,可以进一步提升测试的稳定性和可维护性。希望这些建议对你有所帮助!