selenium_ui自动化测试_unittest
unittest 简介
unittest 是Python 内置单元测试框架,无需 pip 安装,是接口、UI 自动化常用框架。
核心作用:批量管理测试用例、执行用例、断言结果、统计成功 / 失败 / 错误数量。
核心
- TestCase(测试用例类)
- 导入:
import unittest - 必须继承
unittest.TestCase,一个类代表一组测试场景。 - 类中只有以
test_开头的方法,才会被识别为测试用例 - 测试方法名建议见名知意
- Fixture 前置后置(4个核心方法)
- Fixture:测试执行前后,自动运行的准备、清理代码(比如打开浏览器、关闭浏览器、进入页面)
Fixture 就是测试的前置准备、后置清理的一套操作,准备测试环境,测试结束后回收环境。
执行顺序:
setUpClass(类开始,只执行 1 次)
→ setUp(每条用例前执行)
→ test_xxx(测试用例)
→ tearDown(每条用例后执行)
→ setUp → test_xxx → tearDown
→ tearDownClass(全部用例跑完,最后执行 1 次)
| 方法 | 装饰器 | 参数 | 作用 |
|---|---|---|---|
| setUpClass | @classmethod | cls | 整个测试类只执行一次,全局初始化(打开浏览器一次) |
| setUp | 无 | self | 每条用例执行前执行,每条用例独立初始化 |
| tearDown | 无 | self | 每条用例结束后执行,清理(关闭浏览器) |
| tearDownClass | @classmethod | cls | 全部用例跑完,执行一次,全局清理 |
两种使用模式
模式 1:setUp 打开浏览器,tearDown 关闭浏览器。每条用例独立浏览器,用例隔离,失败互不影响,速度慢(你当前在用)
模式 2:setUpClass 打开浏览器,tearDownClass 关闭。全程共用浏览器,速度快;但用例互相污染
- 断言assert(判断用例是否通过⭐️)
- 用来判断实际结果和预期是否一致,断言失败 → 用例标记失败
self.assertEqual(实际值,预期值)相等self.assertTrue(表达式)表达式结果为 Trueself.assertIn(内容,对象)判断是否包含
self.assertEqual(a, b) # a == b 相等
self.assertNotEqual(a,b) # a != b
self.assertTrue(表达式) # 表达式为True
self.assertFalse(表达式)
self.assertIn("文本", 字符串) # 包含文本
self.assertNotIn("xxx",str)
UI 自动化场景:查询结果、弹窗提示文字、状态校验,都用断言。
断言失败 → 用例标记 FAILED;代码报错 → 标记 ERROR。
- TestLoader 用例加载器
unittest.TestLoader().discover(start_dir, pattern="test*.py")start_dir:用例文件夹路径pattern:匹配文件,如test*.py、用*.py
- 扫描指定目录、py 文件,自动识别继承
TestCase、以test_开头的测试方法,打包成测试套件TestSuite,交给 TestRunner 去运行。
一句话:负责找用例、收集用例
- discover () 【最常用,批量跑多个文件】
start_dir:起始目录,从哪个文件夹开始搜索用例,可以写相对路径 / 绝对路径pattern:文件匹配规则,支持通配符*pattern="test*.py":匹配testCase.py、test_login.py
top_level_dir:顶层目录,一般不用填,默认 None
返回值:
TestSuite测试套件,里面打包了所有找到的测试用例
- 遍历
start_dir下,符合pattern的.py文件- 在 py 文件中,找到所有继承
unittest.TestCase的类- 在类里面,自动收集以
test_开头的方法作为测试用例- 非
test_开头的方法(封装的业务函数enter_user_management_page)不会当成用例执行
-
loadTestsFromTestCase () 【加载单个测试类】
只加载一个测试类里面所有test_开头的用例 -
loadTestsFromModule () 加载整个模块
-
loadTestsFromName () 按名字加载
可以精准指定:模块。类. 方法,适合执行单条用例
- TestRunner 运行器
- 执行收集好的用例,输出结果
- 原生:
unittest.TextTestRunner()控制台打印结果- 只在控制台打印执行结果,不会生成 html 文件
- verbosity:日志等级
- 0:静默
- 1:简单结果(默认)
- 2:详细输出,每条用例成功 / 失败都打印(自动化调试推荐)
- 第三方:
unittestreport.TestRunner生成 HTML 可视化报告- 执行用例,自动生成 HTML 测试报告
suite:必填,传入 TestSuite 对象(TestLoader 加载出来的用例集合)filename:报告名称,不要后缀;代码内部自动拼接.htmlreport_dir:报告存放文件夹,不存在会自动创建title:HTML 报告页面标题tester:测试人desc:报告描述信息
runner.run():启动执行所有用例,必须调用 run () 才会开始跑用例 + 生成报告from unittestreport import TestRunner import os import unittest if __name__ == '__main__': test_path = os.getcwd() loader = unittest.TestLoader() suite = loader.discover(test_path, pattern="test*.py") runner = TestRunner( suite=suite, # 必填:测试套件 filename="ui_auto_report", # 报告文件名,不需要加 .html report_dir="./", # 报告保存目录 title="后台管理系统UI自动化测试报告", tester="鹿", desc="后台管理系统自动化测试执行结果" ) runner.run()
执行流程
- TestRunner 接收 suite
- 遍历 suite 里所有测试类、测试用例
- 自动执行每个用例对应的 Fixture(setUp → test 方法 → tearDown)
- 捕获每条用例结果:成功 / 失败 / 错误
- success:断言通过
- failure:断言失败
AssertionError - error:代码异常(找不到元素、超时等 Exception)
- 汇总统计,写入 HTML 报告 / 控制台输出
执行顺序规则
- 先执行 Fixture,再执行 test 用例
- 同一个类内,test 用例按照方法名字符串排序执行,不是代码从上往下顺序
例:test_01在test_02前面执行
运行方式
- 直接脚本末尾写
unittest.main(),右键运行 - 命令行运行
# 运行整个文件
python testCase.py
# 运行指定类
python -m unittest testCase.DemoTest
# 运行单个用例
python -m unittest testCase.DemoTest.test_case1
常用装饰器
@unittest.skip("暂时跳过该用例")
def test_xxx(self):
pass
@unittest.skipIf(条件,"条件成立跳过")
def test_xxx(self):
pass
高频坑点(遇到过的)
- ❌
tearDownClass忘记写@classmethod,报参数缺失错误 - ❌ 测试方法不写
test_开头,框架识别不到用例 - ❌ 用例执行顺序:默认按方法名字母排序,不是代码从上到下顺序
- ❌ setUpClass 里面使用 self,必须用 cls
- ❌ 混用隐式等待 + 显式等待,容易出现超时异常
- ❌ 用中文文件名,discover 加载容易出问题
- ❌ 忘记传
suite参数,直接传 TestLoader 对象,会报错 - ❌ filename 写完整
xxx.html,会生成xxx.html.html多余后缀 - ❌ 多次调用
runner.run(),会重复执行两遍所有用例 - ❌ 中文文件名、中文路径,偶尔出现报告生成失败
- ❌ 执行完脚本,浏览器闪退、进程残留:Fixture 的 tearDown/tearDownClass 没有写
driver.quit() - ❌ 找不到用例:TestLoader 的 pattern 匹配不到文件,Runner 拿到空套件,报告为空
- 一定要加断言,没有断言只能叫 “脚本”,不算测试用例
概念区分
- ERROR:代码异常(报错,比如找不到元素、语法错)
- FAILED:代码正常执行,断言不满足(业务结果不符合预期)
实例
import os
import unittest
from time import sleep, strftime
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from unittestreport import TestRunner
# 显式等待 指定等待某个【条件】成立,达到条件立刻继续执行;超过设定最大时间还不满足,抛出 TimeoutException。
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# 常量抽离,方便统一维护
BASE_URL = "http://49.233.201.254:8080/cms/manage/login.do"
class AutoUiTest(unittest.TestCase):
@classmethod
def setUpClass(cls): # 类所有用例执行之前,执行 1 次;必须加装饰器 @classmethod,第一个参数必须是 cls,不能写 self
cls.opt = Options()
cls.opt.add_experimental_option('detach', True)
cls.wait = None
@classmethod
def tearDownClass(cls): # 在整个测试类【所有测试用例全部执行完成之后,只执行 1 次】;必须加装饰器 @classmethod,第一个参数必须是 cls,不能写 self
print('全部用例执行完毕')
def setUp(self): # 每条用例执行前都会跑
self.driver = webdriver.Chrome(options=self.opt)
self.wait = WebDriverWait(self.driver, 10) # # 参数1:driver浏览器对象;参数2:最长等待秒数
# self.driver.implicitly_wait(4) # 显式等待和隐式等待混用,会带来超时不可预期,直接删除。
self.driver.get(BASE_URL)
# 登录
# 简单静态页面,只需要查找元素,不需要判断可见、可点击的时候。
# UI 自动化项目,工业上主流做法:废弃隐式等待,全部使用显式等待,稳定性更好。
self.wait.until(EC.element_to_be_clickable((By.ID,'userAccount'))).send_keys('admin')
self.wait.until(EC.element_to_be_clickable((By.ID, 'loginPwd'))).send_keys('123456')
self.wait.until(EC.element_to_be_clickable((By.ID, 'loginBtn'))).click()
def tearDown(self): # 每条用例执行完都会跑
sleep(5)
self.driver.quit()
# ==========封装公共方法==========
def enter_user_management_page(self):
''' 进入用户管理页面,封装此重复操作 '''
self.wait.until(EC.element_to_be_clickable((By.XPATH, '//*[@id="menu-user"]/dt'))).click()
self.wait.until(EC.element_to_be_clickable((By.XPATH,'//*[@id="menu-user"]/dd/ul/li[1]/a'))).click()
# 切换iframe
iframe_in = self.wait.until(EC.presence_of_element_located((By.XPATH,'//*[@id="iframe_box"]/div[2]/iframe')))
self.driver.switch_to.frame(iframe_in)
def back_to_main_page(self):
"""退出iframe,切回主页面"""
self.driver.switch_to.default_content()
# ==========测试用例==========
def test_uc_user_management(self):
''' 打开 用户中心 - 用户管理页面 '''
self.enter_user_management_page()
# 断言:校验搜索按钮存在,证明页面打开成功
self.assertTrue(self.wait.until(EC.presence_of_element_located((By.ID,'searchBtn'))))
self.back_to_main_page()
def test_uc_user_management_search(self):
''' 用户管理页面 查询功能 时间范围 '''
self.enter_user_management_page()
self.wait.until(EC.element_to_be_clickable((By.ID,'datemin'))).send_keys('2026-09-12')
self.wait.until(EC.element_to_be_clickable((By.ID, 'datemax'))).send_keys('2026-09-20')
self.wait.until(EC.element_to_be_clickable((By.ID, 'searchBtn'))).click()
# 断言:判断表格加载完成
self.wait.until(EC.presence_of_element_located((By.ID, 'sysUserTab')))
self.back_to_main_page()
def test_uc_user_management_search_phone(self):
''' 用户管理页面 查询手机号为13245611111的用户 '''
self.enter_user_management_page()
self.wait.until(EC.element_to_be_clickable((By.ID,'searchValue'))).send_keys('13245611111')
self.wait.until(EC.element_to_be_clickable((By.ID, 'searchBtn'))).click()
# 断言:表格包含手机号
self.assertTrue(self.wait.until(EC.presence_of_element_located(
(By.XPATH, '//*[@id="sysUserTab"]//tr[contains(., "13245611111")]')
)))
self.back_to_main_page()
def test_uc_user_management_search_email(self):
''' 用户管理页面 - 根据邮箱 autotest@163.com 查询用户 '''
self.enter_user_management_page()
self.wait.until(EC.element_to_be_clickable((By.ID, 'searchValue'))).send_keys('autotest@163.com')
self.wait.until(EC.element_to_be_clickable((By.ID, 'searchBtn'))).click()
# 断言
self.assertTrue(self.wait.until(EC.presence_of_element_located(
(By.XPATH, '//*[@id="sysUserTab"]//tr[contains(., "autotest@163.com")]')
)))
self.back_to_main_page()
def test_uc_user_management_search_username(self):
''' 用户管理页面 - 根据用户名 zzz 查询用户 '''
self.enter_user_management_page()
self.wait.until(EC.element_to_be_clickable((By.ID, 'searchValue'))).send_keys('zzz') # 模糊查询
self.wait.until(EC.element_to_be_clickable((By.ID, 'searchBtn'))).click()
self.back_to_main_page()
def test_uc_user_management_search_stop(self):
''' 用户管理页面 - 停用|启用用户 SBB5886 '''
self.enter_user_management_page()
# 查询
self.wait.until(EC.element_to_be_clickable((By.ID,'searchValue'))).send_keys('SBB5886')
self.wait.until(EC.element_to_be_clickable((By.ID, 'searchBtn'))).click()
# 添加步骤 等待表格加载出来,才能点击停用按钮 - 等"包含 SBB5886 的那一行"出现,再把按钮搜索范围限定在该行
self.wait.until(EC.presence_of_element_located((By.XPATH,'//*[@id="sysUserTab"]//tr[contains(., "SBB5886")]/td')))
# get_property(一般用来获取DOM原生属性,title两种都可以)
before_status = self.wait.until(EC.presence_of_element_located((By.XPATH,'//*[@id="sysUserTab"]/tr[1]/td[9]/a[1]')))
before_title = before_status.get_property("title")
status_btn = self.wait.until(EC.element_to_be_clickable((By.XPATH,'//*[@id="sysUserTab"]/tr[1]/td[9]/a[1]')))
status_btn.click()
self.wait.until(EC.presence_of_element_located((By.CLASS_NAME,'xubox_layer')))
self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME,'xubox_yes'))).click()
# 新增第一步:等待弹窗消失!!
self.wait.until(EC.invisibility_of_element_located((By.CLASS_NAME, 'xubox_layer')))
# 第二步:等待旧按钮元素失效
self.wait.until(EC.staleness_of(before_status))
# 断言 判断前后两个状态不同
after_status = self.wait.until(
EC.presence_of_element_located((By.XPATH, '//*[@id="sysUserTab"]//tr[contains(.,"SBB5886")]/td[9]/a[1]')))
after_title = after_status.get_property("title")
self.assertTrue(before_title != after_title)
self.back_to_main_page()
'''
遇到陈旧元素引用异常的情况 - 找到了元素,拿到了元素对象;但是页面 DOM 刷新 / 表格重载,旧元素已经消失了,再调用 .click() 就报错。
常见触发场景:查询后表格异步渲染、翻页、筛选,table 的 tr 重新生成。
`self.wait.until(EC.element_to_be_clickable(...)).click()` 看似是"查找+点击"连在一起,但实际上是 两步 :
1. `until(...)` 内部反复查找,直到条件满足, 返回那个元素对象
2. `.click()` 在返回的元素对象上调用点击
这两步之间只要有 几毫秒 的间隔,如果此时表格正在异步刷新(搜索后表格 tr 重新生成),第 1 步拿到的元素对象就变成了"尸体",第 2 步`.click()` 就报 stale。
'''
if __name__ == '__main__':
# unittest.main() # unittest.main() 一旦执行,就会直接运行所有测试用例,运行完成之后,直接退出程序!
# 直接加载当前类里面的用例,不要用discover(避免文件匹配问题)
# test_suite = unittest.TestSuite()
# test_suite.addTests(unittest.TestLoader().loadTestsFromTestCase(AutoUiTest)) # loadTestsFromTestCase(AutoUiTest) 自动获取这个类下面所有以 `test_` 开头的测试方法
# test_suite.addTest(AutoUiTest('test_uc_user_management_search_stop')) # 测试单个用例
# unittest.TextTestRunner(verbosity=2).run(test_suite) # 跑测试用例
test_path = os.getcwd()
print(test_path)
test_loader = unittest.TestLoader().discover(start_dir=test_path,pattern='test*.py') # 文件名为中文时,会读取失败,discover找不到脚本
# unittest.TextTestRunner().run(test_loader)
test_time = strftime('%y-%m-%d_%H-%M-%S')
test_report = test_path + '/' + str(test_time) + ' ui_auto.html'
# test_write = open(test_report, 'wb')
TestRunner(
suite = test_loader,
report_dir = test_path,
filename = f'{str(test_time)} ui_auto',
title = '后台管理系统测试报告',
tester = '鹿',
desc = '后台管理系统自动化测试报告',
).run()
讲解流程
- 导入相关模块:
- unittest模块 import unittest;
- selenium模块 from selenium import webdriver
- 时间模块 from time import *
- os模块 import os
- unittestreport模块 from unittestreport import TestRunner
- 等
- 创建一个类,继承 unittest.TestCase
- 编写fixture 前置后置(打开关闭浏览器)
- 编写test_开头的测试用例,封装公共方法,断言
- 入口:TestLoader.discover() 搜集全部用例,生成TestSuite
- TsetRunner.run() 执行套件,生成HTML报告
- 查看报告,分析 error/failure

浙公网安备 33010602011771号