4 GUI的测试代码
4.1为什么要使用GUI的测试代码
在TestRegister.py使用
代码语言:javascript
复制
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中类似
代码语言:javascript
复制
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的测试脚本。
前端代码
代码语言:javascript
复制
<!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 – 一个用户重复注册两次:
一个用户重复注册两次,预期结果:用户名已经被注册过!"。
输出
代码语言:javascript
复制
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()
说明:
路径替换:请确保在 self.page.goto() 中替换为您的 HTML 文件的正确路径。
错误信息检查:根据您的实际页面反馈,可能需要调整断言中的文本。
浏览器设置:可以根据需要设置 headless 参数来选择是否在后台运行浏览器。
运行测试:确保已安装 pytest 和 playwright,可以通过 pytest 命令运行测试。
请根据您的实际需求调整测试脚本。
4.3 调整
4.3.1 调整GUI的测试代码
修改
代码语言:javascript
复制
self.page.goto('file:///path/to/your/html/file.html')
为
代码语言:javascript
复制
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也用的是这个手机号,所以改为
代码语言:javascript
复制
self.page.fill('input[name="phone"]', '13800138001') …
# 第二次注册相同用户
代码语言:javascript
复制
… page.fill('input[name="phone"]', '13800138001') …
另外抽出去掉stup()方法,抽出REGISTER_URL作为全局变量
代码语言:javascript
复制
REGISTER_URL = 'http://127.0.0.1:8080/CharGPTEbusiness/register.html'
改造每个几个方法
代码语言:javascript
复制
# 测试类 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)
最后与接口测试用例一样,为每个程序结束增加一个情况数据库的方法
代码语言:javascript
复制
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()
通过
代码语言:javascript
复制
pytest -sv .\Test_register_GUI.py –headed
命令运行
4.3.2 调整合并的测试代码
在接口代码和GUI代码都是用热清空数据库的操作,现在来进行统一的封装合并。
建立Util.py
代码语言:javascript
复制
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
代码语言:javascript
复制
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文件形成单元测试用例
代码语言:javascript
复制
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
代码语言:javascript
复制
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
代码语言:javascript
复制
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。
代码语言:javascript
复制
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 来模拟数据库操作。以下是一个简单的测试示例。
代码语言:javascript
复制
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 方法。以下是一个简单的示例:
代码语言:javascript
复制
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中加上
代码语言:javascript
复制
<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给出修正后的代码
代码语言:javascript
复制
@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
代码语言:javascript
复制
<!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
代码语言:javascript
复制
<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
代码语言:javascript
复制
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
代码语言:javascript
复制
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)
优点:
清晰的表单结构,标签和输入框的配对良好。
使用了 required 属性来确保用户输入。
改进建议:
表单的 action 属性:建议使用更现代的方式处理表单提交,比如使用 AJAX,而不是直接提交到 jsp/register.jsp。这样可以提高用户体验。
错误信息显示:目前错误信息是通过 div 显示的,可以考虑在表单提交后通过 AJAX 获取并更新这些信息,而不是在页面上直接输出。
安全性:考虑使用 HTTPS 来保护用户数据。
2. CSS (index.css)
优点:
代码结构清晰,使用了 JSP 标签。
改进建议:
CSS 文件命名:文件名 index.css 可能不够具体,建议使用 register.css 或类似名称以提高可读性。改为register.css
分离逻辑和样式:尽量将 CSS 和 JSP 逻辑分开,保持 CSS 文件只包含样式,而不包含 Java 逻辑。CSS中没有包含逻辑
3. JavaScript (index.js)
优点:
使用正则表达式进行输入验证,确保用户输入符合要求。
在输入不合法时,及时给出反馈。
改进建议:
密码哈希:建议使用 HTTPS 进行数据传输,虽然你在客户端进行了 SHA-256 哈希,但这并不能替代后端的安全措施。
用户体验:注册成功的提示使用了 alert,可以考虑使用更友好的方式,比如在页面上显示成功消息,而不是弹出框。(取消alert,改为console.log)
代码重复:在validateForm中的错误信息显示逻辑可以提取成一个函数,以减少重复代码。
4. 响应处理 (showerror.js)
优点:
使用 switch 语句处理不同的错误消息,逻辑清晰。
改进建议:
DOM 操作:使用 document.writeln 不是最佳实践,建议使用 innerText 或 innerHTML 来更新页面内容。(以后改为重定向)
函数的通用性:可以考虑将错误信息的处理逻辑封装成一个更通用的函数,接受错误类型和目标元素作为参数。(目前错误信息比较少,暂不考虑)
5. 其他建议
输入验证:可以在后端再次进行输入验证,确保数据的安全性和有效性。(的确如此处理)
用户体验:在注册成功或失败后,可以考虑重定向用户到登录页面或其他页面,而不是停留在注册页面。(下面考虑)
代码注释:在 JavaScript 和 JSP 中添加适当的注释,帮助其他开发者理解代码逻辑。
总结
你的前端代码已经有了良好的基础,经过一些改进后可以提升用户体验和代码的可维护性。希望这些建议对你有所帮助!
6.1.3 封装validateForm()函数
输入
对于validateForm 中的错误信息显示逻辑可以提取成一个函数,以减少重复代码。请帮助实现下。
输出
当然可以!为了减少重复代码,我们可以创建一个辅助函数来处理错误信息的显示。在 validateForm 函数中,当验证失败时,我们调用这个新函数来更新对应的错误信息。以下是修改后的代码示例:
修改后的 index.js
代码语言:javascript
复制
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
代码语言:javascript
复制
<%@ 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
代码语言:javascript
复制
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
代码语言:javascript
复制
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
代码语言:javascript
复制
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
优点:
表单结构清晰,能够处理用户注册逻辑。
使用了validateUser 类进行用户输入的验证。
改进建议:
业务逻辑与视图分离:建议将业务逻辑(如数据库操作和验证)移到 Servlet 或其他控制器中,而不是直接在 JSP 中处理。这样可以更好地遵循 MVC 模式。
错误处理:在发生异常时,建议返回用户友好的错误信息,而不是直接打印异常信息。可以考虑使用日志记录异常。
安全性:在处理用户密码时,确保密码在存储前进行加密处理,而不是在数据库中以明文形式存储。
HTML 输出:在 body 标签的 onload 中直接调用 showerror 函数并传递 info,这可能会导致 XSS 攻击。建议使用 HTML 实体编码来避免潜在的安全问题(考虑避免)。
2. validateUser.java
优点:
使用正则表达式进行输入验证,逻辑清晰。
改进建议:
返回值设计:返回字符串作为验证结果的方式不够灵活,建议使用一个枚举类型或自定义异常来处理不同的验证错误,以提高代码的可读性和可维护性。
扩展性:如果将来需要添加更多的验证规则,可能会导致代码膨胀。可以考虑将验证逻辑抽象成接口,使用策略模式来处理不同的验证规则。
3. DBuser.java
优点:
使用了 PreparedStatement,有效防止 SQL 注入。
连接和断开数据库的逻辑封装得当。
改进建议:
连接管理:建议使用连接池来管理数据库连接,以提高性能和资源利用率。
异常处理:在数据库操作时,建议抛出自定义异常而不是直接打印堆栈跟踪,这样可以更好地处理错误并提供用户友好的反馈。
代码重复:在userExists 方法中,查询用户的逻辑可以提取成一个通用的方法,以减少代码重复。
4. User.java
优点:
简单明了的用户模型。
改进建议:
数据验证:可以在 User 类中添加数据验证方法,以确保在设置属性时符合要求,进一步增强代码的健壮性。
构造函数:可以考虑添加构造函数,以便在创建 User 对象时直接传入参数。
总结
你的后端代码已经具备了基本的功能,但通过一些结构上的调整和安全性的增强,可以使其更加健壮和易于维护。希望这些建议对你有所帮助!
6.3测试代码
测试代码包括API测试代码和GUI测试代码
6.3.1提出测试代码评审请求
请对测试代码进行评审
TestRegister.py
代码语言:javascript
复制
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
代码语言:javascript
复制
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
优点:
结构清晰:使用 unittest 框架,测试用例分明,易于理解。
覆盖全面:涵盖了有效注册、无效输入、重复用户等多种场景。
使用了会话:通过 requests.Session() 维护会话状态,适合于需要保持登录状态的测试。
改进建议:
teardown_class 方法命名:根据 unittest 的命名约定,建议将 teardown_class 改为 tearDownClass,以便于识别和遵循 Python 的命名规范。(改为def tearDown(self))
错误信息检查:在检查错误信息时,建议使用更具体的断言,例如 self.assertIn("手机号格式错误", response.text),以确保捕获到的错误信息是准确的。
使用常量:将 URL 和其他常量提取为类属性或常量,以便于维护和修改。
测试用例命名:可以考虑更清晰的命名,例如 test_username_length_too_short,以便于快速理解每个测试用例的意图。(修改)
数据库状态管理:在每个测试后重置数据库状态,以确保测试之间不会相互影响。
2. Test_register_GUI.py
优点:
使用 Playwright:通过 Playwright 进行 GUI 测试,能够模拟用户操作,适合前端功能测试。
覆盖全面:涵盖了有效注册、密码长度错误、手机号格式错误等场景。
改进建议:
错误信息检查:在检查错误信息时,可以考虑使用 assert page.locator('text=...').is_visible() 来确保错误信息的可见性,增强测试的可靠性。
页面加载等待:在进行页面交互之前,可以考虑添加等待逻辑,以确保页面完全加载。这可以通过 page.wait_for_load_state('networkidle') 或者其他适合的方法实现。
测试用例命名:同样,建议使用更具描述性的测试用例名称,例如 test_valid_username_registration,以便于快速理解每个测试用例的目的。
代码重复:在多个测试用例中可能会重复使用相同的输入数据,建议将这些数据提取到类属性或方法中,以减少重复代码。
总体建议
使用 Mock 或 Fixture:在进行数据库操作时,可以考虑使用 Mock 或 Fixture,以避免对实际数据库的依赖,确保测试的独立性和稳定性。
测试报告:可以考虑集成测试报告工具,记录测试结果,便于后续分析和改进。
代码风格:确保遵循 PEP 8 风格指南,保持代码的一致性和可读性。
总体来说,你的测试代码已经相当不错,通过以上的改进建议,可以进一步提升测试的稳定性和可维护性。希望这些建议对你有所帮助!