# 基于unittest+selenium的UI自动化测试框架 **Repository Path**: testcodes/web-ui ## Basic Information - **Project Name**: 基于unittest+selenium的UI自动化测试框架 - **Description**: 采用Unittest + Selenium + unittestreport + Typer搭建的UI自动化测试框架: 项目结构清晰,便于统一管理; 用例支持失败重试,多线程并发执行; 可生成3种不同风格的测试报告;并支持将报告及结果发送钉钉和企业微信; 加入Typer命令行应用,简单化用例执行,模型迁移等操作; - **Primary Language**: Python - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 1 - **Created**: 2021-12-28 - **Last Updated**: 2025-09-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: UI自动化, Selenium, unittest, unittestreport, 自动化测试 ## README # UI自动化测试项目 ## 介绍 - 采用Unittest + Selenium + unittestreport + Typer搭建的自动化测试框架: - 项目结构清晰,便于统一管理; - 用例支持失败重试,多线程并发执行; - 可生成3种不同风格的测试报告;并支持将报告及结果发送钉钉和企业微信; - 加入Typer命令行应用,简单化用例执行,模型迁移等操作; - Unittest是Python标准库中自带的测试框架,它遵循了xUnit风格,即将测试组织成类和方法: - 兼容性好:Unittest不需要安装任何额外的包,可以在任何Python环境中使用。Unittest也可以与其他测试框架协作,如nose或doctest; - 结构化清晰:Unittest将测试分为三个阶段:setUp,test和tearDown。这样可以方便地设置和清理测试环境,以及管理测试依赖; - 丰富的断言方法:Unittest提供了很多专门的断言方法,可以覆盖各种情况,如检查相等性,包含性,异常抛出等; - Selenium 是支持 web 浏览器自动化的一系列工具和库的综合项目。它提供了扩展来模拟用户与浏览器的交互,用于扩展浏览器分配的分发服务器, 以及用于实现W3C WebDriver规范的基础结构, 该规范允许您为所有主要 Web 浏览器编写可互换的代码; - 测试可以使用以下任何一种语言编写:Java、Python、C#、PHP、Ruby 和 JavaScript; - 测试可以在以下任何操作系统中进行:Windows、Mac 或 Linux; - 可以使用任何浏览器进行测试:Mozilla、Firefox、IE、Chrome、Safari或Opera; - 开源且可移植; - 易于识别和使用网络元素; - unittestreport是柠檬班-木森老师基于unittest开发的的一个功能扩展库: - 支持3种风格HTML测试报告生成; - 支持测试用例失败重运行; - 多线程并发执行用例; - 支持发送测试结果及报告到邮箱; - 支持测试结果推送到钉钉及企业微信; unittest 官方文档: https://docs.python.org/zh-cn/3/library/unittest.html selenium 官方文档:https://www.selenium.dev/zh-cn/documentation/ unittestreport 官方文档:https://unittestreport.readthedocs.io/en/latest/ Typer 官方文档:https://typer.tiangolo.com/ ## 项目结构 - application:主项目配置目录,也存放了主路由文件 - env:基础环境配置文件 - prod.py:生产环境 - testing.py:测试环境 - routers.py:主路由文件,统一管理 - settings.py:主项目配置文件 - core:核心文件目录 - database.py:数据库操作配置 - driver.py:浏览器驱动 - page.py:页面基本操作 - unit_case.py:用例执行预置操作 - script: 脚本存放目录 - create_case:按模型生成测试用例 - create_pages:按模型生成测试页面对象服务 - utils:封装的一些工具类目录 - website:项目的测试内容存放目录 - datas:测试数据存放目录 - pages:测试页面对象服务 - menu:功能-菜单 - locators:页面定位元素 - operates:页面操作步骤 - test_cases:测试用例集 - menu:功能-菜单 目录 - test_case_name:测试用例执行文件 - logs:日志目录 - report:测试报告存放目录 - main.py:主程序入口文件 ## 开发环境 开发语言:Python 3.10 开发框架:unittest + selenium + unitestreport + Typer ## 开发工具 Pycharm 2024.3.6 推荐插件:Chinese (Simplified) Language Pack / 中文语言包 ## 使用 ``` # 安装依赖库 pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ # 第三方源: 1. 阿里源: https://mirrors.aliyun.com/pypi/simple/ 2. 清华大学: https://pypi.tuna.tsinghua.edu.cn/simple/ 3. 中国科技大学: https://mirrors.bfsu.edu.cn/pypi/web/simple/ 4. 豆瓣: https://mirrors.cloud.tencent.com/pypi/simple/ ``` ### 初始化模型 ```shell # 项目根目录下执行,路径已存在时需要先删除 # 会自动将模型迁移到路径下,并按模型生成对应的.py文件 # 生成页面对象服务 # python main.py init-pages 功能/菜单 路径 python main.py init-pages user/list # 生成测试用例 # python main.py init-case 功能/菜单 路径 python main.py init-case user/list ``` ### 执行测试用例集 ```shell # 直接运行main文件run-test命令 python main.py run-test ``` ## 其他操作 用例规范 - 用例文件需存放于testCase目录下,且以test开头(示例:test_login.py); - 用例必须要写在类中,并且类要继承UnittestCase,类中用例名称要以test_开头; 用例默认执行顺序 - 同一层级的文件名通过ASC码排序,先找到的文件,先收集里面的用例; - 不同层级的文件:从外到内,即先把外层目录下的收集完成后,在找文件夹里面的; - 文件当中的用例执行顺序:从上到下,按照代码顺序; - 需要跳过用例不执行时,在用例方法中添加装饰器@unittest.skip即可; - 想要按照自定义顺序执行时: ``` 文件顺序:在test*.py中加入序号,如:test_01login.py,test_02register.py 用例顺序:在方法名称中加入序号,如:def test_01normal_login, def test_02empty_name_login ``` ## 代码示例: - 定义页面定位元素 ```python # locators.py from selenium.webdriver.common.by import By # 通过By方法获取定位信息 class PageLocators: """页面定位元素集""" username = (By.ID, 'Username') # 用户名,通过id定位 password = (By.NAME, 'Password') # 密码,通过name定位 login_btn = (By.XPATH, '/form/div[3]/div/button') # 登录按钮,通过xpath定位 success_mark = (By.CSS_SELECTOR, '#app > div > div.app-main > div') # 登录成功标识,通过CSS定位 fail_hint = (By.XPATH, '/html/body/div/div/div[1]/form/div[3]/div/div/ul/li') # 登录失败提示内容 ``` - 定义页面操作步骤 ```python # operates.py from application.routers import Login from core.base_page import BasePage from website.pages.login.locators import PageLocators class PageOperate(BasePage): """页面操作""" loc = PageLocators() def input_username(self, username): """ 输入登录用户名 :param username: 用户名 """ # 先清空, 组件原因直接clear不生效,使用按键清空 self.backspace_clear(self.loc.loc_username) # 再输入 self.input_value(self.loc.loc_username, username) def input_password(self, password): """ 输入登录密码 :param password: 用户密码 """ # 先清空 self.backspace_clear(self.loc.loc_password) # 再输入 self.input_value(self.loc.loc_password, password) def click_login_button(self): """点击登录按钮""" self.click_element(self.loc.loc_login_btn) def login_opt(self, username, password): """ 登录操作 :param username: 用户名 :param password: 登录密码 """ # 1.输入用户名 self.input_username(username) # 2.输入密码 self.input_password(password) # 3.点击登录按钮 self.click_login_button() def get_success_mark(self): """获取登录成功标识""" try: return self.get_element_text(self.loc.loc_success_mark) except ValueError: return '' def get_fail_mark(self): """获取登录失败标识""" # 检测页面仍停留在登录页面则表示登录失败 try: self.wait_element_visible(self.loc.loc_fail_hint) return self.get_element_text(self.loc.loc_fail_hint) except ValueError: return '' def user_logout(self): """退出登录""" # 点击个人中心选项 self.click_element(self.loc.loc_user_avatar) # 移动到退出登录选项上方并点击 self.move_and_click_element(self.loc.loc_logout) # 弹窗点击确认退出 self.click_element(self.loc.loc_logout_confirm) ``` - 编写测试用例 ```python # test_login.py from ddt import ddt, data # 引入数据驱动测试 from selenium.common import NoSuchElementException from application.routers import Login from core.conftest import * # 引入预置操作 from website.pages.login import operates # 引入页面操作步骤 from website.datas.test_data import LoginData # 引入登录页面驱动测试数据 from utils.file_reader import * @ddt # 几种不同方式的数据驱动测试 class TestLogin(MyUnit): """登录测试""" def setUp(self): # 初始化页面操作 self.login_page = operates.PageOperate(self.driver) # 打开登录页面 self.login_page.open_page(Login.login) # TODO:用例执行前需要进行的操作 logger.info(f"开始执行登录用例: {self._testMethodName}") def tearDown(self): # TODO:用例执行完成后需要进行的操作 # 用例执行完成后检查是否登录,登录了则先退出登录 try: self.driver.find_element(*self.login_page.loc.loc_success_mark) logger.info("当前账户已登录,执行退出登录操作!") self.login_page.user_logout() logger.info("当前账户已退出!") except NoSuchElementException: pass logger.info(f"登录用例执行完成: {self._testMethodName}") # 非数据驱动模型 # @unittest.skip('skip this case') def test_login_normal(self): """输入正确的用户名密码可正常登录""" username = '15020221010' password = 'kinit2022' expect = 'kinit' # 输入用户名 self.login_page.input_username(username) # 输入密码 self.login_page.input_password(password) # 点击登录 self.login_page.click_login_button() # 断言 mark = self.login_page.get_success_mark() # 获取登录成功标识 self.assertEqual(expect, mark) # 判断结果是否等于预期 # py数据驱动测试 @data(*LoginData.empty_data) # @unittest.skip('skip this case') def test_login_empty_by_py(self, datas): """用户名或密码为空不能正常登录-数据驱动[ py数据 ]""" # 执行登录操作 self.login_page.login_opt(datas['user'], datas['pwd']) # 断言, 获取登录失败提示信息 if not datas['user']: mark = self.login_page.get_name_empty_text() else: mark = self.login_page.get_pwd_empty_text() self.assertEqual(datas['expect'], mark) # 判断结果是否等于预期 # yaml数据驱动测试 @data(*read_yaml(data_file('test_data.yaml'), "error_data")) # @unittest.skip('skip this case') def test_login_error_by_yaml(self, datas): """用户名或密码不正确不能正常登录-数据驱动[ yaml数据 ]""" # 执行登录操作 self.login_page.login_opt(datas['user'], datas['pwd']) # 断言 mark = self.login_page.get_fail_mark() # 获取登录失败标识 self.assertEqual(mark, datas['expect']) # 判断结果是否等于预期 # json数据驱动测试 @data(*read_json(data_file('test_data.json'), "error_data")) # @unittest.skip('skip this case') def test_login_error_by_json(self, datas): """用户名或密码不正确不能正常登录-数据驱动[ json数据 ]""" # 执行登录操作 self.login_page.login_opt(datas['user'], datas['pwd']) # 断言 mark = self.login_page.get_fail_mark() # 获取登录失败标识 self.assertEqual(mark, datas['expect']) # 判断结果是否等于预期 if __name__ == '__main__': unittest.main() ``` ## 定时执行测试 - 参照utils.task_scheduler.py定时任务示例; - 支持4种执行方式:立即执行、定时执行、Cron时间执行、间隔时间执行; - 定时服务启动后,访问 http://127.0.0.1:8001/jobs 接口地址,即可查询执行中的定时任务列表; ## 测试报告 ![img.png](static/report.png)