功能测试
后台端登录
登录页面30条用例
添加购物车功能
接口测试
接口测试
编写测试用例:id,名称,所属模块,优先级,前置条件,准备数据,执行步骤,预期结果,实际结果
id,模块,优先级,用例名称,接口名称,请求url, 请求类型,请求头,请求参数类型,请求参数,预期结果,测试结果
分类接口测试用例:
ID | 模块 | 用例名称 | 接口名称 | URL | 请求方法 | 请求头 | 请求参数类型 | 请求参数 | 预期结果 | 测试结果 |
---|---|---|---|---|---|---|---|---|---|---|
1 | 分类模块 | 成功新增早餐分类 | 新增分类接口 | /category | POST | Content-Type: application/json | JSON | { "name": "面包", "type": "1", "sort": 1 } | {"code": 1, "msg": "新增分类成功", "data": null} | 通过 |
2 | 分类模块 | 成功新增套餐分类 | 新增分类接口 | /category | POST | Content-Type: application/json | JSON | { "name": "套餐A", "type": "2", "sort": 2 } | {"code": 1, "msg": "新增分类成功", "data": null} | 通过 |
3 | 分类模块 | 分类名称为空 | 新增分类接口 | /category | POST | Content-Type: application/json | JSON | { "name": "", "type": "1", "sort": 3 } | {"code": 0, "msg": "请输入分类名称或排序"} | 通过 |
4 | 分类模块 | 分类类型缺失 | 新增分类接口 | /category | POST | Content-Type: application/json | JSON | { "name": "粥", "sort": 4 } | {"code": 0, "msg": "分类类型不能为空"} | 通过 |
5 | 分类模块 | 排序字段为空 | 新增分类接口 | /category | POST | Content-Type: application/json | JSON | { "name": "牛奶", "type": "1", "sort": "" } | {"code": 0, "msg": "排序值不能为空"} | 通过 |
6 | 分类模块 | 排序字段为非数字字符 | 新增分类接口 | /category | POST | Content-Type: application/json | JSON | { "name": "果汁", "type": "1", "sort": "abc" } | {"code": 0, "msg": "排序只能输入数字类型"} | 通过 |
7 | 分类模块 | 重复的分类名称和类型 | 新增分类接口 | /category | POST | Content-Type: application/json | JSON | { "name": "面包", "type": "1", "sort": 5 } | {"code": 0, "msg": "分类名称已存在"} | 通过 |
8 | 分类模块 | 分类类型无效 | 新增分类接口 | /category | POST | Content-Type: application/json | JSON | { "name": "水果", "type": "3", "sort": 6 } | {"code": 0, "msg": "无效的分类类型"} | 通过 |
登录接口测试用例
ID | 模块 | 用例名称 | 接口名称 | URL | 请求方法 | 请求头 | 请求参数类型 | 请求参数 | 预期结果 | 测试结论 |
---|---|---|---|---|---|---|---|---|---|---|
1 | 登录模块 | 正常登录 | login | /employee/login | POST | Content-Type: application/json | JSON | {"username": "admin", "password": "123456"} | 登录成功 | 通过 |
2 | 登录模块 | 密码错误 | login | /employee/login | POST | Content-Type: application/json | JSON | {"username": "admin", "password": "wrongpassword"} | 登录失败 | 通过 |
3 | 登录模块 | 用户不存在 | login | /employee/login | POST | Content-Type: application/json | JSON | {"username": "notexist", "password": "123456"} | 登录失败 | 通过 |
4 | 登录模块 | 账号已禁用 | login | /employee/login | POST | Content-Type: application/json | JSON | {"username": "disabledUser", "password": "123456"} | 账号已禁用 | 通过 |
添加早餐接口测试用例
ID | 模块 | 优先级 | 用例名称 | 接口名称 | 请求URL | 请求类型 | 请求头 | 请求参数类型 | 请求参数 | 预期结果 | 测试结果 |
---|---|---|---|---|---|---|---|---|---|---|---|
TC01 | 早餐管理 | 高 | 添加早餐成功 | 添加早餐 | /dish | POST | Content-Type: application/json | JSON | {"name": "鸡蛋饼", "price": 15.5, "categoryId": 3, "description": "美味的鸡蛋饼", "status": 1, "flavors": [{"name": "甜", "value": "加糖"}, {"name": "辣", "value": "加辣椒"}]} | 响应状态码为200,返回成功消息 "新增菜品成功" | 通过 |
TC02 | 早餐管理 | 高 | 添加早餐 - 缺少必填字段 | 添加早餐 | /dish | POST | Content-Type: application/json | JSON | {"price": 15.5, "categoryId": 3, "description": "美味的鸡蛋饼", "status": 1} | 响应状态码为400,返回错误消息 "请求参数错误" | 通过 |
TC03 | 早餐管理 | 中 | 添加早餐 - 价格为负数 | 添加早餐 | /dish | POST | Content-Type: application/json | JSON | {"name": "鸡蛋饼", "price": -5, "categoryId": 3, "description": "美味的鸡蛋饼", "status": 1} | 响应状态码为400,返回错误消息 "请求参数错误" | 通过 |
TC04 | 早餐管理 | 低 | 添加早餐 - 状态为无效值 | 添加早餐 | /dish | POST | Content-Type: application/json | JSON | {"name": "鸡蛋饼", "price": 15.5, "categoryId": 3, "description": "美味的鸡蛋饼", "status": 2} | 响应状态码为400,返回错误消息 "请求参数错误" | 通过 |
TC05 | 早餐管理 | 中 | 添加早餐 - 口味信息为空 | 添加早餐 | /dish | POST | Content-Type: application/json | JSON | {"name": "鸡蛋饼", "price": 15.5, "categoryId": 3, "description": "美味的鸡蛋饼", "status": 1, "flavors": []} | 响应状态码为200,返回成功消息 "新增菜品成功" | 通过 |
TC06 | 早餐管理 | 中 | 添加早餐 - 口味名称缺失 | 添加早餐 | /dish | POST | Content-Type: application/json | JSON | {"name": "鸡蛋饼", "price": 15.5, "categoryId": 3, "description": "美味的鸡蛋饼", "status": 1, "flavors": [{"value": "加糖"}]} | 响应状态码为400,返回错误消息 "请求参数错误" | 通过 |
TC07 | 早餐管理 | 中 | 添加早餐 - 大批量数据测试 | 添加早餐 | /dish | POST | Content-Type: application/json | JSON | 大量菜品数据(例如 1000 条记录) | 响应状态码为200,所有菜品正确添加 | 通过 |
TC08 | 早餐管理 | 低 | 添加早餐 - 重复菜品名称 | 添加早餐 | /dish | POST | Content-Type: application/json | JSON | {"name": "鸡蛋饼", "price": 15.5, "categoryId": 3, "description": "重复的鸡蛋饼", "status": 1} | 响应状态码为200,返回成功消息 "新增菜品成功" | 通过 |
TC09 | 早餐管理 | 高 | 添加早餐 - 特殊字符输入 | 添加早餐 | /dish | POST | Content-Type: application/json | JSON | {"name": "<鸡蛋饼>", "price": 15.5, "categoryId": 3, "description": "包含特殊字符的描述!@#$%^&*()", "status": 1} | 响应状态码为200,返回成功消息 "新增菜品成功" | 通过 |
TC10 | 早餐管理 | 中 | 添加早餐 - 长字段测试 | 添加早餐 | /dish | POST | Content-Type: application/json | JSON | {"name": "鸡蛋饼", "price": 15.5, "categoryId": 3, "description": "非常长的描述" * 1000, "status": 1} | 响应状态码为200,返回成功消息 "新增菜品成功" | 通过 |
这个表格涵盖了各种测试场景,包括正常情况和异常情况,确保接口的稳定性和可靠性。
接口设计用例
1.基本正向用例(查看接口,证明接口能够跑通)
2.带可选参数的扩展正向用例(必填跑通后试一下可选参数)
3.反向用例(错误的账号密码,有效无效)
4.破坏性的测试(sql注入,数据包大会不会崩溃)
执行用例
postman执行测试
ui自动化测试
登录:
pom代码
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
class LoginPage:
def __init__(self, driver: WebDriver):
self.driver = driver
self.username_input = (By.CSS_SELECTOR, 'input[placeholder="账号"]')
self.password_input = (By.CSS_SELECTOR, 'input[placeholder="密码"]')
self.login_button = (By.XPATH, '//span[text()="登录"]')
def enter_username(self, username):
self.driver.find_element(*self.username_input).send_keys(username)
def enter_password(self, password):
self.driver.find_element(*self.password_input).send_keys(password)
def click_login(self):
self.driver.find_element(*self.login_button).click()
def wait_for_element_visible(self, by_locator, timeout=10):
"""显式等待,直到元素可见"""
WebDriverWait(self.driver, timeout).until(
EC.visibility_of_element_located(by_locator)
)
def wait_for_element_clickable(self, by_locator, timeout=10):
"""显式等待,直到元素可点击"""
WebDriverWait(self.driver, timeout).until(
EC.element_to_be_clickable(by_locator)
)
def implicit_wait(self, timeout=5):
"""隐式等待"""
self.driver.implicitly_wait(timeout)
def force_wait(self, seconds):
"""强制等待"""
time.sleep(seconds)
#全屏
def full_screen(self):
self.driver.maximize_window()
test:
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
class LoginPage:
def __init__(self, driver: WebDriver):
self.driver = driver
self.username_input = (By.CSS_SELECTOR, 'input[placeholder="账号"]')
self.password_input = (By.CSS_SELECTOR, 'input[placeholder="密码"]')
self.login_button = (By.XPATH, '//span[text()="登录"]')
def enter_username(self, username):
self.driver.find_element(*self.username_input).send_keys(username)
def enter_password(self, password):
self.driver.find_element(*self.password_input).send_keys(password)
def click_login(self):
self.driver.find_element(*self.login_button).click()
def wait_for_element_visible(self, by_locator, timeout=10):
"""显式等待,直到元素可见"""
WebDriverWait(self.driver, timeout).until(
EC.visibility_of_element_located(by_locator)
)
def wait_for_element_clickable(self, by_locator, timeout=10):
"""显式等待,直到元素可点击"""
WebDriverWait(self.driver, timeout).until(
EC.element_to_be_clickable(by_locator)
)
def implicit_wait(self, timeout=5):
"""隐式等待"""
self.driver.implicitly_wait(timeout)
def force_wait(self, seconds):
"""强制等待"""
time.sleep(seconds)
#全屏
def full_screen(self):
self.driver.maximize_window()
添加早餐分类并将测试环境中的早餐数据删除
pytest
from time import sleep
import pytest
import yaml
from selenium import webdriver
from pythontest.smallmi.pages.login_page2 import LoginPage
from pythontest.smallmi.pages.breakfast_category_page import CategoryPage
@pytest.fixture(scope="module")
def driver():
driver = webdriver.Chrome()
driver.implicitly_wait(10)
yield driver
driver.quit()
def test_login(driver):
with open('E:/breakfast/samllsamllmibase/mi/pythontest/smallmi/data/test_data.yaml',encoding='utf-8') as f:
data = yaml.safe_load(f)
login_page = LoginPage(driver)
category_page = CategoryPage(driver)
category_page.full_screen()
driver.get(data['login']['url'])
login_page.click_login_button()
assert login_page.assert_login_success()
category_page.click_category()
print(driver.current_window_handle)
category_page.switch_to_iframe()
print(driver.current_window_handle)
category_page.click_add_button()
category_page.enter_category_name(data['category']['name'])
category_page.enter_sort_order(data['category']['sort_order'])
category_page.click_confirm()
# 验证添加是否成功
assert category_page.is_category_present(data['category']['name'])
category_page = CategoryPage(driver)
category_page.full_screen()
assert category_page.is_category_present(data['category']['name'])
sleep(5)
category_name = data['category']['name']
try:
# 尝试删除类别
category_page.delete_category(category_name)
sleep(5)
# 验证类别是否仍然存在
assert not category_page.is_category_present(category_name)
print(f"Category '{category_name}' successfully deleted.")
except Exception as e:
print(f"Failed to delete category '{category_name}': {e}")
# # 打印页面源代码片段,帮助调试
# print("Page source snippet after failure:")
# print(driver.page_source[:1000])
# 关闭浏览器
driver.quit()
from time import sleep
from selenium.webdriver.support import expected_conditions as EC
from selenium.common import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
class CategoryPage:
def __init__(self, driver):
self.driver = driver
def click_category(self):
self.driver.find_element(By.XPATH, '//*[@id="app"]/div/div[1]/div[2]/div[1]/div/ul/div[2]/li/span').click()
def switch_to_iframe(self):
try:
WebDriverWait(self.driver, 30).until(
EC.frame_to_be_available_and_switch_to_it((By.ID,"cIframe"))
)
except TimeoutException:
print("Timeout while waiting for iframe 'cIframe'.")
self.driver.save_screenshot('iframe_wait_timeout.png')
raise
def click_add_button(self):
self.driver.find_element(By.XPATH, '//*[@id="category-app"]/div[1]/div[1]/button[1]/span').click()
def enter_category_name(self, name):
self.driver.find_element(By.XPATH, "//input[@placeholder='请输入分类名称']").send_keys(name)
def enter_sort_order(self, order):
self.driver.find_element(By.XPATH, "//input[@placeholder='请输入排序']").send_keys(order)
def click_confirm(self):
self.driver.find_element(By.XPATH, "//span[text()='确 定']").click()
def is_category_present(self, category_name):
elements = self.driver.find_elements(By.XPATH, f"//div[text()='{category_name}']")
print(f"Found {len(elements)} elements with the name '{category_name}'") # 调试信息
return len(elements) > 0
#全屏
def full_screen(self):
self.driver.maximize_window()
def delete_category(self, category_name):
try:
# 定位“删除”按钮并点击
delete_button_xpath = f"//tr[td/div[normalize-space(text())='{category_name}']]//button[span[normalize-space(text())='删除']]"
delete_button = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.XPATH, delete_button_xpath))
)
delete_button.click()
sleep(3)
# 等待确认对话框的出现
confirm_button = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable(
(By.XPATH, "/html/body/div[2]/div/div[3]/button[2]/span"))
)
confirm_button.click()
print(f"Successfully deleted category {category_name}.")
except Exception as e:
print(f"Failed to delete category {category_name}: {e}")
yaml
login: url: 'http://localhost:8080/backend/page/login/login.html' category: name: '测试三分类' sort_order: '1'