项目介绍
ZipLink闪链短链接平台是一款提供高效、便捷短链接生成与管理服务的SaaS平台,用户可以通过订阅或付费方式使用该服务。通过ZipLink,用户能够轻松地将原始的长URL转换为简洁美观的短链接,便于记忆和分享。同时,ZipLink提供丰富的统计和分析功能,帮助用户了解链接的点击次数、地理位置、设备类型等详细数据,提升用户的决策能力。
ZipLink闪链短链接平台的主要优势包括:
- 优化用户体验:通过简洁的短链接,用户更容易记忆和分享,显著提升使用体验。
- 节省字符空间:特别适用于字符数受限的平台,如微博、短信等场景中使用,简洁实用。
- 简洁美观:相较于冗长的URL,ZipLink生成的短链接外观更整洁、更专业。
- 全面的统计与分析:为用户提供详细的访问数据报告,帮助用户深入了解链接表现和用户行为。
ZipLink闪链平台致力于为用户提供稳定、安全、易用的短链接服务,适用于各种营销、推广和数据分析需求。
测试用例设计
以“创建短链”功能为例,用户在输入下面的内容之后,点击确定即可创建一条短链接
- 跳转链接:以 http 或者 https 开头的链接或者应用跳转链接
- 描述信息:在输入跳转链接之后会自动补充该链接对应的描述;也可以为用户自行填写
- 短链分组:对于每一条将要创建的短链接都需要选择一个已经创建好的分组
- 有效期:分为永久有效期和自定义有效期,自定义有效期会存在过期现象,届时此条短链接无法跳转
测试用例设计如下:
1、功能测试
- URL 地址输入
- 输入以
http://开头的有效 URL(如http://example.com),点击确认,验证短链接创建成功并返回正确的短链 - 输入以
https://开头的有效 URL(如https://example.com),点击确认, 验证短链接创建成功 - 输入不以
http://或https://开头的 URL(如ftp://example.com),验证系统提示“请输入 http:// 或 https:// 开头的链接或应用跳转链接” - 输入格式错误的 URL(如
http:///example,http://),验证系统提示“请输入 http:// 或 https:// 开头的链接或应用跳转链接” - 输入不存在的 URL(如
https://www.baidu.com/sfa),验证描述信息中会展示“Error while fetching title.” - 输入超长的 URL(如长度超过 2048 字符),点击确认,验证系统提示“URL 过长”或自动截断
- 输入以
- 描述信息输入
- 输入有效 URL 后,验证描述信息是否自动补全(如根据 URL 生成简要描述)
- 自动补全后手动修改描述信息,点击确认,验证修改后的描述被正确保存
- 输入空描述信息,点击确认,验证系统是否允许为空或自动填充默认描述(如“无描述”)
- 输入超长描述信息(如超过 500 字符),点击确认,验证系统提示“描述过长”或自动截断
- 短链分组选择
- 选择一个已存在的分组(如“营销活动”),点击确认,验证短链接被正确分配到该分组
- 在当前无分组的情况下创建短链接,验证系统自动分配至“默认分组”或提示用户创建分组
- 尝试选择不存在的分组(通过前端绕过,如手动输入分组 ID),点击确认,验证系统显示错误信息或将链接分配至“未分组”
- 有效期设置
- 选择“永久有效期”,点击确认,验证创建的短链接没有过期时间
- 选择“自定义有效期”,设置未来日期(如 30 天后),点击确认,验证短链接在该日期前有效
- 选择“自定义有效期”,设置过去日期(如昨天),点击确认,验证系统提示“无效的有效期”
- 未选择有效期选项,点击确认,验证系统是否应用默认有效期(如“永久”)
- 确认按钮操作
- 所有必填项正确填写,点击确认,验证短链接创建成功并显示成功消息及短链接
- 必填项未填写或填写不正确(如缺少 URL),点击确认,验证系统显示“创建失败”的错误提示
2、性能测试
- 基本性能测试
- 测试单个用户在无负载条件下创建短链接的响应时间,重复操作多次,计算平均响应时间是否符合预期
- 负载测试
- 并发用户测试:评估系统在多用户同时创建短链时的性能,使用 JMeter 模拟 1000、4000、10000 等不同数量的并发用户,验证在不同并发用户数下系统是否保持稳定响应时间,错误率应保持在低水平
- 持续负载测试:在一段时间内模拟恒定负载的用户请求,系统应在整个测试期间保持性能稳定,无明显退化
- 压力测试
- 最大承载:逐步增加并发用户数,直到系统性能明显下降或崩溃,识别系统的最大承载能力,并确保在接近此极限时系统能够优雅降级,而不是直接崩溃
- 瞬时高峰测试:模拟瞬时大批量用户(如1000用户)同时发起短链创建请求,系统应能处理瞬时高峰请求,并在负载消失后快速恢复
3、界面测试
- 布局和显示
- 验证所有输入框(URL、描述)、下拉菜单(分组)、日期选择器(有效期)及确认按钮在页面上正确显示且对齐
- 验证自动补全描述信息后,描述框内容正确显示,并支持编辑
- 响应式设计
- 调整浏览器窗口大小,验证界面元素是否自适应布局,保持良好显示
- 在移动端设备上访问创建界面,验证触摸元素(按钮、输入框)是否易于操作
- 提示信息
- 在输入错误信息时(如无效 URL),验证错误提示信息是否清晰、易懂且位置合适
- 短链接创建成功后,验证成功提示信息及短链接展示是否明显且易于复制
4、安全性测试
- 输入验证
- 跨站脚本攻击测试:在 URL 或描述信息输入框中输入恶意脚本,如
<script>alert('XSS')</script>,点击确认,验证系统是否对输入进行转义或过滤,防止 XSS 攻击 - SQL 注入测试:在 URL 或描述信息输入框中输入 SQL 注入语句,如
'; DROP TABLE users; --,点击确认,验证系统是否防止 SQL 注入并提示输入错误
- 跨站脚本攻击测试:在 URL 或描述信息输入框中输入恶意脚本,如
- 权限控制
- 未授权访问测试:未登录用户尝试访问短链接创建界面,确认系统是否重定向至登录页面或拒绝访问
- 越权测试:已登录用户只能访问到自己创建的短链接,无法越权访问其他用户创建的短链接
- 数据保护
- 数据传输加密:通过网络抓包工具 Charles 拦截创建短链接请求,验证数据传输是否加密,比如说使用 HTTPS
- 短链接唯一性:验证短链接生成的唯一性,即确保在相同输入下不会生成相同的短链接,防止碰撞和覆盖
5、兼容性测试
- 浏览器兼容性
- 不同浏览器测试:在最新版本的 Chrome、Firefox、Safari、Edge 和 Opera 浏览器上测试短链创建过程,确保功能一致性
- 浏览器版本测试:在不同版本的同一浏览器上进行测试,验证旧版本浏览器的兼容性
- 操作系统兼容性
- 桌面操作系统测试:在 Windows、macOS 和 Linux 系统上使用不同浏览器测试短链创建功能
- 移动操作系统测试:在 iOS 和 Android 设备上使用移动浏览器 Safari, Chrome 测试短链创建功能
- 设备兼容性
- 响应式设计测试:在不同屏幕尺寸的设备上(如手机、平板、桌面)测试,确保用户界面自适应调整,验证在横屏和竖屏模式下的显示效果
- 高分辨率屏幕测试:在支持Retina显示屏或2K、4K分辨率的设备上测试界面清晰度
- 网络兼容性
- 不同网络速度测试:在不同的网络条件下(如高速 Wi-Fi、4G、3G、2G)测试短链创建功能,观察性能变化
- 网络延迟测试:模拟高延迟网络环境,验证系统在响应时间和用户体验上的表现
6、易用性测试
- 用户操作流程
- 观察用户是否能在无指导的情况下找到“创建短链”功能入口
- 评估用户是否能理解每个输入字段(跳转链接、描述信息、短链分组、有效期)的用途
- 测量用户从打开功能界面到成功创建短链的时间
- 界面布局和导航
- 确保所有必填字段在界面上清晰可见且标记明确。
- 测试用户能否轻松找到并使用“确定”按钮。
- 验证导航栏或菜单项的设计是否支持快速访问短链创建功能
- 提示和反馈
- 在用户输入错误信息时,系统是否及时提供友好的错误反馈,指导用户修正。
- 在用户成功创建短链接后,是否提供明确的成功指示(如弹出提示、跳转至短链接详情页)
- 帮助和文档
- 验证界面是否提供必要的帮助文档或提示,如“如何选择有效期”、“什么是短链分组”
- 检查是否有悬停提示或信息图标,帮助用户理解各输入项的作用和格式要求
- 错误恢复
- 在网络中断或意外错误发生时,系统是否能够保存用户输入的数据,允许用户重新提交而不丢失信息
- 用户输入部分信息后意外刷新页面,验证系统是否能够恢复部分或全部输入内容
UI 自动化测试
使用 Selenium 进行 Web 自动化测试,实现用户登录、创建短链接、跳转短链接、删除短链接、修改等短链接等操作。
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.chrome.options import Options
import pytest
@pytest.fixture(scope="module")
def driver():
chrome_options = Options()
chrome_options.add_argument("--remote-allow-origins=*")
service = Service('path/to/chromedriver') # chromedriver的路径
driver = webdriver.Chrome(service=service, options=chrome_options)
driver.maximize_window()
yield driver
driver.quit()
def test_shortlink(driver):
driver.get("http://60.205.178.95/login")
wait = WebDriverWait(driver, 20)
# 用户登录
login_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, ".login-box .btn-gourp .el-button[data-v-cf06c5d9]")))
login_button.click()
print("登录按钮已成功点击.")
time.sleep(2)
# 创建短链接
add_shortlink_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[@class='el-button el-button--primary addButton' and @type='button' and @aria-disabled='false']")))
add_shortlink_button.click()
print("添加短链接按钮已成功点击")
time.sleep(2)
# 输入原始长连接
input_field = wait.until(EC.element_to_be_clickable((By.XPATH, "//input[@placeholder='请输入http://或https://开头的链接或应用跳转链接']")))
input_field.send_keys("https://liaoxuefeng.com/books/summerframework/introduction/index.html")
time.sleep(4)
# 确认创建短链接
add_shortlink_confirm_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[@class='el-button el-button--primary buttons' and @type='button' and not(@aria-disabled='true')]")))
add_shortlink_confirm_button.click()
# 实现短链接跳转
link = wait.until(EC.element_to_be_clickable((By.XPATH, "//a[@class='el-link el-link--primary']")))
link.click()
time.sleep(2)
original_window = driver.current_window_handle
for window_handle in driver.window_handles:
if window_handle != original_window:
driver.switch_to.window(window_handle)
break
driver.switch_to.window(original_window) # 返回到原来的窗口
time.sleep(1)
driver.refresh() # 刷新当前页面
# 查看短链接统计之后的统计信息
table_icons = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, ".table-edit")))
if table_icons:
diagram_icon = table_icons[1]
diagram_icon.click()
time.sleep(2)
else:
print("没有找到任何符合条件的元素")
width = driver.execute_script("return document.body.scrollWidth;") # 获取页面宽度
click_x = int(width * 0.2)
click_y = 500
actions = ActionChains(driver) # 移动鼠标到指定位置并点击
actions.move_by_offset(click_x, click_y).click().perform()
# 删除这条短链接
if table_icons:
delete_icon = table_icons[3]
delete_icon.click()
delete_confirm_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[contains(@class, 'el-button--primary') and contains(@class, 'el-button--small') and span[text()='确定']]")))
delete_confirm_button.click()
else:
print("没有找到任何符合条件的元素")
# 点击回收站
recycle_box = wait.until(EC.element_to_be_clickable((By.XPATH, "//div[contains(@class, 'recycle-box') and contains(text(), '回收站')]")))
recycle_box.click()
time.sleep(2)
# 恢复这条短链接
resumption_button = wait.until(EC.element_to_be_clickable((By.XPATH, "(//i[@class='el-icon table-edit el-tooltip__trigger el-tooltip__trigger'])[2]")))
resumption_button.click()
time.sleep(2)
# 回到“技术博客”分组
blog_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//span[text()='技术博客']")))
blog_button.click()
time.sleep(2)
second_edit_icon = wait.until(EC.element_to_be_clickable((By.XPATH, "(//div[@style='display: flex; align-items: center;']//i[contains(@class, 'table-edit')])[3]")))
second_edit_icon.click()
# 更新短链接描述
textarea = wait.until(EC.element_to_be_clickable((By.XPATH, "//textarea[@placeholder='可通过换行创建多个短链,一行一个,单次最多创建50条']")))
textarea.send_keys("本教程的目标就是以Spring框架为原型,专注于实现Spring的核心功能,编写一个迷你版的Spring框架,我们把它命名为Summer Framework")
time.sleep(2)
# 更新短链接分组
input_field = wait.until(EC.visibility_of_element_located((By.XPATH, "//div[@class='el-input__wrapper']")))
input_field.click()
last_option = wait.until(EC.visibility_of_element_located((By.XPATH, "(//div[@class='el-select-dropdown']//li)[last()]")))
last_option.click()
# 点击确认按钮
confirm_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[@class='el-button el-button--primary buttons']")))
confirm_button.click()
接口自动化测试
使用 pytest + requests + allure 对 ZipLink 短链接平台进行接口测试,最终生成测试报告
1、用户管理:针对用户注册、用户登录、检查用户是否处于登录状态以及用户退出登录操作进行接口自动化测试
import pytest
import requests
import uuid
import random
import allure
# 测试用的用户名和密码
valid_credentials = {
"username": "myth",
"password": "Ch096368"
}
invalid_credentials = {
"username": "invalidUser",
"password": "invalidPassword"
}
missing_username = {
"username": "",
"password": "validPassword"
}
missing_password = {
"username": "validUser",
"password": ""
}
def generate_unique_username():
return f"user_{uuid.uuid4().hex}"
def generate_unique_real_name():
return f"用户_{uuid.uuid4().hex[:6]}"
def generate_unique_phone():
return f"186{random.randint(10000000, 99999999)}"
def generate_unique_email():
return f"{uuid.uuid4().hex[:8]}@example.com"
@pytest.fixture
def get_login_token():
login_url = "http://127.0.0.1:8002/api/short-link/admin/v1/user/login"
response = requests.post(login_url, json=valid_credentials)
assert response.status_code == 200, f"Expected status code 200, but got {response.status_code}"
json_response = response.json()
assert json_response["code"] == "0", f"Expected code '0', but got {json_response['code']}"
assert json_response["message"] is None, "Expected message to be None"
assert json_response["success"] is True, "Expected success to be True"
# 验证 token 是否存在
assert "token" in json_response["data"], "Response data should contain 'token'"
assert isinstance(json_response["data"]["token"], str), "Token should be a string"
return json_response["data"]["token"], valid_credentials["username"]
@allure.feature('User Login')
@allure.title('User Login Test Cases')
@pytest.mark.parametrize("credentials, expected_code, expected_message, expected_success, token_expected", [
(valid_credentials, "0", None, True, True),
(invalid_credentials, "A000001", "用户不存在", False, False),
(missing_username, "A000001", "用户不存在", False, False),
(missing_password, "A000001", "用户不存在", False, False),
])
@allure.description('Test the user login functionality with various credentials.')
def test_short_link_user_login(credentials, expected_code, expected_message, expected_success, token_expected):
login_url = "http://127.0.0.1:8002/api/short-link/admin/v1/user/login"
response = requests.post(login_url, json=credentials)
with allure.step("Verify the response status code"):
assert response.status_code == 200, f"Expected status code 200, but got {response.status_code}"
json_response = response.json()
with allure.step("Verify the response content"):
assert json_response[
"code"] == expected_code, f"Expected code '{expected_code}', but got {json_response['code']}"
assert json_response[
"message"] == expected_message, f"Expected message '{expected_message}', but got {json_response['message']}"
assert json_response[
"success"] == expected_success, f"Expected success '{expected_success}', but got {json_response['success']}"
if token_expected:
with allure.step("Verify the token in response"):
assert "token" in json_response["data"], "Response data should contain 'token'"
assert isinstance(json_response["data"]["token"], str), "Token should be a string"
else:
with allure.step("Verify the response data is None"):
assert json_response["data"] is None, "Expected data to be None"
@allure.feature('User Login Status')
@allure.title('Check User Login Status')
@pytest.mark.parametrize("username, token, expected_data", [
("validUser", "validToken", True),
("validUser", "invalidToken", False), # 测试无效 token ("invalidUser", "validToken", False) # 测试无效用户名
])
@allure.description('Test the check-login functionality with various username and token combinations.')
def test_short_link_check_login(get_login_token, username, token, expected_data):
url = "http://127.0.0.1:8002/api/short-link/admin/v1/user/check-login"
valid_token, valid_username = get_login_token
if username == "validUser":
username = valid_username
if token == "validToken":
token = valid_token
params = {
"username": username,
"token": token
}
response = requests.get(url, params=params)
with allure.step("Verify the response status code"):
assert response.status_code == 200, f"Expected status code 200, but got {response.status_code}"
json_response = response.json()
with allure.step("Verify the response content"):
assert json_response["code"] == "0", f"Expected code '0', but got {json_response['code']}"
assert json_response["message"] is None, "Expected message to be None"
assert json_response["success"] is True, "Expected success to be True"
assert json_response[
"data"] is expected_data, f"Expected data to be {expected_data}, but got {json_response['data']}"
@allure.feature('User Logout')
@allure.title('User Logout Test Cases')
@pytest.mark.parametrize("token_type, expected_code, expected_message, expected_success", [
("validToken", "0", None, True),
("invalidToken", "A000001", "用户Token不存在或用户未登录", False),
("logoutTwice", "A000001", "用户Token不存在或用户未登录", False)
])
@allure.description('Test the user logout functionality with various token scenarios.')
def test_short_link_user_logout(get_login_token, token_type, expected_code, expected_message, expected_success):
url = "http://127.0.0.1:8002/api/short-link/admin/v1/user/logout"
valid_token, username = get_login_token
if token_type == "validToken":
token = valid_token
elif token_type == "invalidToken":
token = "invalidToken"
elif token_type == "logoutTwice":
# 首先正常退出登录
params = {
"username": username,
"token": valid_token
}
logout_response = requests.delete(url, params=params)
assert logout_response.status_code == 200, f"Expected status code 200, but got {logout_response.status_code}"
json_response = logout_response.json()
assert json_response["code"] == "0", f"Expected code '0', but got {json_response['code']}"
# 再次尝试退出,使用相同的 token token = valid_token
params = {
"username": username,
"token": token
}
response = requests.delete(url, params=params)
with allure.step("Verify the response status code"):
assert response.status_code == 200, f"Expected status code 200, but got {response.status_code}"
json_response = response.json()
with allure.step("Verify the response content"):
assert json_response[
"code"] == expected_code, f"Expected code '{expected_code}', but got {json_response['code']}"
assert json_response[
"message"] == expected_message, f"Expected message '{expected_message}', but got {json_response['message']}"
assert json_response[
"success"] == expected_success, f"Expected success '{expected_success}', but got {json_response['success']}"
@allure.feature('User Registration')
@allure.title('User Registration Test Cases')
@pytest.mark.parametrize("new_user_data, expected_code, expected_message, expected_success", [
(lambda: {
"username": generate_unique_username(),
"password": "12345678",
"realName": generate_unique_real_name(),
"phone": generate_unique_phone(),
"mail": generate_unique_email()
}, "0", None, True),
({
"username": "positive12",
"password": "12345678",
"realName": "重复用户",
"phone": "18640654564",
"mail": "existingUser@qq.com"
}, "B000201", "用户名已存在", False)
])
@allure.description('Test the user registration functionality with new and existing user data.')
def test_user_register(new_user_data, expected_code, expected_message, expected_success):
url = "http://127.0.0.1:8002/api/short-link/admin/v1/user"
user_data = new_user_data() if callable(new_user_data) else new_user_data
response = requests.post(url, json=user_data)
with allure.step("Verify the response status code"):
assert response.status_code == 200, f"Expected status code 200, but got {response.status_code}"
json_response = response.json()
with allure.step("Verify the response content"):
assert json_response[
"code"] == expected_code, f"Expected code '{expected_code}', but got {json_response['code']}"
assert json_response[
"message"] == expected_message, f"Expected message '{expected_message}', but got {json_response['message']}"
assert json_response[
"success"] == expected_success, f"Expected success '{expected_success}', but got {json_response['success']}"
2、短链接管理:针对创建短链接、更新短链接以及分页查询短链接操作进行接口自动化测试
import requests
import pytest
import allure
@pytest.fixture(scope="module")
def test_data():
headers = {
"Content-Type": "application/json",
"token": "ea9828ed-a379-4d0f-b987-9f21366b5d0d",
"username": "cuntian"
}
return headers
@allure.feature('Short Link Management')
@allure.story('Create Short Link')
@allure.title('Test Create Short Link')
@allure.description('This test case creates a new short link.')
def test_create_short_link(test_data):
headers = test_data
url = "http://127.0.0.1:8002/api/short-link/admin/v1/create"
data = {
"domainProtocol": "http://",
"domain": "nurl.ink",
"originUrl": "https://nageoffer.com/",
"gid": "dKgtNk",
"createdType": 1,
"validDateType": 1,
"validDate": "",
"describe": "default short url"
}
response = requests.post(url, json=data, headers=headers)
with allure.step("Verify the response status code"):
assert response.status_code == 200, f"Expected status code 200, but got {response.status_code}"
response_data = response.json()
with allure.step("Verify the response content"):
assert response_data.get('success') is True, "Expected success to be True"
assert response_data.get('code') == "0", "Expected code to be '0'"
data = response_data.get('data')
assert data is not None, "Response data should not be None"
assert data.get('gid') == "dKgtNk", "Expected gid to be 'dKgtNk'"
assert data.get('originUrl') == "https://nageoffer.com/", "Expected originUrl to be 'https://nageoffer.com/'"
assert data.get('fullShortUrl') is not None, "Expected fullShortUrl to be present"
allure.attach(response.text, name='Response', attachment_type=allure.attachment_type.JSON)
@allure.feature('Short Link Management')
@allure.story('Update Short Link')
@allure.title('Test Update Short Link')
@allure.description('This test case updates a short link.')
def test_update_short_link(test_data):
url = "http://127.0.0.1:8002/api/short-link/admin/v1/update"
data = {
"fullShortUrl": "http://baidu.com/2z9dv1",
"originUrl": "http://nageoffer.com",
"gid": "dKgtNk",
"validDateType": 1,
"validDate": "2022-01-01 00:00:00",
"describe": "hello"
}
headers = test_data
response = requests.post(url, json=data, headers=headers)
with allure.step("Verify the response status code"):
assert response.status_code == 200, f"Expected status code 200, but got {response.status_code}"
response_data = response.json()
with allure.step("Verify the response content"):
assert response_data.get('success') is True, "Expected success to be True"
assert response_data.get('code') == "0", "Expected code to be '0'"
assert response_data.get('data') is None, "Expected data to be None"
assert response_data.get('message') is None, "Expected message to be None"
assert response_data.get('requestId') is None, "Expected requestId to be None"
allure.attach(response.text, name='Response', attachment_type=allure.attachment_type.JSON)
@allure.feature('Short Link Management')
@allure.story('Page Short Link')
@allure.title('Test Page Short Link')
@allure.description('This test case retrieves a paginated list of short links.')
def test_page_short_link(test_data):
url = "http://127.0.0.1:8002/api/short-link/admin/v1/page"
params = {
"gid": "dKgtNk",
"current": "1",
"size": "10",
"orderTag": "createTime"
}
headers = test_data
response = requests.get(url, params=params, headers=headers)
response_data = response.json()
with allure.step("Verify the response status code"):
assert response.status_code == 200, f"Expected status code 200, but got {response.status_code}"
with allure.step("Verify the response content"):
assert response_data.get('success') is True, "Expected success to be True"
data = response_data["data"]
assert "records" in data, "Response data should contain 'records'"
assert isinstance(data["records"], list), "'records' should be a list"
for record in data["records"]:
assert "id" in record, "Each record should contain 'id'"
assert "shortUri" in record, "Each record should contain 'shortUri'"
assert "fullShortUrl" in record, "Each record should contain 'fullShortUrl'"
assert "originUrl" in record, "Each record should contain 'originUrl'"
allure.attach(response.text, name='Response', attachment_type=allure.attachment_type.JSON)
3、短链接监控:针对获取单个短链接监控、获取分组短链接监控、获取单个短链接监控访问记录以及获取分组短链接监控访问记录操作进行接口自动化测试
import requests
import pytest
import allure
@pytest.fixture(scope="module")
def test_data():
headers = {
"Content-Type": "application/json",
"token": "ea9828ed-a379-4d0f-b987-9f21366b5d0d",
"username": "cuntian"
}
return headers
@allure.feature('Short Link Stats')
@allure.story('Get Short Link Stats')
@allure.title('Test Get Short Link Stats')
@allure.description('This test case retrieves the stats for a specific short link.')
def test_short_link_stats(test_data):
url = "http://127.0.0.1:8002/api/short-link/admin/v1/stats"
params = {
"fullShortUrl": "nurl.ink:8001/TFhdJ",
"gid": "tSUBMP",
"startDate": "2024-2-3",
"endDate": "2024-2-6"
}
headers = test_data
response = requests.get(url, params=params, headers=headers)
response_data = response.json()
with allure.step("Verify the response status code"):
assert response.status_code == 200, f"Expected status code 200, but got {response.status_code}"
with allure.step("Verify the response content"):
assert response_data.get('success') is True, "Expected success to be True"
allure.attach(response.text, name='Response', attachment_type=allure.attachment_type.JSON)
@allure.feature('Short Link Stats')
@allure.story('Get Group Stats')
@allure.title('Test Get Group Stats')
@allure.description('This test case retrieves the stats for a specific group of short links.')
def test_short_link_stats_group(test_data):
url = "http://127.0.0.1:8002/api/short-link/admin/v1/stats/group"
params = {
"gid": "tSUBMP",
"startDate": "2024-2-3",
"endDate": "2024-2-21"
}
headers = test_data
response = requests.get(url, params=params, headers=headers)
response_data = response.json()
with allure.step("Verify the response status code"):
assert response.status_code == 200, f"Expected status code 200, but got {response.status_code}"
with allure.step("Verify the response content"):
assert response_data.get('success') is True, "Expected success to be True"
allure.attach(response.text, name='Response', attachment_type=allure.attachment_type.JSON)
@allure.feature('Short Link Stats')
@allure.story('Get Access Record')
@allure.title('Test Get Access Record')
@allure.description('This test case retrieves the access record for a specific short link.')
def test_short_link_stats_access_record(test_data):
url = "http://127.0.0.1:8002/api/short-link/admin/v1/stats/access-record"
params = {
"fullShortUrl": "nurl.ink:8001/TFhdJ",
"gid": "tSUBMP",
"startDate": "2024-2-3",
"endDate": "2024-2-21",
"current": 1,
"size": 10
}
headers = test_data
response = requests.get(url, params=params, headers=headers)
response_data = response.json()
with allure.step("Verify the response status code"):
assert response.status_code == 200, f"Expected status code 200, but got {response.status_code}"
with allure.step("Verify the response content"):
assert response_data.get('success') is True, "Expected success to be True"
allure.attach(response.text, name='Response', attachment_type=allure.attachment_type.JSON)
@allure.feature('Short Link Stats')
@allure.story('Get Group Access Record')
@allure.title('Test Get Group Access Record')
@allure.description('This test case retrieves the access record for a specific group of short links.')
def test_short_link_stats_access_record_group(test_data):
url = "http://127.0.0.1:8002/api/short-link/admin/v1/stats/access-record/group"
params = {
"gid": "tSUBMP",
"startDate": "2024-2-3",
"endDate": "2024-2-21",
"current": 1,
"size": 10
}
headers = test_data
response = requests.get(url, params=params, headers=headers)
response_data = response.json()
with allure.step("Verify the response status code"):
assert response.status_code == 200, f"Expected status code 200, but got {response.status_code}"
with allure.step("Verify the response content"):
assert response_data.get('success') is True, "Expected success to be True"
allure.attach(response.text, name='Response', attachment_type=allure.attachment_type.JSON)
4、短链接分组管理:针对创建短链接分组、查询短链接分组、更新短链接分组、删除短链接分组以及短链接分组排序操作进行接口自动化测试
import pytest
import requests
import allure
@pytest.fixture(scope="module")
def test_data():
headers = {
"Content-Type": "application/json",
"token": "ea9828ed-a379-4d0f-b987-9f21366b5d0d",
"username": "cuntian"
}
return headers
@allure.feature('Short Link Group Management')
@allure.story('Create Group')
@allure.title('Test Create Short Link Group')
@allure.description('This test case creates a new short link group named "视频网站".')
def test_short_link_group_create(test_data):
url = "http://127.0.0.1:8002/api/short-link/admin/v1/group"
data = {
"name": "视频网站"
}
headers = test_data
response = requests.post(url, json=data, headers=headers)
response_data = response.json()
with allure.step("Verify the response status code"):
assert response.status_code == 200, f"Expected status code 200, but got {response.status_code}"
with allure.step("Verify the response content"):
assert response_data.get('success') is True, "Expected success to be True"
allure.attach(response.text, name='Response', attachment_type=allure.attachment_type.JSON)
@allure.feature('Short Link Group Management')
@allure.story('Search Group')
@allure.title('Test Search Short Link Group')
@allure.description('This test case searches for a short link group named "视频网站".')
def test_short_link_group_search(test_data):
url = "http://127.0.0.1:8002/api/short-link/admin/v1/group"
params = {
"name": "视频网站"
}
headers = test_data
response = requests.get(url, params=params, headers=headers)
response_data = response.json()
with allure.step("Verify the response status code"):
assert response.status_code == 200, f"Expected status code 200, but got {response.status_code}"
with allure.step("Verify the response content"):
assert response_data.get('success') is True, "Expected success to be True"
allure.attach(response.text, name='Response', attachment_type=allure.attachment_type.JSON)
@allure.feature('Short Link Group Management')
@allure.story('Update Group')
@allure.title('Test Update Short Link Group')
@allure.description('This test case updates the name of a short link group with gid "BNstwQ" to "影音网站111".')
def test_short_link_group_update(test_data):
url = "http://127.0.0.1:8002/api/short-link/admin/v1/group"
data = {
"gid": "BNstwQ",
"name": "影音网站111"
}
headers = test_data
response = requests.put(url, json=data, headers=headers)
response_data = response.json()
with allure.step("Verify the response status code"):
assert response.status_code == 200, f"Expected status code 200, but got {response.status_code}"
with allure.step("Verify the response content"):
assert response_data.get('success') is True, "Expected success to be True"
allure.attach(response.text, name='Response', attachment_type=allure.attachment_type.JSON)
@allure.feature('Short Link Group Management')
@allure.story('Delete Group')
@allure.title('Test Delete Short Link Group')
@allure.description('This test case deletes a short link group with gid "we1IvT".')
def test_short_link_group_update_delete(test_data):
url = "http://127.0.0.1:8002/api/short-link/admin/v1/group"
params = {
"gid": "we1IvT"
}
headers = test_data
response = requests.delete(url, params=params, headers=headers)
response_data = response.json()
with allure.step("Verify the response status code"):
assert response.status_code == 200, f"Expected status code 200, but got {response.status_code}"
with allure.step("Verify the response content"):
assert response_data.get('success') is True, "Expected success to be True"
allure.attach(response.text, name='Response', attachment_type=allure.attachment_type.JSON)
@allure.feature('Short Link Group Management')
@allure.story('Sort Group')
@allure.title('Test Sort Short Link Group')
@allure.description('This test case sorts the short link groups.')
def test_short_link_group_update_sort(test_data):
url = "http://127.0.0.1:8002/api/short-link/admin/v1/group/sort"
data = [
{ "gid": "BNstwQ",
"sortOrder": 0
},
{
"gid": "we1lvT",
"sortOrder": 1
}
] headers = test_data
response = requests.post(url, json=data, headers=headers)
response_data = response.json()
with allure.step("Verify the response status code"):
assert response.status_code == 200, f"Expected status code 200, but got {response.status_code}"
with allure.step("Verify the response content"):
assert response_data.get('success') is True, "Expected success to be True"
allure.attach(response.text, name='Response', attachment_type=allure.attachment_type.JSON)
当将所有的 pytest 文件编写完成后,可以使用 pytest-allure 插件来生成测试报告
1、生成 allure 数据
pytest -s -q --alluredir=E:\Project\shortlink_test\result --clean-alluredir
2、查看在线报告
allure serve E:\Project\shortlink_test\result
效果如下:
性能测试
背景与目标
在 ZipLink 闪链短链接平台中,在创建短链接时判断生成的短链接是否存在有两种方式:布隆过滤器和分布式锁
- 分布式锁确实通过锁机制确保并发控制,防止多线程或多个服务实例同时生成相同的短链接。这意味着当一个线程持有锁时,其他线程必须等待锁被释放,因此是串行化的过程
- 布隆过滤器是一种高效的、基于概率的数据结构,能够并行判断某个短链接是否已经存在。布隆过滤器的查询操作非常快速,可以在高并发场景下处理大量请求,因为它不需要锁机制,也不依赖任何顺序性操作
测试目标:验证系统使用布隆过滤器性能要超出使用分布式锁 6 倍以上
请求数据
压测数据来源为测试创建短链接接口功能是使用的 JSON 数据
{
"domain":"myth.com",
"originUrl":"https://ethan_tsui@foxmail.com",
"gid":"siCwZo",
"createdType":1,
"validDateType":0,
"describe":"this is ethan_tsui offical website"
}
使用上面的数据分别请求如下两个接口:
- 接口 1(布隆过滤器):
/api/short-link/v1/create - 接口 2(分布式锁):
/api/short-link/v1/create/by-lock - 硬件环境:在压力测试过程中,需要确保服务器、数据库、网络等资源充足,以避免外部环境对测试结果的影响
测试策略
| 场景编号 | 场景描述 | 并发用户数 | 循环次数 |
|---|---|---|---|
| 1 | 低并发场景,模拟少量用户创建短链接 | 10 | 100 |
| 2 | 中等并发场景,模拟中等用户创建短链接 | 40 | 100 |
| 3 | 高并发场景,模拟中等用户创建短链接 | 100 | 100 |
- 并发用户数:设置不同的并发用户数(10、40、100等),以模拟不同规模的并发场景
- 请求速率:每个并发用户发起的请求速率
- 循环次数:每个用户请求执行的次数,模拟持续高负载的场景
- 数据变动:准备不同的请求参数,模拟真实场景中的短链接生成行为
- 测试执行次数:每个接口分别压测三次
操作步骤
1、准备测试环境
- 确保待测接口可用,并在生产或接近生产环境的条件下运行
- 检查测试服务器的资源(CPU、内存、网络)是否足够,避免资源瓶颈影响测试结果
- 配置数据库和缓存系统,确保与生产环境一致
- 在 JMeter 中准备好测试计划,配置线程组、HTTP 请求采样器、监听器等
2、设计测试计划
- 线程组配置
- 名称:布隆过滤器线程组、分布式锁线程组
- 线程数:根据不同场景设置,10、40、100、500 并发用户
- 循环次数:每个线程循环 100 次
- Ramp-Up 时间:10 秒,模拟用户逐步上线的情况,避免瞬时全量并发
- HTTP 请求采样器
- 创建两个 HTTP 请求采样器,分别指向两个不同接口:
- 接口1:
/api/short-link/v1/create(使用布隆过滤器) - 接口2:
/api/short-link/v1/create/by-lock(使用分布式锁)
- 接口1:
- 设置 POST 请求,添加请求头
Content-Type: application/json,并传递合适的 JSON 请求体参数(模拟真实短链接请求数据)
- 创建两个 HTTP 请求采样器,分别指向两个不同接口:
- 监听器配置
- Aggregate Report:收集吞吐量、响应时间等数据
- View Results in Table:查看详细的请求结果和响应
- Response Time Graph:绘制响应时间图表,评估系统在不同并发条件下的响应时间
- Error% Monitor:监控错误率,检查接口是否有失败请求
3、执行测试,压测脚本如下:
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.5">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<boolProp name="TestPlan.functional_mode">false</boolProp>
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
<stringProp name="TestPlan.comments"></stringProp>
<stringProp name="TestPlan.user_define_classpath"></stringProp>
</TestPlan>
<hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="线程组" enabled="true">
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="循环控制器" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<stringProp name="LoopController.loops">100</stringProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">500</stringProp>
<stringProp name="ThreadGroup.ramp_time">1</stringProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
</ThreadGroup>
<hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="布隆过滤器" enabled="false">
<boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
<elementProp name="HTTPsampler.Arguments" elementType="Arguments">
<collectionProp name="Arguments.arguments">
<elementProp name="" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">{
"domain": "nurl.ink",
"originUrl": "https://nageoffer.com/",
"gid": "dKgtNk",
"createdType": 1,
"validDateType": 1,
"describe": "default short url"
}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain">127.0.0.1</stringProp>
<stringProp name="HTTPSampler.port">8001</stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/api/short-link/v1/create</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">false</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree>
<ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="查看结果树" enabled="true">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>false</xml>
<fieldNames>true</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
<sentBytes>true</sentBytes>
<url>true</url>
<threadCounts>true</threadCounts>
<idleTime>true</idleTime>
<connectTime>true</connectTime>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
<HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP信息头管理器" enabled="true">
<collectionProp name="HeaderManager.headers">
<elementProp name="" elementType="Header">
<stringProp name="Header.name">Content-Type</stringProp>
<stringProp name="Header.value">application/json</stringProp>
</elementProp>
</collectionProp>
</HeaderManager>
<hashTree/>
<BeanShellPostProcessor guiclass="TestBeanGUI" testclass="BeanShellPostProcessor" testname="BeanShell 后置处理程序" enabled="true">
<stringProp name="filename"></stringProp>
<stringProp name="parameters"></stringProp>
<boolProp name="resetInterpreter">false</boolProp>
<stringProp name="script">prev.setDataEncoding("UTF-8");</stringProp>
</BeanShellPostProcessor>
<hashTree/>
<ResultCollector guiclass="StatVisualizer" testclass="ResultCollector" testname="聚合报告" enabled="true">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>false</xml>
<fieldNames>true</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
<sentBytes>true</sentBytes>
<url>true</url>
<threadCounts>true</threadCounts>
<idleTime>true</idleTime>
<connectTime>true</connectTime>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
</hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="分布式锁" enabled="true">
<boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
<elementProp name="HTTPsampler.Arguments" elementType="Arguments">
<collectionProp name="Arguments.arguments">
<elementProp name="" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">{
"domain": "nurl.ink",
"originUrl": "https://nageoffer.com/",
"gid": "dKgtNk",
"createdType": 1,
"validDateType": 1,
"describe": "default short url"
}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain">127.0.0.1</stringProp>
<stringProp name="HTTPSampler.port">8001</stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/api/short-link/v1/create/by-lock</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">false</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree>
<ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="查看结果树" enabled="true">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>false</xml>
<fieldNames>true</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
<sentBytes>true</sentBytes>
<url>true</url>
<threadCounts>true</threadCounts>
<idleTime>true</idleTime>
<connectTime>true</connectTime>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
<HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP信息头管理器" enabled="true">
<collectionProp name="HeaderManager.headers">
<elementProp name="" elementType="Header">
<stringProp name="Header.name">Content-Type</stringProp>
<stringProp name="Header.value">application/json</stringProp>
</elementProp>
</collectionProp>
</HeaderManager>
<hashTree/>
<BeanShellPostProcessor guiclass="TestBeanGUI" testclass="BeanShellPostProcessor" testname="BeanShell 后置处理程序" enabled="true">
<stringProp name="filename"></stringProp>
<stringProp name="parameters"></stringProp>
<boolProp name="resetInterpreter">false</boolProp>
<stringProp name="script">prev.setDataEncoding("UTF-8");</stringProp>
</BeanShellPostProcessor>
<hashTree/>
<ResultCollector guiclass="StatVisualizer" testclass="ResultCollector" testname="聚合报告" enabled="true">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>false</xml>
<fieldNames>true</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
<sentBytes>true</sentBytes>
<url>true</url>
<threadCounts>true</threadCounts>
<idleTime>true</idleTime>
<connectTime>true</connectTime>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
</hashTree>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>
- 场景 1:低并发测试,配置 10 个线程(模拟 10 个并发用户),循环 100 次,观察响应时间和吞吐量
- 场景 2:中等并发测试,配置 40 个线程,循环 100 次,检查在中等负载下系统的表现
- 场景 3:高并发测试,配置 100 个线程,循环 100 次,观察系统在高并发场景下的响应时间变化,关注布隆过滤器和分布式锁的处理情况
压测结果
在场景 1 低并发下,使用 JMeter 压测布隆过滤器和分布式锁的三次性能表现如下:
第一次:
第二次:
第三次:
在场景 2 中等并发下,使用 JMeter 压测分布式锁和布隆过滤器的三次性能表现如下
第一次:
第二次:
第三次:
在场景 3 高并发下,使用 JMeter 压测分布式锁和布隆过滤器的三次性能表现如下
第一次:
第二次:
第三次:
由上图得出结论:在创建短链接判断生成的短链接是否存在时,使用布隆过滤器的性能要远远优于使用分布式锁,并且随着并发量的增大,布隆过滤器的优势更为明显,符合预期。