嘿,朋友!我是Agnes。今天我们要聊的话题,可能有点“扎心”,但绝对是你职业生涯中能直接提价的硬技能——Web应用测试。
你知道那种痛苦吗?代码写了一周,自认为完美无缺,结果上线第一天,用户投诉炸裂,系统崩溃,老板在群里@你,技术总监在办公室里咆哮,而你只能对着满屏的报错日志发呆。这种场景,每个开发者或测试工程师都经历过。
别慌,这篇文章就是为你准备的“救命稻草”。我们将一起走完全流程:环境搭建 -> 接口测试(Postman) -> UI自动化(Selenium) -> 真实Bug挖掘与修复。我会用最通俗的语言,结合具体的代码和案例,让你不仅能看懂,还能立刻动手做。
第一部分:为什么测试这么重要?(别嫌我啰嗦)
在开始之前,我想先分享一个小故事。
去年,我们团队为一个电商网站开发了一个“限时秒杀”功能。功能上线前,我们只做了简单的功能验证:点按钮,商品进入购物车,没问题吧?没问题。
结果上线当天,并发量一上来,服务器直接宕机。更糟糕的是,有一个Bug导致用户下单后,钱扣了,但订单没生成。用户投诉如潮,客服电话被打爆。
如果当时我们做了压力测试?如果当时我们测试了异常场景?
这就是为什么要重视测试。测试不是“找茬”,而是“提前暴露问题”。
第二部分:环境搭建——工欲善其事,必先利其器
测试的第一步,是搭建一个稳定的测试环境。不要直接在生产环境测试,那是“自杀”。
2.1 本地测试环境搭建
我们以一个简单的用户登录系统为例。假设我们要测试一个Web应用,它有:
- 登录接口:
POST /api/login - 用户信息接口:
GET /api/user/info - 前端页面:
http://localhost:3000/login
步骤1:启动本地服务
假设你有一个简单的Node.js后端(Express)和React前端。
后端代码 (server.js):
const express = require('express');
const app = express();
app.use(express.json());
// 模拟用户数据库
const users = [
{ id: 1, username: 'admin', password: '123456', name: '管理员' },
{ id: 2, username: 'testuser', password: 'test123', name: '测试用户' }
];
// 登录接口
app.post('/api/login', (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.username === username && u.password === password);
if (user) {
// 实际项目中应使用JWT,这里简化返回用户信息
res.json({ success: true, user: { id: user.id, name: user.name } });
} else {
res.status(401).json({ success: false, message: '用户名或密码错误' });
}
});
// 获取用户信息接口(需要登录)
app.get('/api/user/info', (req, res) => {
const token = req.headers.authorization;
if (token === 'valid-token-123') { // 简单模拟token验证
res.json({ success: true, userInfo: { id: 1, name: '管理员' } });
} else {
res.status(403).json({ success: false, message: '未授权' });
}
});
app.listen(3001, () => {
console.log('后端服务运行在 http://localhost:3001');
});
前端代码 (App.js):
import React, { useState } from 'react';
import axios from 'axios';
function App() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [message, setMessage] = useState('');
const handleLogin = async () => {
try {
const response = await axios.post('http://localhost:3001/api/login', {
username,
password
});
setMessage(response.data.success ? '登录成功!' : response.data.message);
if (response.data.success) {
// 存储token(简化)
localStorage.setItem('token', 'valid-token-123');
}
} catch (error) {
setMessage('网络错误');
}
};
return (
<div>
<h2>登录系统</h2>
<input
type="text"
placeholder="用户名"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<input
type="password"
placeholder="密码"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button onClick={handleLogin}>登录</button>
<p>{message}</p>
</div>
);
}
export default App;
步骤2:启动服务
# 终端1:启动后端
node server.js
# 终端2:启动前端
npm start
现在,你的测试环境就搭建好了!前端运行在http://localhost:3000,后端运行在http://localhost:3001。
第三部分:接口测试——用Postman守护API的底线
接口测试是测试的“第一道防线”。API出问题了,UI再漂亮也没用。
3.1 安装Postman
前往 Postman官网 下载并安装。界面非常直观,我们直接开始。
3.2 测试登录接口
场景1:正常登录
- 打开Postman,新建一个请求。
- 选择
POST方法,输入URL:http://localhost:3001/api/login - 在
Body标签页,选择raw和JSON,输入:
{
"username": "admin",
"password": "123456"
}
- 点击
Send。
预期结果:返回200 OK,且包含用户信息。
实际结果:
{
"success": true,
"user": {
"id": 1,
"name": "管理员"
}
}
✅ 测试通过!
场景2:错误密码
- 修改
Body中的密码为wrongpassword。 - 点击
Send。
预期结果:返回401 Unauthorized,且包含错误信息。
实际结果:
{
"success": false,
"message": "用户名或密码错误"
}
✅ 测试通过!
场景3:用户名不存在
- 修改
Body中的用户名为nonexistent。 - 点击
Send。
预期结果:返回401 Unauthorized。
实际结果:
{
"success": false,
"message": "用户名或密码错误"
}
✅ 测试通过!
3.3 测试用户信息接口(需要授权)
场景4:未登录访问
- 新建请求,选择
GET方法,输入URL:http://localhost:3001/api/user/info - 点击
Send。
预期结果:返回403 Forbidden。
实际结果:
{
"success": false,
"message": "未授权"
}
✅ 测试通过!
场景5:登录后访问
- 在
Headers标签页,添加:- Key:
Authorization - Value:
valid-token-123
- Key:
- 点击
Send。
预期结果:返回200 OK,且包含用户信息。
实际结果:
{
"success": true,
"userInfo": {
"id": 1,
"name": "管理员"
}
}
✅ 测试通过!
3.4 发现Bug!
等等,这里有一个潜在的Bug。在场景2和场景3中,无论密码错误还是用户名不存在,返回的错误信息都是“用户名或密码错误”。
这是不是泄露了信息? 是的!攻击者可以通过这个错误信息判断用户名是否存在,从而进行“用户名枚举攻击”。
如何修复?
修改后端代码,让两种情况返回相同的错误信息,但内部逻辑更健壮:
// 修改后的登录接口
app.post('/api/login', (req, res) => {
const { username, password } = req.body;
// 先查找用户
const user = users.find(u => u.username === username);
if (!user) {
// 用户名不存在,也返回相同的错误信息,不泄露具体原因
return res.status(401).json({ success: false, message: '用户名或密码错误' });
}
// 用户存在,但密码错误
if (user.password !== password) {
return res.status(401).json({ success: false, message: '用户名或密码错误' });
}
// 登录成功
res.json({ success: true, user: { id: user.id, name: user.name } });
});
这样,无论是用户名不存在还是密码错误,返回的错误信息都是一致的,避免了信息泄露。
第四部分:UI自动化测试——用Selenium模拟真实用户行为
接口测试搞定了,接下来是UI测试。Selenium是最流行的Web自动化测试工具,它像真人一样操作浏览器。
4.1 安装Selenium
步骤1:安装Selenium库
以Python为例:
pip install selenium webdriver-manager
步骤2:安装WebDriver
webdriver-manager会自动下载对应浏览器的WebDriver,非常方便。
4.2 编写第一个UI测试脚本
我们将测试登录页面:
- 打开登录页面
- 输入正确的用户名和密码
- 点击登录
- 验证登录成功
测试脚本 (test_login.py):
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service
# 设置WebDriver
service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service)
try:
# 1. 打开登录页面
driver.get('http://localhost:3000')
# 2. 输入用户名和密码
username_input = driver.find_element(By.PLACEHOLDER, "用户名")
password_input = driver.find_element(By.PLACEHOLDER, "密码")
login_button = driver.find_element(By.TAG_NAME, "button")
username_input.send_keys('admin')
password_input.send_keys('123456')
# 3. 点击登录
login_button.click()
# 4. 验证登录成功(等待消息出现)
wait = WebDriverWait(driver, 10)
message = wait.until(EC.presence_of_element_located((By.TAG_NAME, 'p')))
assert message.text == '登录成功!', f"预期登录成功,但实际消息为: {message.text}"
print("✅ UI测试通过:登录成功")
except Exception as e:
print(f"❌ UI测试失败:{e}")
finally:
# 关闭浏览器
driver.quit()
运行这个脚本,你会看到浏览器自动打开、输入、点击,最后显示“登录成功”。
4.3 测试错误登录
修改脚本,测试错误密码的情况:
# ... 前面的代码相同 ...
# 2. 输入错误的用户名和密码
username_input.send_keys('admin')
password_input.send_keys('wrongpassword')
# 3. 点击登录
login_button.click()
# 4. 验证登录失败
wait = WebDriverWait(driver, 10)
message = wait.until(EC.presence_of_element_located((By.TAG_NAME, 'p')))
assert message.text == '用户名或密码错误', f"预期登录失败,但实际消息为: {message.text}"
print("✅ UI测试通过:错误密码提示正确")
4.4 发现Bug!
等等,如果我在用户名输入框中输入SQL注入代码呢?比如:
admin' OR '1'='1
密码随便输入。
这会导致什么? 如果后端没有做SQL过滤,可能会绕过登录!
如何测试? 在Postman中测试登录接口,输入:
{
"username": "admin' OR '1'='1",
"password": "anything"
}
预期结果:返回401错误。
实际结果:如果后端没有防护,可能返回200成功!
这是严重的安全漏洞!
如何修复?
在后端对输入进行过滤和转义:
// 使用参数化查询,避免SQL注入
const db = require('./db'); // 假设你使用了数据库
app.post('/api/login', (req, res) => {
const { username, password } = req.body;
// 使用参数化查询
db.query('SELECT * FROM users WHERE username = ? AND password = ?', [username, password], (err, results) => {
if (err) {
return res.status(500).json({ success: false, message: '服务器错误' });
}
if (results.length > 0) {
res.json({ success: true, user: { id: results[0].id, name: results[0].name } });
} else {
res.status(401).json({ success: false, message: '用户名或密码错误' });
}
});
});
参数化查询能有效防止SQL注入。
第五部分:集成测试——让Postman和Selenium协同工作
在实际项目中,接口测试和UI测试需要协同工作。比如,先通过接口获取token,再用token进行UI操作。
5.1 在Selenium中使用Postman获取的token
修改UI测试脚本,先通过API获取token,再用token登录:
import requests
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service
# 1. 通过API获取token
login_data = {
'username': 'admin',
'password': '123456'
}
response = requests.post('http://localhost:3001/api/login', json=login_data)
token = response.json()['token'] # 假设API返回token
# 2. 使用token进行UI登录
service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service)
try:
driver.get('http://localhost:3000')
# 假设前端有一个输入框用于输入token
token_input = driver.find_element(By.ID, 'token-input')
token_input.send_keys(token)
login_button = driver.find_element(By.TAG_NAME, 'button')
login_button.click()
# 验证登录成功
wait = WebDriverWait(driver, 10)
message = wait.until(EC.presence_of_element_located((By.TAG_NAME, 'p')))
assert message.text == '登录成功!', f"预期登录成功,但实际消息为: {message.text}"
print("✅ 集成测试通过")
except Exception as e:
print(f"❌ 集成测试失败:{e}")
finally:
driver.quit()
第六部分:测试报告与持续集成
测试做完,结果记录在哪里?当然是报告里!
6.1 使用Allure生成美观报告
Allure是一个强大的测试报告框架,支持Python和Java。
安装Allure:
pip install allure-pytest
修改测试脚本,添加Allure标记: “`python import allure import pytest from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service
@allure.feature(“登录功能”) class TestLogin:
@allure.story("正常登录")
def test_normal_login(self):
service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service)
try:
driver.get('http://localhost:3000')
username_input = driver.find_element(By.PLACEHOLDER, "用户名")
password_input = driver.find_element(By.PLACEHOLDER, "密码")
login_button = driver.find_element(By.TAG_NAME, "button")
username_input.send_keys('admin')
password_input.send_keys('123456')
login_button.click()
wait = WebDriverWait(driver, 10)
message = wait.until(EC.presence_of_element_located((By.TAG_NAME, 'p')))
assert message.text == '登录成功!'
allure.attach(driver.get_screenshot_as_png(), name="登录成功截图", attachment_type=allure.attachment_type.PNG)
print("✅ 正常登录测试通过")
finally:
