diff --git a/backend/app/scripts/data/sys_param.json b/backend/app/scripts/data/sys_param.json index 5ea56392..405d0b82 100644 --- a/backend/app/scripts/data/sys_param.json +++ b/backend/app/scripts/data/sys_param.json @@ -90,7 +90,7 @@ { "config_name": "项目版本", "config_key": "sys_web_version", - "config_value": "2.0.0", + "config_value": "3.0.0", "config_type": true, "status": "0", "description": "初始化数据" diff --git a/backend/data/group.jpg b/backend/data/group.jpg index 5b517ff4..64769f9d 100644 Binary files a/backend/data/group.jpg and b/backend/data/group.jpg differ diff --git a/backend/run_linux.sh b/backend/run_linux.sh index 0931bfc9..302defce 100755 --- a/backend/run_linux.sh +++ b/backend/run_linux.sh @@ -68,7 +68,7 @@ show_banner() { echo -e "╭──────────────────────────────────────────────╮" echo -e "│ \033[1;34m👋 欢迎使用 FastAPI 初始化脚本\033[0m │" echo -e "╰──────────────────────────────────────────────╯" - echo "版本: 1.0.0" + echo "版本: 3.0.0" echo "作者:coderxslee" echo "" } diff --git a/backend/tests/.env b/backend/tests/.env deleted file mode 100644 index 9d24b2cb..00000000 --- a/backend/tests/.env +++ /dev/null @@ -1,33 +0,0 @@ -# 环境配置 -TEST_TYPE=UI -ENVIRONMENT=测试环境 -ENVIRONMENT_HOST=https://xxx.xxx.com -ENVIRONMENT_TOKEN=xxxx - -# 测试人员 -TESTER=张三 - -# 公共配置 -DELETE_ON_OFF=False -EMAIL_ON_OFF=False -DINGDING_ON_OFF=False -QIWEI_ON_OFF=True -JENKINS_URL=http://xxx.xxx.com:10086/job/auto_test/ -REPORT_URL=http://xxx.xxx.com:10086/job/auto_test - -# 邮件配置 -EMAIL_SMTP_SERVER=smtp.qq.com -EMAIL_SENDER=xx@qq.com -EMAIL_PASSWORD=xxx -EMAIL_RECEIVER=xx@qq.com - -# 钉钉配置 -DINGDING_SECRET=xxx -DINGDING_WEBHOOK=https://oapi.dingtalk.com/robot/send?access_token=xxx -DINGDING_AT_MOBILES=["xxx"] - -# 企业微信配置 -WEBHOOK_URL=https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxx - -# 浏览器配置 -BROWSER_TYPE=chromium \ No newline at end of file diff --git a/backend/tests/.gitignore b/backend/tests/.gitignore deleted file mode 100644 index 656907ed..00000000 --- a/backend/tests/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -/.idea/* -/.pytest_cache/ -/venv/ -.venv -.vscode/* - -reports/* -logs/* -case/api/.DS_Store -case/.DS_Store -.DS_Store diff --git a/backend/tests/LICENSE b/backend/tests/LICENSE deleted file mode 100644 index f63f5a9c..00000000 --- a/backend/tests/LICENSE +++ /dev/null @@ -1,194 +0,0 @@ -木兰宽松许可证,第2版 - -木兰宽松许可证,第2版 - -2020年1月 http://license.coscl.org.cn/MulanPSL2 - -您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束: - -0. 定义 - -“软件” 是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 - -“贡献” 是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 - -“贡献者” 是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 - -“法人实体” 是指提交贡献的机构及其“关联实体”。 - -“关联实体” 是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是 -指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 - -1. 授予版权许可 - -每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可 -以复制、使用、修改、分发其“贡献”,不论修改与否。 - -2. 授予专利许可 - -每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定 -撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡 -献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软 -件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“ -关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或 -其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权 -行动之日终止。 - -3. 无商标许可 - -“本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定 -的声明义务而必须使用除外。 - -4. 分发限制 - -您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“ -本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 - -5. 免责声明与责任限制 - -“软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对 -任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于 -何种法律理论,即使其曾被建议有此种损失的可能性。 - -6. 语言 - -“本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文 -版为准。 - -条款结束 - -如何将木兰宽松许可证,第2版,应用到您的软件 - -如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: - -1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; - -2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; - -3, 请将如下声明文本放入每个源文件的头部注释中。 - -Copyright (c) [Year] [name of copyright holder] -[Software Name] is licensed under Mulan PSL v2. -You can use this software according to the terms and conditions of the Mulan -PSL v2. -You may obtain a copy of Mulan PSL v2 at: - http://license.coscl.org.cn/MulanPSL2 -THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY -KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -See the Mulan PSL v2 for more details. - -Mulan Permissive Software License,Version 2 - -Mulan Permissive Software License,Version 2 (Mulan PSL v2) - -January 2020 http://license.coscl.org.cn/MulanPSL2 - -Your reproduction, use, modification and distribution of the Software shall -be subject to Mulan PSL v2 (this License) with the following terms and -conditions: - -0. Definition - -Software means the program and related documents which are licensed under -this License and comprise all Contribution(s). - -Contribution means the copyrightable work licensed by a particular -Contributor under this License. - -Contributor means the Individual or Legal Entity who licenses its -copyrightable work under this License. - -Legal Entity means the entity making a Contribution and all its -Affiliates. - -Affiliates means entities that control, are controlled by, or are under -common control with the acting entity under this License, ‘control’ means -direct or indirect ownership of at least fifty percent (50%) of the voting -power, capital or other securities of controlled or commonly controlled -entity. - -1. Grant of Copyright License - -Subject to the terms and conditions of this License, each Contributor hereby -grants to you a perpetual, worldwide, royalty-free, non-exclusive, -irrevocable copyright license to reproduce, use, modify, or distribute its -Contribution, with modification or not. - -2. Grant of Patent License - -Subject to the terms and conditions of this License, each Contributor hereby -grants to you a perpetual, worldwide, royalty-free, non-exclusive, -irrevocable (except for revocation under this Section) patent license to -make, have made, use, offer for sale, sell, import or otherwise transfer its -Contribution, where such patent license is only limited to the patent claims -owned or controlled by such Contributor now or in future which will be -necessarily infringed by its Contribution alone, or by combination of the -Contribution with the Software to which the Contribution was contributed. -The patent license shall not apply to any modification of the Contribution, -and any other combination which includes the Contribution. If you or your -Affiliates directly or indirectly institute patent litigation (including a -cross claim or counterclaim in a litigation) or other patent enforcement -activities against any individual or entity by alleging that the Software or -any Contribution in it infringes patents, then any patent license granted to -you under this License for the Software shall terminate as of the date such -litigation or activity is filed or taken. - -3. No Trademark License - -No trademark license is granted to use the trade names, trademarks, service -marks, or product names of Contributor, except as required to fulfill notice -requirements in section 4. - -4. Distribution Restriction - -You may distribute the Software in any medium with or without modification, -whether in source or executable forms, provided that you provide recipients -with a copy of this License and retain copyright, patent, trademark and -disclaimer statements in the Software. - -5. Disclaimer of Warranty and Limitation of Liability - -THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY -KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR -COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT -LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING -FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO -MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF -THE POSSIBILITY OF SUCH DAMAGES. - -6. Language - -THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION -AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF -DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION -SHALL PREVAIL. - -END OF THE TERMS AND CONDITIONS - -How to Apply the Mulan Permissive Software License,Version 2 -(Mulan PSL v2) to Your Software - -To apply the Mulan PSL v2 to your work, for easy identification by -recipients, you are suggested to complete following three steps: - -i. Fill in the blanks in following statement, including insert your software -name, the year of the first publication of your software, and your name -identified as the copyright owner; - -ii. Create a file named "LICENSE" which contains the whole context of this -License in the first directory of your software package; - -iii. Attach the statement to the appropriate annotated syntax at the -beginning of each source file. - -Copyright (c) [Year] [name of copyright holder] -[Software Name] is licensed under Mulan PSL v2. -You can use this software according to the terms and conditions of the Mulan -PSL v2. -You may obtain a copy of Mulan PSL v2 at: - http://license.coscl.org.cn/MulanPSL2 -THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY -KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -See the Mulan PSL v2 for more details. diff --git a/backend/tests/README.md b/backend/tests/README.md deleted file mode 100644 index 14402a96..00000000 --- a/backend/tests/README.md +++ /dev/null @@ -1,86 +0,0 @@ -# Auto_Test - -## 项目简介 - -Auto_Test 是一个完全开源免费的自动化测试框架,旨在整合多种工具和技术,提供全面的API和UI自动化测试解决方案。通过使用pytest、playwright、locust、pytest-html、requests、Jenkins等技术,本项目能够帮助测试员高效地进行自动化测试,并生成详细的测试报告。支持通过Jenkins构建发送测试结果通知到邮件、钉钉、企微,方便团队成员及时了解测试状态。 - -## 关键特性 - -- **多技术整合**:结合Python、pytest、playwright等多种工具,实现API和UI自动化测试。 -- **详细报告**:使用pytest-html生成美观且详细的测试报告。 -- **数据处理**:利用openpyxl和pandas处理Excel数据,方便测试数据管理和结果分析。 -- **通知集成**:通过DingTalk发送测试结果通知,确保团队及时了解测试状态。 -- **性能测试**:利用locust进行性能测试,评估系统在高负载下的性能表现。 -- **数据伪造**:借助Faker生成假数据,丰富测试场景。 -- **持续集成**:通过Jenkins实现持续集成,确保项目代码始终处于良好状态。 - -## 项目结构 - -```plaintext -AUTO_TEST -├── case # 测试数据目录 -│ ├── api # 接口测试数据目录 -│ ├── locustfiles # locust性能测试数据目录 -│ └── ui # UI测试数据目录 -├── common # 全局公共模块 -├── config # 配置文件 -├── logs # 日志目录 -├── report # 测试报告 -├── testCase # 测试用例目录 -│ ├── api # 接口测试用例目录 -│ └── ui # UI测试用例目录 -├── tools # 工具类目录 -├── .env # 环境变量配置 -├── locust.conf # locust性能测试配置文件 -├── main.py # 测试主入口 -├── pytest.ini # pytest配置文件 -├── README.md # 项目说明文档 -└── requirements.txt # 依赖包版本文件 -``` - -## 快速开始 - -1.工具: - -- python下载地址: -- pycharm下载地址: - -2.搭建步骤 - -- 2.1拉取代码 - - git clone - - 查看本地和远程所有分支: git branch -a - - 切换分支:git checkout [branch-name] - -- 2.2创建虚拟环境: - - python -m venv venv - - venv/Scripts/activate - - 回车激活环境 - -- 2.3安装项目依赖包 - - pip install -r requirements.txt - - pip install -r requirements.txt --upgrade - -- 2.4运行项目 - - 运行前检查config目录中Config.ini文件是否配置正确 - - python main.py - -- 2.5ui录制脚本 - - playwright codegen - - - 如果没有找到浏览器驱动则执行下方,一次会下载三个浏览器,chromium、firefox、webkit - playwright install - - - 或者指定下载浏览器 - playwright install chromium - playwright install firefox - playwright install webkit - -- 性能测试 - - https://docs.locust.io/en/stable/api.html - -## 🎨 社区交流 - -| 群组二维码 | 微信支付二维码 | -| --- | --- | -| ![群组二维码](https://gitee.com/fastapiadmin/FastDocs/raw/master/docs/public/group.jpg) | ![微信支付二维码](https://gitee.com/fastapiadmin/FastDocs/raw/master/docs/public/wechatPay.jpg) | \ No newline at end of file diff --git a/backend/tests/case/api/case.xlsx b/backend/tests/case/api/case.xlsx deleted file mode 100644 index 16b87803..00000000 Binary files a/backend/tests/case/api/case.xlsx and /dev/null differ diff --git a/backend/tests/case/api/case.yaml b/backend/tests/case/api/case.yaml deleted file mode 100644 index c1531207..00000000 --- a/backend/tests/case/api/case.yaml +++ /dev/null @@ -1,142 +0,0 @@ -Case: - - Test: - name: test_001_yaml - run: yes - type: json - request: - method: POST - url: $host/gateway/traceplan/trackPlan/getData - headers: - Content-Type: application/json;charset=UTF-8 - token: $token - data: {"staSelect":"09100101","startTime":"2025-10-14 00:00:00","endTime":"2025-10-14 00:30:00","page":1,"limit":50,"showApplication":true,"trackStatus":"4"} - expected: - code: 200 - isSuccess: true - - Test: - name: test_002_yaml - run: yes - type: json - request: - method: POST - url: $host/gateway/traceplan/trackPlan/getData - headers: - Content-Type: application/json;charset=UTF-8 - token: $token - data: {"staSelect":"09100101","startTime":"2025-10-14 00:00:00","endTime":"2025-10-14 00:30:00","page":1,"limit":50,"showApplication":true,"trackStatus":"4"} - expected: - code: 200 - isSuccess: true - - Test: - name: test_003_yaml - run: yes - type: json - request: - method: POST - url: $host/gateway/traceplan/trackPlan/getData - headers: - Content-Type: application/json;charset=UTF-8 - token: $token - data: {"staSelect":"09100101","startTime":"2025-10-14 00:00:00","endTime":"2025-10-14 00:30:00","page":1,"limit":50,"showApplication":true,"trackStatus":"4"} - expected: - code: 200 - isSuccess: true - - Test: - name: test_004_yaml - run: yes - type: json - request: - method: POST - url: $host/gateway/traceplan/trackPlan/getData - headers: - Content-Type: application/json;charset=UTF-8 - token: $token - data: {"staSelect":"09100101","startTime":"2025-10-14 00:00:00","endTime":"2025-10-14 00:30:00","page":1,"limit":50,"showApplication":true,"trackStatus":"4"} - expected: - code: 200 - isSuccess: true - - Test: - name: test_005_yaml - run: yes - type: json - request: - method: POST - url: $host/gateway/traceplan/trackPlan/getData - headers: - Content-Type: application/json;charset=UTF-8 - token: $token - data: {"staSelect":"09100101","startTime":"2025-10-14 00:00:00","endTime":"2025-10-14 00:30:00","page":1,"limit":50,"showApplication":true,"trackStatus":"4"} - expected: - code: 200 - isSuccess: true - - Test: - name: test_006_yaml - run: yes - type: json - request: - method: POST - url: $host/gateway/traceplan/trackPlan/getData - headers: - Content-Type: application/json;charset=UTF-8 - token: $token - data: {"staSelect":"09100101","startTime":"2025-10-14 00:00:00","endTime":"2025-10-14 00:30:00","page":1,"limit":50,"showApplication":true,"trackStatus":"4"} - expected: - code: 200 - isSuccess: true - - Test: - name: test_007_yaml - run: yes - type: json - request: - method: POST - url: $host/gateway/traceplan/trackPlan/getData - headers: - Content-Type: application/json;charset=UTF-8 - token: $token - data: {"staSelect":"09100101","startTime":"2025-10-14 00:00:00","endTime":"2025-10-14 00:30:00","page":1,"limit":50,"showApplication":true,"trackStatus":"4"} - expected: - code: 200 - isSuccess: true - - Test: - name: test_008_yaml - run: yes - type: json - request: - method: POST - url: $host/gateway/traceplan/trackPlan/getData - headers: - Content-Type: application/json;charset=UTF-8 - token: $token - data: {"staSelect":"09100101","startTime":"2025-10-14 00:00:00","endTime":"2025-10-14 00:30:00","page":1,"limit":50,"showApplication":true,"trackStatus":"4"} - expected: - code: 200 - isSuccess: true - - Test: - name: test_009_yaml - run: yes - type: json - request: - method: POST - url: $host/gateway/traceplan/trackPlan/getData - headers: - Content-Type: application/json;charset=UTF-8 - token: $token - data: {"staSelect":"09100101","startTime":"2025-10-14 00:00:00","endTime":"2025-10-14 00:30:00","page":1,"limit":50,"showApplication":true,"trackStatus":"4"} - expected: - code: 200 - isSuccess: true - - Test: - name: test_010_yaml - run: yes - type: json - request: - method: POST - url: $host/gateway/traceplan/trackPlan/getData - headers: - Content-Type: application/json;charset=UTF-8 - token: $token - data: {"staSelect":"09100101","startTime":"2025-10-14 00:00:00","endTime":"2025-10-14 00:30:00","page":1,"limit":50,"showApplication":true,"trackStatus":"4"} - expected: - code: 200 - isSuccess: true - diff --git a/backend/tests/case/locustfiles/demo01.py b/backend/tests/case/locustfiles/demo01.py deleted file mode 100644 index b62a3f33..00000000 --- a/backend/tests/case/locustfiles/demo01.py +++ /dev/null @@ -1,66 +0,0 @@ -import time -from locust import HttpUser, task, between, User -from locust.runners import MasterRunner - -from locust import events - -@events.test_start.add_listener -def on_test_start(environment, **kwargs): - print("开始监听") - -@events.test_stop.add_listener -def on_test_stop(environment, **kwargs): - print("结束监听") - -@events.init.add_listener -def on_locust_init(environment, **kwargs): - if isinstance(environment.runner, MasterRunner): - print("我在master节点上") - else: - print("我在worker节点或独立节点上") - -class QuickstartUser(HttpUser): - # 例如,让每个用户在每次任务执行之间等待0.5到10秒: - wait_time = between(0.5, 10) - - # 例如,下一个用户类会睡一秒,然后两秒,再三秒,依此类推。 - last_wait_time = 0 - - def wait_time(self): - self.last_wait_time += 1 - return self.last_wait_time - - # @task 会有一个可选的权重参数,可以用来指定任务的执行比率。在 以下示例,任务2被选中的概率是任务1的两倍: - @task(3) - def task1(self): - self.client.get("/hello") - self.client.get("/world") - - @task(6) - def task2(self): - for item_id in range(10): - self.client.get(f"/item?id={item_id}", name="/item") - time.sleep(1) - - - @task - def my_task(self): - with self.client.get("/", catch_response=True) as response: - if response.text != "Success": - response.failure("Got wrong response") - elif response.elapsed.total_seconds() > 0.5: - response.failure("Request took too long") - elif response.status_code == 404: - raise RescheduleTask() - - def on_start(self): - self.client.post("/login", json={"username":"foo", "password":"bar"}) - -# 例如,假设我们有一个WebUser类和一个MobileUser类,我们想让MobileUser类的用户权重为1,而WebUser类的用户权重为3。 -class WebUser(User): - weight = 3 - ... - -class MobileUser(User): - weight = 1 - ... \ No newline at end of file diff --git a/backend/tests/case/locustfiles/image.png b/backend/tests/case/locustfiles/image.png deleted file mode 100644 index adb6738c..00000000 Binary files a/backend/tests/case/locustfiles/image.png and /dev/null differ diff --git a/backend/tests/case/locustfiles/locust.py b/backend/tests/case/locustfiles/locust.py deleted file mode 100644 index a9e1c8ce..00000000 --- a/backend/tests/case/locustfiles/locust.py +++ /dev/null @@ -1,75 +0,0 @@ -# 在最佳情况下(在 -loop 内执行小请求),单个 Locust 进程(限制在一个 CPU 核心) -# 使用 FastHttpUser 可以每秒处理大约 16000 个请求,使用 HttpUser 可以执行 4000 -# 个请求(测试于 2021 年 M1 MacBook Pro 和 Python 3.11 上) - -from locust import TaskSet, task, HttpUser, run_single_user, FastHttpUser -from locust.clients import ResponseContextManager -from locust.runners import logger -from locust import HttpUser, task, between - -class Task(TaskSet): - tasks = {ThreadPage:15, write_post:1} - @tag('thread') - @task(3) - def demo_api(self): - # 基于注释中的接口信息设置请求参数 - payload = {} - - # 设置请求头 - headers = { - "Content-Type": "application/json", - "token": "xxxx" - } - - path = "/gateway/xxx" - logger.info(f"请求路径 {path}") - with self.client.post(path, json=payload, headers=headers, catch_response=True) as res: - res: ResponseContextManager - if res.status_code != 200: - # 输出请求的完整 URL 和状态码 - logger.error(f"请求失败. 接口地址: {res.request.url}, 状态码: {res.status_code}") - # 输出响应文本 - logger.error(f"响应文本: {res.text}") - # 标记请求为失败 - res.failure(res.text) - else: - # 检查响应内容是否包含预期的数据结构 - try: - response_json = res.json() - if not response_json: - res.failure("响应体为空") - except ValueError: - res.failure("响应体不是有效的 JSON 格式") - - - - def on_start(self): - logger.info('s'*10 + '测试开始' + 's'*10) - - def on_stop(self): - logger.info('s'*10 + '测试结束' + 's'*10) - -# class Test(HttpUser): -class Test(FastHttpUser): - wait_time = between(1, 5) - - # 根据注释中的接口地址更新host配置 - host = 'https://xxx.com' - tasks = [Task, ] - - @task - def t(self): - with self.rest("POST", "/", json={"foo": 1}) as resp: - if resp.js is None: - pass # no need to do anything, already marked as failed - elif "bar" not in resp.js: - resp.failure(f"'bar' missing from response {resp.text}") - elif resp.js["bar"] != 42: - resp.failure(f"'bar' had an unexpected value: {resp.js['bar']}") - -if __name__ == '__main__': - # 修复变量名错误,应该是Test类而不是test - run_single_user(Test) - - # 启动 Locust 测试 - # locust -f locust.py diff --git a/backend/tests/case/ui/__init__.py b/backend/tests/case/ui/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/tests/case/ui/pages/__init__.py b/backend/tests/case/ui/pages/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/tests/case/ui/pages/base_page.py b/backend/tests/case/ui/pages/base_page.py deleted file mode 100644 index 109d43c1..00000000 --- a/backend/tests/case/ui/pages/base_page.py +++ /dev/null @@ -1,81 +0,0 @@ -# -*- coding: utf-8 -*- - -from typing import Dict, Any -from playwright.sync_api import Page, expect - - -class BasePage: - def __init__(self, page: Page) -> None: - self.page = page - - def login(self, test_data: Dict[str, Any]) -> bool: - # 设置页面加载超时 - self.page.set_default_timeout(30000) - - # 访问登录页面并等待页面完全加载 - self.page.goto(test_data["url"]) - self.page.wait_for_load_state("networkidle") # 等待网络活动空闲 - - # 等待用户名输入框可见且可交互 - username_input = self.page.get_by_role("textbox", name="用户名") - username_input.wait_for(state="visible", timeout=10000) - username_input.click() - username_input.fill(test_data["username"]) - - # 等待密码输入框可见且可交互 - password_input = self.page.get_by_role("textbox", name="请输入登录密码") - password_input.wait_for(state="visible", timeout=10000) - password_input.click() - password_input.fill(test_data["password"]) - - # 等待登录按钮可见且可交互 - login_button = self.page.get_by_role("button", name="登录") - login_button.wait_for(state="visible", timeout=10000) - # 使用click()方法本身会等待元素可交互,不需要单独检查enabled状态 - login_button.click() - - # 等待登录后页面加载和弹窗出现 - self.page.wait_for_load_state("networkidle") - - # 等待SaaS系统使用引导文本出现 - self.page.wait_for_selector("text=SaaS系统使用引导", timeout=15000) - expect(self.page.get_by_text("SaaS系统使用引导")).to_be_visible() - expect(self.page.get_by_text("登录成功")).to_be_visible() - - # 等待关闭按钮可见且可交互 - close_button = self.page.get_by_role("button", name="Close") - close_button.wait_for(state="visible", timeout=10000) - # 使用click()方法本身会等待元素可交互,不需要单独检查enabled状态 - close_button.click() - - # 关闭登录成功提示 - self.page.locator(".el-icon.el-notification__closeBtn > svg").click() - - # 等待弹窗关闭 - self.page.wait_for_selector("text=SaaS系统使用引导", state="hidden", timeout=5000) - - return True - - def logout(self) -> bool: - # 等待用户名显示 - expect(self.page.get_by_text("涛")).to_be_visible(timeout=20000) - self.page.get_by_text("涛").click() - # 等待退出登录选项显示 - self.page.wait_for_selector("text=退出登录", state="visible", timeout=10000) - - expect(self.page.get_by_text("退出登录")).to_be_visible(timeout=10000) - self.page.get_by_text("退出登录").click() - # 等待确认对话框显示 - self.page.wait_for_selector("text=确定要退出登录吗?", state="visible", timeout=10000) - - expect(self.page.get_by_text("确定要退出登录吗?")).to_be_visible(timeout=10000) - expect(self.page.get_by_role("button", name="确认")).to_be_visible(timeout=10000) - self.page.get_by_role("button", name="确认").click() - # 等待退出成功提示显示 - self.page.wait_for_selector("text=退出成功", state="visible", timeout=10000) - - expect(self.page.get_by_text("退出成功")).to_be_visible(timeout=10000) - # 关闭退出成功提示 - self.page.get_by_role("img").nth(4).click() - - return True diff --git a/backend/tests/common/__init__.py b/backend/tests/common/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/tests/common/reponse_tool.py b/backend/tests/common/reponse_tool.py deleted file mode 100644 index 8d307275..00000000 --- a/backend/tests/common/reponse_tool.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- - -import logging -import requests - -class Response: - def __init__(self): - pass - - def result(self, res: requests.Response) -> dict: - """ - :param res: requests响应对象 - :return: 格式化后的响应字典 - """ - try: - response_dicts = dict() - - # 响应状态码 - response_dicts['code'] = res.status_code - - # 响应body - response_body = res.json() - - # 确保响应体是字典格式 - if isinstance(response_body, dict): - response_dicts['body'] = response_body - - # 响应秒时间 - response_dicts['time_total'] = res.elapsed.total_seconds() # 秒为单位 - - logging.info(f"【请求响应结果为: {response_dicts}】") - return response_dicts - except Exception as e: - logging.error(f"响应结果处理异常:{e}") diff --git a/backend/tests/common/request_tool.py b/backend/tests/common/request_tool.py deleted file mode 100644 index 56ea5d9e..00000000 --- a/backend/tests/common/request_tool.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- - -import requests -import logging - -class Requests: - def __init__(self): - """ - 单例模式保证测试过程中使用的都是一个session对象 - """ - self.session = requests.Session() - - def send_request(self,url: str, method: str, data_type: str, headers: dict = None, data: dict = None): - """ - :param url: 请求url - :param method: 请求方法 - :param data_type: 入参关键字, params(查询参数类型,明文传输,一般在url?参数名=参数值), data(一般用于form表单类型参数), json(一般用于json类型请求参数) - :param headers: 请求头 - :param data: 参数数据,默认等于None - :return: 返回res对象 - """ - res = None - try: - logging.info(f""" - 【请求地址】Url: {url} - 【请求类型】Method: {method} - 【请求头】Headers: {headers} - 【请求数据】Data: {data} - """) - if data_type == 'params': - res = self.session.request(method=method, url=url, params=data, headers=headers) - elif data_type == 'data': - res = self.session.request(method=method, url=url, data=data, headers=headers) - elif data_type == 'json': - res = self.session.request(method=method, url=url, json=data, headers=headers) - elif data_type == 'file': - res = self.session.request(method=method, url=url, files=data, headers=headers) - else: - logging.error('parametric_key为params、json、data、file, 不支持其他类型') - except Exception as e: - logging.error(f"发送请求异常:{e}") - return res diff --git a/backend/tests/config/__init__.py b/backend/tests/config/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/tests/config/setting.py b/backend/tests/config/setting.py deleted file mode 100644 index 478f87cd..00000000 --- a/backend/tests/config/setting.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- coding: utf-8 -*-# - -from pydantic_settings import BaseSettings, SettingsConfigDict -from pathlib import Path -from functools import lru_cache - -from tools.random_tool import ContextPack - -class Settings(BaseSettings): - model_config = SettingsConfigDict( - env_file='.env', - env_file_encoding='utf-8', - extra='ignore' - ) - - BANNER: str = """ - __ _ _ _____ ___ _____ ____ __ _____ - / /\ | | | | | / / \ | | | |_ ( (` | | - /_/--\ \_\_/ |_| \_\_/ |_| |_|__ _)_) |_| - """ - # ===================公共配置====================== - # 项目的根目录 - BASE_PATH: Path = Path(__file__).parent.parent - # 用例所需测试文件路径 - CASE_PATH: Path = BASE_PATH / "case" - # log路径 - LOG_PATH: Path = BASE_PATH / "logs" / (ContextPack().get_day_time + '.log') - # 项目测试报告 - REPORT_PATH: Path = BASE_PATH / "reports" - # 项目测试报告路径 - HTML_REPORT_PATH: Path = REPORT_PATH / (ContextPack().get_day_time + '.html') - # api数据路径 - API_EXCEL_FILE: Path = CASE_PATH / "api" / "case.xlsx" - API_YAML_PATH: Path = CASE_PATH / "api" / 'case.yaml' - # UI截图路径 - UI_IMG_PATH: Path = REPORT_PATH / "img" - - # ===================环境配置====================== - TEST_TYPE: str - ENVIRONMENT: str - ENVIRONMENT_HOST: str - ENVIRONMENT_TOKEN: str - - # ===================测试人员====================== - TESTER: str - - # ===================公共配置====================== - DELETE_ON_OFF: bool - EMAIL_ON_OFF: bool - DINGDING_ON_OFF: bool - JENKINS_URL: str - REPORT_URL: str - - # ===================邮件配置====================== - EMAIL_SMTP_SERVER: str - EMAIL_SENDER: str - EMAIL_PASSWORD: str - EMAIL_RECEIVER: str - - # ===================钉钉配置====================== - DINGDING_SECRET: str - DINGDING_WEBHOOK: str - DINGDING_AT_MOBILES: list[str] - - # ===================浏览器配置====================== - BROWSER_TYPE: str - - # ===================企业微信配置====================== - QIWEI_ON_OFF: bool - WEBHOOK_URL: str - -@lru_cache() -def get_settings(): - return Settings() - -settings = get_settings() \ No newline at end of file diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py deleted file mode 100644 index f80bfeb1..00000000 --- a/backend/tests/conftest.py +++ /dev/null @@ -1,34 +0,0 @@ -import os -import sys - -# 必须在首次 import app 之前设置,以便 Settings / database 引擎使用 SQLite 与内存 Redis -_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) -sys.path.insert(0, _ROOT) - -os.environ.setdefault("ENVIRONMENT", "dev") -os.environ["TESTING"] = "1" -os.environ["DATABASE_TYPE"] = "sqlite" -os.environ["DATABASE_NAME"] = os.path.join(_ROOT, "pytest_fastapiadmin") - -import pytest -from fastapi.testclient import TestClient - -from main import create_app - -# 创建测试客户端(依赖上述环境变量) -app = create_app() - - -@pytest.fixture(scope="session") -def test_client(): - """ - 会话级 HTTP 测试客户端(与 ``conftest`` 中单例 ``app`` 对齐,只启动一次生命周期)。 - - 若使用 ``module`` 作用域,多个测试文件各开一个 ``TestClient`` 会重复驱动同一 - ``app`` 的 lifespan,易导致第二次 ``__enter__`` 时出现 ``CancelledError``。 - - 返回: - - TestClient: 供用例发起的同步测试客户端(yield 注入)。 - """ - with TestClient(app) as client: - yield client diff --git a/backend/tests/locust.conf b/backend/tests/locust.conf deleted file mode 100644 index d556b5e8..00000000 --- a/backend/tests/locust.conf +++ /dev/null @@ -1,9 +0,0 @@ -locustfile = locust_files/my_locust_file.py -headless = true -master = true -expect-workers = 5 -host = https://target-system -users = 100 -spawn-rate = 10 -run-time = 10m -tags = [Critical, Normal] \ No newline at end of file diff --git a/backend/tests/main.py b/backend/tests/main.py deleted file mode 100644 index 1c70763c..00000000 --- a/backend/tests/main.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- - -import pytest -from config.setting import settings - -# 获取Jenkins选项参数(切记参数名称不可以使用中文) -# import os -# TEST_TYPE = os.environ['TESTTYPE'] -# ENVIRONMENT: str = os.environ['ENVIRONMENT'] -# API_HOST: str = os.environ['APIHOST'] -# TESTER: str = os.environ['TESTER'] - -args = [ - '-sv', - f'--html={settings.HTML_REPORT_PATH}', - '--self-contained-html', # 静态html, 不需要额外的资源文件 - f'--log-file={settings.LOG_PATH}', - '-W', 'ignore:Module already imported:pytest.PytestWarning' -] - -if __name__ == '__main__': - # 执行测试 - pytest.main(args) diff --git a/backend/tests/pytest.ini b/backend/tests/pytest.ini deleted file mode 100644 index bf7c4695..00000000 --- a/backend/tests/pytest.ini +++ /dev/null @@ -1,48 +0,0 @@ -[pytest] -; addopts = -v -s -q --report=_report.html --tester=Phoenixy --title=测试报告 --desc=报告描述信息 --template=2 -; addopts = -v -s -q --html=./reports/result.html --self-contained-html - -generate_report_on_test = True -environment_table_redact_list = ^foo$ - .*redact.* - bar -render_collapsed = failed,error,skipped - -testpaths = test_case/ui -python_files = test_*.py *_test .py test*.py -python_classes = Test* Test_* test* test_* -python_functions = test_* test* - -log_file_level = INFO -log_file_format = %(asctime)s | %(levelname)s | %(filename)s - %(funcName)s[line:%(lineno)d]: %(message)s -log_file_date_format = %Y-%m-%d %H:%M:%S - -log_cli = True -log_cli_level = INFO -; log_cli_format = %(asctime)s | %(levelname)s | %(filename)s - %(funcName)s[line:%(lineno)d]: %(message)s -; log_cli_date_format=%Y-%m-%d %H:%M:%S - -# --html: 生成html的测试报告,后面 需要跟上所创建的文件位置及文件名称(前提安装插件:pytest-html) -; pytest -vs --html ./reports/result.html -; # -n: 多线程运行(前提安装插件:pytest-xdist) -; pytest -vs -n=2 -; # --reruns num: 失败重跑(前提安装插件:pytest-rerunfailres) -; pytest -vs --reruns=2 -; # -x: 出现一个用例失败则停止测试 -; pytest -vs -x -; # --maxfail: 出现几个失败才终止 -; pytest -vs --maxfail=2 -; # -k: 运行测试用例名称中包含某个字符串的测试用例,我们可以采用or表示或者,采用and表示都 -; pytest -vs -k "qiuluo" -; pytest -vs -k "qiuluo or weiliang" -; pytest -vs -k "qiuluo and weiliang" -; # -m:冒烟用例执行,后面需要跟一个冒烟名称,执行user_manage这个分组 -; pytest -vs -m user_manage - -; 下面是解决中文乱码问题:是为了在测试用例收集完成后对测试项的 name 和 nodeid 进行处理,以确保它们在控制台上的显示是正确的。 -disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True - -filterwarnings = - error - ignore::UserWarning - ignore:function ham\(\) is deprecated:DeprecationWarning \ No newline at end of file diff --git a/backend/tests/requirements.txt b/backend/tests/requirements.txt deleted file mode 100644 index 7f8250cf..00000000 Binary files a/backend/tests/requirements.txt and /dev/null differ diff --git a/backend/tests/test_case/api/__init__.py b/backend/tests/test_case/api/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/tests/test_case/api/conftest.py b/backend/tests/test_case/api/conftest.py deleted file mode 100644 index 64373331..00000000 --- a/backend/tests/test_case/api/conftest.py +++ /dev/null @@ -1,119 +0,0 @@ -# -*- coding: utf-8 -*- - -import datetime -import time -import os -import shutil -import pytest -from _pytest.terminal import TerminalReporter -import pytest_html -import pytest_html.extras -from pytest_metadata.plugin import metadata_key -import logging - -from config.setting import settings -from tools.email_tool import EmailPack -from tools.dingding_tool import DingTalkPack -from tools.qiwei_tool import QiWeiPack - -# 常量定义 -TEST_TYPE: str = settings.TEST_TYPE -ENVIRONMENT: str = settings.ENVIRONMENT -TESTER: str = settings.TESTER - -""" -module级别的测试初始化和结果收集 -class级别的测试初始化和结果收集 -function级别的测试初始化和结果收集 -""" -@pytest.fixture(scope="session", autouse=True) -def init_session(): - """会话级别的测试初始化和结果收集""" - if TEST_TYPE not in ['API', 'UI']: - raise ValueError("请输入正确的自动化测试类型['API','UI']") - if settings.DELETE_ON_OFF == 'True': - if os.path.exists(settings.REPORT_PATH): - shutil.rmtree(settings.REPORT_PATH) - logging.info("历史报告数据清理完成") - logging.info(f"{settings.BANNER}\n" - f"开始执行 {TEST_TYPE} 自动化测试\n" - f"测试环境: {ENVIRONMENT}\n" - f"测试人员: {TESTER}") - yield - logging.info(f"========== {TEST_TYPE} 自动化测试结束 ==========") - -def pytest_terminal_summary(terminalreporter: TerminalReporter, exitstatus: int, config: pytest.Config): - """收集终端测试结果""" - stats = terminalreporter.stats - result = { - 'total': terminalreporter._numcollected, - 'pass_num': len(stats.get('passed', [])), - 'fail_num': len(stats.get('failed', [])), - 'skip_num': len(stats.get('skipped', [])), - 'error_num': len(stats.get('error', [])), - 'rate': f"{((len(stats.get('passed', [])) / terminalreporter._numcollected * 100) if terminalreporter._numcollected else 0):.2f}%", - 'duration': f'{round(time.time() - terminalreporter._sessionstarttime, 2)}秒', - 'reprot_url': settings.REPORT_URL, - 'jenkins_url': settings.JENKINS_URL - } - - logging.info(f"总用例数: {result['total']} | 通过: {result['pass_num']}| 失败: {result['fail_num']} | 跳过: {result['skip_num']} | 错误: {result['error_num']} | 成功率: {result['rate']} | 总耗时: {result['duration']}") - - # 发送邮件 - if settings.EMAIL_ON_OFF: - EmailPack( - fromaddr=settings.EMAIL_SENDER, - password=settings.EMAIL_PASSWORD, - toaddrs=[settings.EMAIL_RECEIVER], - server_host=settings.EMAIL_SMTP_SERVER, - ).send_default_email( - title=TEST_TYPE, - environment=ENVIRONMENT, - tester=TESTER, - **result - ) - - # 发送钉钉消息 - if settings.DINGDING_ON_OFF: - DingTalkPack(settings.DINGDING_WEBHOOK, settings.DINGDING_SECRET).send_dingding( - title=TEST_TYPE, - environment=ENVIRONMENT, - tester=TESTER, - **result - ) - - # 发送企业微信消息 - if settings.QIWEI_ON_OFF: - QiWeiPack(settings.WEBHOOK_URL).send_test_report( - title=TEST_TYPE, - environment=ENVIRONMENT, - tester=TESTER, - **result - ) - -def pytest_html_report_title(report): - """设置报告标题""" - report.title = f"{TEST_TYPE}自动化测试报告" - -def pytest_configure(config: pytest.Config): - """测试配置""" - config.stash[metadata_key].update({ - "项目名称": f"{TEST_TYPE} 自动化测试", - "测试类型": TEST_TYPE, - "测试环境": ENVIRONMENT, - "测试人员": TESTER, - "开始时间": datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), - }) - -@pytest.hookimpl(hookwrapper=True) -def pytest_runtest_makereport(item, call: pytest.CallInfo): - """处理测试报告""" - outcome = yield - report: pytest.TestReport = outcome.get_result() - - if report.when == "call" and report.failed: - extras = getattr(report, 'extras', []) - if os.path.exists(settings.LOG_PATH): - extras.append(pytest_html.extras.url(settings.LOG_PATH, name="查看详细日志")) - report.extras = extras - diff --git a/backend/tests/test_case/api/test_api.py b/backend/tests/test_case/api/test_api.py deleted file mode 100644 index ce5c4a19..00000000 --- a/backend/tests/test_case/api/test_api.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- - -import pytest -import logging -from typing import Dict -from config.setting import settings - -from tools.excel_tool import ExcelPack -from tools.yaml_tool import YamlPack -from tools.manage import Manage - -manage = Manage() -excel = ExcelPack(file_name=settings.API_EXCEL_FILE, api_host=settings.ENVIRONMENT_HOST, token=settings.ENVIRONMENT_TOKEN) -yaml = YamlPack(yaml_path=settings.API_YAML_PATH, api_host=settings.ENVIRONMENT_HOST, token=settings.ENVIRONMENT_TOKEN) - - -class TestExcel: - - @pytest.mark.parametrize('case', excel.load_test_cases(), ids=lambda x: x['name']) - def test_excel_api(self, case: Dict) -> None: - """Excel数据驱动的API测试类""" - logging.info(f"开始执行用例: {case['name']}") - - # 检查是否需要执行 - if str(case['run']).lower() != 'yes': - logging.info(f"跳过用例: {case['name']}") - excel.write_to_excel(row=case['row'], result=manage.create_result(case['name'], 'skip')) - pytest.skip() - - # 执行测试用例 - result = manage.execute_test_case(case) - - # 写入结果 - excel.write_to_excel(row=case['row'], result=result) - - # 断言结果 - assert result['result'] == 'pass', ( - f"用例执行失败: {case['name']}\n" - f"请求信息: {case['request']}\n" - f"期望结果: {case['expected']}\n" - f"实际结果: {result['response']}" - ) - - @pytest.mark.parametrize('case', yaml.load_test_cases(), ids=lambda x: x['name']) - def test_yaml_api(self, case: Dict) -> None: - """Yaml数据驱动的API测试类""" - logging.info(f"开始执行用例: {case['name']}") - - # 检查是否需要执行 - if not case['run']: - logging.info(f"跳过用例: {case['name']}") - pytest.skip() - - # 执行测试用例 - result = manage.execute_test_case(case) - - # 断言结果 - assert result['result'] == 'pass', ( - f"用例执行失败: {case['name']}\n" - f"请求信息: {case['request']}\n" - f"期望结果: {case['expected']}\n" - f"实际结果: {result['response']}" - ) diff --git a/backend/tests/test_case/ui/__init__.py b/backend/tests/test_case/ui/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/tests/test_case/ui/conftest.py b/backend/tests/test_case/ui/conftest.py deleted file mode 100644 index 6a4897a3..00000000 --- a/backend/tests/test_case/ui/conftest.py +++ /dev/null @@ -1,187 +0,0 @@ -# -*- coding: utf-8 -*- - -import base64 -import time -import logging -import datetime -import shutil -import pytest -from pytest_metadata.plugin import metadata_key -from _pytest.terminal import TerminalReporter - -from config.setting import settings -from tools.email_tool import EmailPack -from tools.dingding_tool import DingTalkPack -from tools.qiwei_tool import QiWeiPack - -# 常量定义 -TEST_TYPE: str = settings.TEST_TYPE -ENVIRONMENT: str = settings.ENVIRONMENT -TESTER: str = settings.TESTER - -# 状态常量 -success = "✓" -fail = "✗" - -# 全局page对象,用于在整个测试会话中共享 -GLOBAL_PAGE = None - -""" -module级别的测试初始化和结果收集 -class级别的测试初始化和结果收集 -function级别的测试初始化和结果收集 -""" -@pytest.fixture(scope="session", autouse=True) -def init_session(): - """会话级别的测试初始化和结果收集 - 使用Playwright,确保唯一驱动实例""" - if TEST_TYPE not in ['API', 'UI']: - raise ValueError("请输入正确的自动化测试类型['API','UI']") - if settings.DELETE_ON_OFF == 'True': - if settings.REPORT_PATH.exists(): - shutil.rmtree(settings.REPORT_PATH) - logging.info("历史报告数据清理完成") - logging.info( - f"{settings.BANNER}\n" - f"开始执行 {TEST_TYPE} 自动化测试\n" - f"测试环境: {ENVIRONMENT}\n" - f"测试人员: {TESTER}" - ) - from playwright.sync_api import sync_playwright - playwright = sync_playwright().start() - browser = playwright.chromium.launch(headless=False) - context = browser.new_context(record_video_dir="./report/videos/", viewport={"width": 1920, "height": 1080}) # 生成适配视频 - page = context.new_page() - # 更新全局page对象 - global GLOBAL_PAGE - GLOBAL_PAGE = page - logging.info(f"{success} ==> Playwright 浏览器实例初始化完成") - # 返回page对象供测试用例使用 - yield page - # 清理资源 - try: - context.close() - browser.close() - playwright.stop() - logging.info(f"{success} ==> Playwright 资源已全部释放") - except Exception as e: - logging.error(f"{fail} ==> Playwright 资源释放失败: {str(e)}") - else: - logging.info(f"========== {TEST_TYPE} 自动化测试结束 ==========") - -def pytest_terminal_summary(terminalreporter: TerminalReporter, exitstatus: int, config: pytest.Config): - """收集终端测试结果""" - stats = terminalreporter.stats - result = { - 'total': terminalreporter._numcollected, - 'passed': len(stats.get('passed', [])), - 'failed': len(stats.get('failed', [])), - 'skipped': len(stats.get('skipped', [])), - 'error': len(stats.get('error', [])), - 'success_rate': f"{((len(stats.get('passed', [])) / terminalreporter._numcollected * 100) if terminalreporter._numcollected else 0):.2f}%", - 'duration': f'{round(time.time() - terminalreporter._sessionstarttime, 2)}秒', - 'reprot_url': settings.REPORT_URL, - 'jenkins_url': settings.JENKINS_URL - } - - logging.info(f"总用例数: {result['total']} | 通过: {result['passed']}| 失败: {result['failed']} | 跳过: {result['skipped']} | 错误: {result['error']} | 成功率: {result['success_rate']} | 总耗时: {result['duration']}") - - # 发送邮件 - if settings.EMAIL_ON_OFF: - EmailPack( - fromaddr=settings.EMAIL_SENDER, - password=settings.EMAIL_PASSWORD, - toaddrs=[settings.EMAIL_RECEIVER], - server_host=settings.EMAIL_SMTP_SERVER - ).send_default_email( - title=TEST_TYPE, - environment=ENVIRONMENT, - tester=TESTER, - **result - ) - - # 发送钉钉消息 - if settings.DINGDING_ON_OFF: - DingTalkPack(settings.DINGDING_WEBHOOK, settings.DINGDING_SECRET).send_dingding( - title=TEST_TYPE, - environment=ENVIRONMENT, - tester=TESTER, - **result - ) - - # 发送企业微信消息 - if settings.QIWEI_ON_OFF: - # 调整参数名以匹配企业微信工具的要求 - qiwei_result = { - 'total': result['total'], - 'pass_num': result['passed'], - 'fail_num': result['failed'], - 'error_num': result['error'], - 'skip_num': result['skipped'], - 'rate': result['success_rate'], - 'duration': result['duration'], - 'report_url': result['reprot_url'], # 修正拼写错误 - 'jenkins_url': result['jenkins_url'] - } - QiWeiPack(settings.WEBHOOK_URL).send_test_report( - title=TEST_TYPE, - environment=ENVIRONMENT, - tester=TESTER, - **qiwei_result - ) - -def pytest_html_report_title(report): - """设置报告标题""" - report.title = f"{TEST_TYPE}自动化测试报告" - -def pytest_configure(config: pytest.Config): - """测试配置""" - config.stash[metadata_key].update({ - "项目名称": f"{TEST_TYPE} 自动化测试", - "测试类型": TEST_TYPE, - "测试环境": ENVIRONMENT, - "测试人员": TESTER, - "开始时间": datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), - }) - -@pytest.hookimpl(hookwrapper=True) -def pytest_runtest_makereport(item: pytest.Item, call: pytest.CallInfo): - pytest_html = item.config.pluginmanager.getplugin('html') - outcome = yield - report: pytest.TestReport = outcome.get_result() - extras = getattr(report, 'extras', []) - extras.append(pytest_html.extras.url(str(settings.LOG_PATH), name="查看运行日志")) - if report.when == 'call' or report.when == "setup": - xfail = hasattr(report, 'wasxfail') - if (report.skipped and xfail) or (report.failed and not xfail): - file_name = report.nodeid.replace("::", "_")+".png" - # 使用全局page对象 - base64_img = screenshot_base64() - if file_name: - html = """ -
- screenshot -
- """ % base64_img - extras.append(pytest_html.extras.html(html)) - report.extras = extras - report.description = str(item.function.__doc__) - -def screenshot_base64(): - """获取当前页面的截图base64编码,使用全局page对象""" - # 生成简洁的截图文件名(年月日时分秒格式) - settings.UI_IMG_PATH.mkdir(parents=True, exist_ok=True) - timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") - screenshot_path = settings.UI_IMG_PATH / f"{timestamp}.png" - # 使用全局page对象 - global GLOBAL_PAGE - page = GLOBAL_PAGE - # 检查page对象是否存在 - if page and hasattr(page, 'screenshot'): - try: - screenshot_bytes = page.screenshot(path=screenshot_path) - logging.info(f"{success} ==> 保存失败截图: {screenshot_path}") - return base64.b64encode(screenshot_bytes).decode('utf-8') - except Exception as e: - logging.error(f"{fail} ==> 截图失败: {str(e)}") - return '' - \ No newline at end of file diff --git a/backend/tests/test_case/ui/test_ui.py b/backend/tests/test_case/ui/test_ui.py deleted file mode 100644 index 8ecd0808..00000000 --- a/backend/tests/test_case/ui/test_ui.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- - -import pytest -from playwright.sync_api import Page -from case.ui.pages.base_page import BasePage -from typing import Dict, Any - - -@pytest.mark.usefixtures('init_session') -class TestUI: - - # 定义测试数据 - TEST_DATA = [{参数化1},{参数化2}] - - @pytest.mark.ui - @pytest.mark.parametrize("test_data", TEST_DATA) - def test_login_scenarios(self, init_session: Page, test_data: Dict[str, Any]): - """参数化测试不同的登录场景""" - emposat_page = BasePage(init_session) - - login_result = emposat_page.login(test_data=test_data) - assert login_result == True, "登录失败" - - logout_result = emposat_page.logout() - assert logout_result == True, "退出登录失败" diff --git a/backend/tests/test_type_conversion.py b/backend/tests/test_type_conversion.py deleted file mode 100644 index 00c5e0ec..00000000 --- a/backend/tests/test_type_conversion.py +++ /dev/null @@ -1,143 +0,0 @@ -""" -类型转换测试。 - -测试MySQL、PostgreSQL、Python和前端TypeScript的代码生成字段类型转换正确性。 -""" - -import pytest -from app.common.constant import GenConstant -from app.config.setting import settings -from app.plugin.module_generator.gencode.tools.gen_util import GenUtils -from app.plugin.module_generator.gencode.tools.jinja2_template_util import Jinja2TemplateUtil - - -def test_mysql_type_conversion(): - """ - 测试MySQL类型转换 - """ - # 保存原始数据库类型 - original_db_type = settings.DATABASE_TYPE - try: - # 设置为MySQL - settings.DATABASE_TYPE = "mysql" - - # 测试MySQL类型 - test_cases = [ - # (数据库类型, 期望的Python类型, 期望的TypeScript类型) - ("INT", "int", "number"), - ("BIGINT", "int", "number"), - ("TINYINT", "int", "number"), - ("TINYINT(1)", "bool", "boolean"), - ("FLOAT", "float", "number"), - ("DOUBLE", "float", "number"), - ("DECIMAL(10,2)", "Decimal", "string"), - ("CHAR(10)", "str", "string"), - ("VARCHAR(255)", "str", "string"), - ("TEXT", "str", "string"), - ("DATE", "date", "string"), - ("TIME", "time", "string"), - ("DATETIME", "datetime", "string"), - ("TIMESTAMP", "datetime", "string"), - ("YEAR", "int", "number"), - ("JSON", "dict", "Record"), - ("ENUM('a','b','c')", "str", "string"), - ] - - for db_type, expected_python_type, expected_ts_type in test_cases: - # 测试数据库类型提取 - extracted_type = GenUtils.get_db_type(db_type) - # 测试Python类型映射 - python_type = GenConstant.DB_TO_PYTHON.get(extracted_type, "str") - assert python_type == expected_python_type, f"MySQL {db_type} -> Python {python_type}, expected {expected_python_type}" - # 测试TypeScript类型映射 - ts_type = Jinja2TemplateUtil.python_type_to_ts_type(python_type) - assert ts_type == expected_ts_type, f"Python {python_type} -> TypeScript {ts_type}, expected {expected_ts_type}" - finally: - # 恢复原始数据库类型 - settings.DATABASE_TYPE = original_db_type - - -def test_postgres_type_conversion(): - """ - 测试PostgreSQL类型转换 - """ - # 保存原始数据库类型 - original_db_type = settings.DATABASE_TYPE - try: - # 设置为PostgreSQL - settings.DATABASE_TYPE = "postgres" - - # 测试PostgreSQL类型 - test_cases = [ - # (数据库类型, 期望的Python类型, 期望的TypeScript类型) - ("INTEGER", "int", "number"), - ("BIGINT", "int", "number"), - ("SMALLINT", "int", "number"), - ("REAL", "float", "number"), - ("DOUBLE PRECISION", "float", "number"), - ("NUMERIC(10,2)", "Decimal", "string"), - ("VARCHAR(255)", "str", "string"), - ("CHAR(10)", "str", "string"), - ("TEXT", "str", "string"), - ("DATE", "date", "string"), - ("TIME", "time", "string"), - ("TIMESTAMP", "datetime", "string"), - ("TIMESTAMP WITH TIME ZONE", "datetime", "string"), - ("BOOLEAN", "bool", "boolean"), - ("JSONB", "dict", "Record"), - ("UUID", "str", "string"), - ("ARRAY[INTEGER]", "list", "unknown[]"), - ("POINT", "list", "unknown[]"), - ] - - for db_type, expected_python_type, expected_ts_type in test_cases: - # 测试数据库类型提取 - extracted_type = GenUtils.get_db_type(db_type) - # 测试Python类型映射 - python_type = GenConstant.DB_TO_PYTHON.get(extracted_type, "str") - assert python_type == expected_python_type, f"PostgreSQL {db_type} -> Python {python_type}, expected {expected_python_type}" - # 测试TypeScript类型映射 - ts_type = Jinja2TemplateUtil.python_type_to_ts_type(python_type) - assert ts_type == expected_ts_type, f"Python {python_type} -> TypeScript {ts_type}, expected {expected_ts_type}" - finally: - # 恢复原始数据库类型 - settings.DATABASE_TYPE = original_db_type - - -def test_special_type_handling(): - """ - 测试特殊类型处理 - """ - # 测试tinyint(1)特殊处理 - assert GenUtils.get_db_type("TINYINT(1)") == "boolean" - - # 测试数组类型处理 - assert GenUtils.get_db_type("INTEGER[]") == "array" - assert GenUtils.get_db_type("TEXT[]") == "array" - - # 测试带COLLATE的类型 - assert GenUtils.get_db_type("VARCHAR(255) COLLATE utf8mb4_unicode_ci") == "varchar" - - # 测试带UNSIGNED的类型 - assert GenUtils.get_db_type("INT UNSIGNED") == "int" - - -def test_sqlalchemy_type_mapping(): - """ - 测试SQLAlchemy类型映射 - """ - # 测试MySQL SQLAlchemy类型 - settings.DATABASE_TYPE = "mysql" - assert Jinja2TemplateUtil.get_sqlalchemy_type("INT") == "Integer" - assert Jinja2TemplateUtil.get_sqlalchemy_type("VARCHAR(255)") == "String(255)" - assert Jinja2TemplateUtil.get_sqlalchemy_type("TINYINT(1)") == "Boolean" - - # 测试PostgreSQL SQLAlchemy类型 - settings.DATABASE_TYPE = "postgres" - assert Jinja2TemplateUtil.get_sqlalchemy_type("INTEGER") == "Integer" - assert Jinja2TemplateUtil.get_sqlalchemy_type("VARCHAR(255)") == "String(255)" - assert Jinja2TemplateUtil.get_sqlalchemy_type("BOOLEAN") == "Boolean" - - -if __name__ == "__main__": - pytest.main(["-v", "tests/test_type_conversion.py"]) diff --git a/backend/tests/test_type_conversion_comprehensive.py b/backend/tests/test_type_conversion_comprehensive.py deleted file mode 100644 index 804faac1..00000000 --- a/backend/tests/test_type_conversion_comprehensive.py +++ /dev/null @@ -1,323 +0,0 @@ -""" -全面的类型转换测试。 - -测试MySQL、PostgreSQL、Python和前端TypeScript的代码生成字段类型转换正确性,覆盖所有支持的类型。 -""" - -import pytest -from app.common.constant import GenConstant -from app.config.setting import settings -from app.plugin.module_generator.gencode.tools.gen_util import GenUtils -from app.plugin.module_generator.gencode.tools.jinja2_template_util import Jinja2TemplateUtil - - -def test_mysql_complete_type_conversion(): - """ - 测试MySQL完整类型转换 - """ - # 保存原始数据库类型 - original_db_type = settings.DATABASE_TYPE - try: - # 设置为MySQL - settings.DATABASE_TYPE = "mysql" - - # 测试所有MySQL类型 - test_cases = [ - # 整数类型 - ("TINYINT", "int", "number"), - ("TINYINT(1)", "bool", "boolean"), - ("TINYINT UNSIGNED", "int", "number"), - ("SMALLINT", "int", "number"), - ("SMALLINT UNSIGNED", "int", "number"), - ("MEDIUMINT", "int", "number"), - ("MEDIUMINT UNSIGNED", "int", "number"), - ("INT", "int", "number"), - ("INT UNSIGNED", "int", "number"), - ("BIGINT", "int", "number"), - ("BIGINT UNSIGNED", "int", "number"), - - # 浮点类型 - ("FLOAT", "float", "number"), - ("DOUBLE", "float", "number"), - ("DECIMAL", "Decimal", "string"), - ("DECIMAL(10,2)", "Decimal", "string"), - ("NUMERIC", "Decimal", "string"), - ("NUMERIC(10,2)", "Decimal", "string"), - - # 字符串类型 - ("CHAR", "str", "string"), - ("CHAR(10)", "str", "string"), - ("VARCHAR", "str", "string"), - ("VARCHAR(255)", "str", "string"), - ("VARCHAR(255) COLLATE utf8mb4_unicode_ci", "str", "string"), - ("TINYTEXT", "str", "string"), - ("TEXT", "str", "string"), - ("MEDIUMTEXT", "str", "string"), - ("LONGTEXT", "str", "string"), - - # 二进制类型 - ("BINARY", "bytes", "string"), - ("BINARY(10)", "bytes", "string"), - ("VARBINARY", "bytes", "string"), - ("VARBINARY(255)", "bytes", "string"), - ("TINYBLOB", "bytes", "string"), - ("BLOB", "bytes", "string"), - ("MEDIUMBLOB", "bytes", "string"), - ("LONGBLOB", "bytes", "string"), - - # 日期时间类型 - ("DATE", "date", "string"), - ("TIME", "time", "string"), - ("DATETIME", "datetime", "string"), - ("TIMESTAMP", "datetime", "string"), - ("YEAR", "int", "number"), - - # 其他类型 - ("JSON", "dict", "Record"), - ("ENUM('a','b','c')", "str", "string"), - ("SET('a','b','c')", "str", "string"), - ("BIT", "int", "number"), - ("BIT(1)", "int", "number"), - - # 空间数据类型 - ("GEOMETRY", "bytes", "string"), - ("POINT", "bytes", "string"), - ("LINESTRING", "bytes", "string"), - ("POLYGON", "bytes", "string"), - ("MULTIPOINT", "bytes", "string"), - ("MULTILINESTRING", "bytes", "string"), - ("MULTIPOLYGON", "bytes", "string"), - ("GEOMETRYCOLLECTION", "bytes", "string"), - ] - - for db_type, expected_python_type, expected_ts_type in test_cases: - # 测试数据库类型提取 - extracted_type = GenUtils.get_db_type(db_type) - # 测试Python类型映射 - - # 特殊处理几何类型,根据数据库类型选择不同的映射 - if extracted_type in ["point", "line", "linestring", "polygon", "multipoint", "multilinestring", "multipolygon", "geometrycollection", "geometry"]: - python_type = "bytes" # MySQL几何类型映射为bytes - else: - python_type = GenConstant.DB_TO_PYTHON.get(extracted_type, "str") - - assert python_type == expected_python_type, f"MySQL {db_type} -> Python {python_type}, expected {expected_python_type}" - # 测试TypeScript类型映射 - ts_type = Jinja2TemplateUtil.python_type_to_ts_type(python_type) - assert ts_type == expected_ts_type, f"Python {python_type} -> TypeScript {ts_type}, expected {expected_ts_type}" - finally: - # 恢复原始数据库类型 - settings.DATABASE_TYPE = original_db_type - - -def test_postgres_complete_type_conversion(): - """ - 测试PostgreSQL完整类型转换 - """ - # 保存原始数据库类型 - original_db_type = settings.DATABASE_TYPE - try: - # 设置为PostgreSQL - settings.DATABASE_TYPE = "postgres" - - # 测试所有PostgreSQL类型 - test_cases = [ - # 整数类型 - ("SMALLINT", "int", "number"), - ("INT2", "int", "number"), - ("INTEGER", "int", "number"), - ("INT4", "int", "number"), - ("BIGINT", "int", "number"), - ("INT8", "int", "number"), - - # 浮点类型 - ("REAL", "float", "number"), - ("DOUBLE PRECISION", "float", "number"), - ("FLOAT8", "float", "number"), - ("NUMERIC", "Decimal", "string"), - ("NUMERIC(10,2)", "Decimal", "string"), - ("DECIMAL", "Decimal", "string"), - ("DECIMAL(10,2)", "Decimal", "string"), - - # 字符串类型 - ("CHAR", "str", "string"), - ("CHAR(10)", "str", "string"), - ("VARCHAR", "str", "string"), - ("VARCHAR(255)", "str", "string"), - ("CHARACTER", "str", "string"), - ("CHARACTER(10)", "str", "string"), - ("CHARACTER VARYING", "str", "string"), - ("CHARACTER VARYING(255)", "str", "string"), - ("TEXT", "str", "string"), - ("CITEXT", "str", "string"), - - # 二进制类型 - ("BYTEA", "bytes", "string"), - - # 日期时间类型 - ("DATE", "date", "string"), - ("TIME", "time", "string"), - ("TIME WITHOUT TIME ZONE", "time", "string"), - ("TIME WITH TIME ZONE", "time", "string"), - ("TIMETZ", "time", "string"), - ("TIMESTAMP", "datetime", "string"), - ("TIMESTAMP WITHOUT TIME ZONE", "datetime", "string"), - ("TIMESTAMP WITH TIME ZONE", "datetime", "string"), - ("TIMESTAMPTZ", "datetime", "string"), - ("INTERVAL", "timedelta", "string"), - - # 布尔类型 - ("BOOLEAN", "bool", "boolean"), - ("BOOL", "bool", "boolean"), - - # JSON类型 - ("JSON", "dict", "Record"), - ("JSONB", "dict", "Record"), - - # 数组类型 - ("INTEGER[]", "list", "unknown[]"), - ("ARRAY[INTEGER]", "list", "unknown[]"), - ("TEXT[]", "list", "unknown[]"), - ("ARRAY[TEXT]", "list", "unknown[]"), - - # 几何类型 - ("POINT", "list", "unknown[]"), - ("LINE", "list", "unknown[]"), - ("LSEG", "list", "unknown[]"), - ("BOX", "list", "unknown[]"), - ("PATH", "list", "unknown[]"), - ("POLYGON", "list", "unknown[]"), - ("CIRCLE", "list", "unknown[]"), - - # 其他类型 - ("UUID", "str", "string"), - ("INET", "str", "string"), - ("CIDR", "str", "string"), - ("MACADDR", "str", "string"), - ("BIT", "int", "number"), - ("BIT VARYING", "int", "number"), - ("VARBIT", "int", "number"), - ("TSVECTOR", "str", "string"), - ("TSQUERY", "str", "string"), - ("XML", "str", "string"), - ("MONEY", "Decimal", "string"), - ("OID", "int", "number"), - ("REGPROC", "str", "string"), - ("REGCLASS", "str", "string"), - ("REGTYPE", "str", "string"), - ("REGROLE", "str", "string"), - ("REGNAMESPACE", "str", "string"), - ("INT2VECTOR", "list", "unknown[]"), - ("OIDVECTOR", "list", "unknown[]"), - ("PG_LSN", "int", "number"), - ("TXID_SNAPSHOT", "str", "string"), - ("PG_NODE_TREE", "str", "string"), - - # 范围类型 - ("INT4RANGE", "list", "unknown[]"), - ("INT8RANGE", "list", "unknown[]"), - ("TSRANGE", "list", "unknown[]"), - ("TSTZRANGE", "list", "unknown[]"), - ("DATERANGE", "list", "unknown[]"), - ] - - for db_type, expected_python_type, expected_ts_type in test_cases: - # 测试数据库类型提取 - extracted_type = GenUtils.get_db_type(db_type) - # 测试Python类型映射 - - # 特殊处理几何类型,根据数据库类型选择不同的映射 - if extracted_type in ["point", "line", "lseg", "box", "path", "polygon", "circle"]: - python_type = "list" # PostgreSQL几何类型映射为list - else: - python_type = GenConstant.DB_TO_PYTHON.get(extracted_type, "str") - - assert python_type == expected_python_type, f"PostgreSQL {db_type} -> Python {python_type}, expected {expected_python_type}" - # 测试TypeScript类型映射 - ts_type = Jinja2TemplateUtil.python_type_to_ts_type(python_type) - assert ts_type == expected_ts_type, f"Python {python_type} -> TypeScript {ts_type}, expected {expected_ts_type}" - finally: - # 恢复原始数据库类型 - settings.DATABASE_TYPE = original_db_type - - -def test_sqlalchemy_type_comprehensive(): - """ - 测试SQLAlchemy类型映射的完整性 - """ - # 测试MySQL SQLAlchemy类型 - settings.DATABASE_TYPE = "mysql" - mysql_test_cases = [ - ("INT", "Integer"), - ("BIGINT", "BigInteger"), - ("TINYINT", "SmallInteger"), - ("TINYINT(1)", "Boolean"), - ("FLOAT", "Float"), - ("DOUBLE", "Float"), - ("DECIMAL", "DECIMAL"), - ("DECIMAL(10,2)", "DECIMAL(10,2)"), - ("CHAR", "CHAR(255)"), - ("CHAR(10)", "CHAR(10)"), - ("VARCHAR", "String(255)"), - ("VARCHAR(255)", "String(255)"), - ("TEXT", "Text"), - ("DATE", "Date"), - ("TIME", "Time"), - ("DATETIME", "DateTime"), - ("TIMESTAMP", "TIMESTAMP"), - ("YEAR", "Integer"), - ("JSON", "JSON"), - ("ENUM('a','b','c')", "Enum"), - ] - - for db_type, expected_sqlalchemy_type in mysql_test_cases: - sqlalchemy_type = Jinja2TemplateUtil.get_sqlalchemy_type(db_type) - assert sqlalchemy_type == expected_sqlalchemy_type, f"MySQL {db_type} -> SQLAlchemy {sqlalchemy_type}, expected {expected_sqlalchemy_type}" - - # 测试PostgreSQL SQLAlchemy类型 - settings.DATABASE_TYPE = "postgres" - postgres_test_cases = [ - ("INTEGER", "Integer"), - ("BIGINT", "BigInteger"), - ("SMALLINT", "SmallInteger"), - ("REAL", "Float"), - ("DOUBLE PRECISION", "Float"), - ("NUMERIC", "Numeric"), - ("NUMERIC(10,2)", "Numeric(10,2)"), - ("VARCHAR", "String(255)"), - ("VARCHAR(255)", "String(255)"), - ("TEXT", "Text"), - ("DATE", "Date"), - ("TIME", "Time"), - ("TIMESTAMP", "DateTime"), - ("BOOLEAN", "Boolean"), - ("JSONB", "JSONB"), - ("UUID", "Uuid"), - ("BYTEA", "LargeBinary"), - ] - - for db_type, expected_sqlalchemy_type in postgres_test_cases: - sqlalchemy_type = Jinja2TemplateUtil.get_sqlalchemy_type(db_type) - assert sqlalchemy_type == expected_sqlalchemy_type, f"PostgreSQL {db_type} -> SQLAlchemy {sqlalchemy_type}, expected {expected_sqlalchemy_type}" - - -def test_edge_cases(): - """ - 测试边缘情况 - """ - # 测试空类型 - assert Jinja2TemplateUtil.python_type_to_ts_type(None) == "string" - assert Jinja2TemplateUtil.python_type_to_ts_type("") == "string" - assert Jinja2TemplateUtil.python_type_to_ts_type(" ") == "string" - - # 测试未知类型 - assert Jinja2TemplateUtil.python_type_to_ts_type("unknown_type") == "string" - - # 测试大小写不敏感 - assert GenUtils.get_db_type("VARCHAR(255)") == "varchar" - assert GenUtils.get_db_type("tinyint(1)") == "boolean" - assert GenUtils.get_db_type("array[integer]") == "array" - - -if __name__ == "__main__": - pytest.main(["-v", "tests/test_type_conversion_comprehensive.py"]) diff --git a/backend/tests/tools/__init__.py b/backend/tests/tools/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/tests/tools/dingding_tool.py b/backend/tests/tools/dingding_tool.py deleted file mode 100644 index 9ac938c0..00000000 --- a/backend/tests/tools/dingding_tool.py +++ /dev/null @@ -1,101 +0,0 @@ -# -*- coding: utf-8 -*-# - -# 钉钉发送消息模块 -from typing import Any -from dingtalkchatbot.chatbot import DingtalkChatbot, FeedLink - -class DingTalkPack: - - def __init__(self, webhook: str, secret: str, at_mobiles: list = None): - - self.webhook=webhook - self.secret=secret - self.at_mobiles=at_mobiles - - self.ding_news = DingtalkChatbot(webhook=self.webhook, secret=self.secret, pc_slide=False, fail_notice=False) - - def send_text(self, msg: str, mobiles: list = None) -> None: - """ - 发送文本信息 - :param msg: 文本内容 - :param mobiles: 用户电话 - :return: - """ - if not mobiles: - self.ding_news.send_text(msg=msg, is_at_all=True) - else: - if isinstance(mobiles, list): - self.ding_news.send_text(msg=msg, at_mobiles=mobiles) - else: - raise TypeError("mobiles类型错误 不是list类型.") - - def send_link(self, title: str, text: str, message_url: str, pic_url: str) -> None: - """ - 发送link通知 - :return: - """ - try: - self.ding_news.send_link(title=title, text=text, message_url=message_url, pic_url=pic_url) - except Exception: - raise - - def send_markdown(self, title: str, msg: str, mobiles: list = None) -> None: - """ - :param mobiles: - :param title: - :param msg: - markdown 格式 - """ - if mobiles is None: - self.ding_news.send_markdown(title=title, text=msg, is_at_all=True) - else: - if isinstance(mobiles, list): - self.ding_news.send_markdown(title=title, text=msg, at_mobiles=mobiles) - else: - raise TypeError("mobiles类型错误 不是list类型.") - - def feed_link(self, title: str, message_url: str, pic_url: str) -> Any: - """ - 发送link类型 - :param title: - :param message_url: - :param pic_url: - :return: - """ - return FeedLink(title=title, message_url=message_url, pic_url=pic_url) - - def send_feed_link(self, *arg) -> None: - """ - 发送feedlink类型 - :param arg: - :return: - """ - try: - self.ding_news.send_feed_card(list(arg)) - except Exception: - raise - - def send_dingding(self, title, environment, tester, total, pass_num, fail_num, error_num, skip_num, rate, duration, reprot_url, jenkins_url): - """ - 发送钉钉通知 - :return: - """ - self.ding_news.send_markdown( - title=f'【{title}测试执行完毕提醒', - text=f"[通知] 测试执行完成 \n\n --- \n\n" + - "执行环境:%s " % environment + "\n\n" + - "执行人员:@%s" % tester + "\n\n --- \n\n" + - "运行总数:%s " % total + "\n\n" + - "通过数量:%s " % pass_num + "\n\n" + - "失败数量:%s " % fail_num + "\n\n" + - "异常数量:%s " % error_num + "\n\n" + - "跳过数量:%s " % skip_num + "\n\n" + - "成功率为:%s " % rate + "\n\n" + - "运行时间:%s " % duration + "\n\n" + - "报告详情:[点击查看](%s)" % reprot_url + "\n\n" + - "构建地址:[点击查看](%s) 详细情况可登录jenkins平台查看,非相关负责人员可忽略此消息。谢谢。" % jenkins_url , - at_mobiles=self.at_mobiles - ) - - - diff --git a/backend/tests/tools/email_tool.py b/backend/tests/tools/email_tool.py deleted file mode 100644 index 5ad0fccb..00000000 --- a/backend/tests/tools/email_tool.py +++ /dev/null @@ -1,89 +0,0 @@ -# -*- coding: utf-8 -*- - -import smtplib -import time -from email.header import Header -from email.mime.application import MIMEApplication -from email.mime.multipart import MIMEMultipart -from email.mime.text import MIMEText -import logging - - -class EmailPack: - # 初始化发件人,密码,收件人列表 - def __init__(self, fromaddr: str, password: str, toaddrs: list, server_host: str): - """ - :param REPORT_END_PATH: - """ - self.fromaddr = fromaddr - self.password = password - self.toaddrs = toaddrs - self.server_host = server_host - - self.server = smtplib.SMTP(self.server_host) - self.message = MIMEMultipart() - - # 设置发件人名称,主题,内容,附件 - def _set_message(self, name: str, title: str, content: str, filelist: list): - """ - :param name: - :param title: - :param content: - :param filelist: - :return: - """ - tm = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) - self.message['From'] = Header(f"{name}<{self.fromaddr}>", 'utf-8') # 发件人名称和地址 - self.message['Subject'] = Header(title + "_" + tm, 'utf-8') # 邮件主题 - self.message.attach(MIMEText(content)) # 邮件内容 - if filelist is not None: # 邮件附件 - for file in filelist: - fileApart = MIMEApplication( open(file, 'rb').read(), file.split('.')[-1]) - fileApart.add_header('Content-Disposition', 'attachment', filename=file.split("\\")[-1]) - self.message.attach(fileApart) - - # 发送邮件 - def _send_message(self): - """ - :return: - """ - try: - self.server.login(self.fromaddr, self.password) - self.server.sendmail(self.fromaddr, self.toaddrs, self.message.as_string()) - logging.info(f'【邮件发送成功!收件人:{self.toaddrs}】') - self.server.quit() - except smtplib.SMTPException as e: - logging.error(f'【邮件发送异常!{e}】') - - # 默认发送邮件 - def send_default_email(self, title: str, environment: str, tester: str, total: int, pass_num: int, fail_num: int, error_num: int, skip_num: int, rate: str, duration: str, reprot_url: str, jenkins_url: str, report_path: list): - """ - :rtype: object - :return: - """ - - self._set_message( - name="自动化测试", - title=f'{title}测试执行完毕提醒!', - content=f''' - 各位同事, 大家好: - 自动化用例执行完成,执行结果如下: - ********************************** - 执行环境: {environment} - 执行人员: {tester} - 运行总数: {total} - 通过: {pass_num} - 失败: {fail_num} - 异常: {error_num} - 跳过: {skip_num} - 成功率: {rate} - 总耗时: {duration} - 报告详情: [点击查看]({reprot_url}) - ********************************** - jenkins地址:{jenkins_url} - 详细情况可登录jenkins平台查看,非相关负责人员可忽略此消息。谢谢。 - ''', - filelist=[report_path] - ) - self._send_message() - diff --git a/backend/tests/tools/excel_tool.py b/backend/tests/tools/excel_tool.py deleted file mode 100644 index 2f543d81..00000000 --- a/backend/tests/tools/excel_tool.py +++ /dev/null @@ -1,118 +0,0 @@ -# -*- coding: utf-8 -*- - -import json -from typing import Dict, List, Optional -import pandas as pd -from openpyxl.styles import Font -from openpyxl import load_workbook -import logging - -class ExcelPack: - # Excel列结构配置 - COLUMNS = { - 'name': 0, - 'run': 1, - 'token': 2, - 'url': 3, - 'data': 4, - 'method': 5, - 'type': 6, - 'header': 7, - 'code': 8, - 'isSuccess': 9, - 'result': 10, - } - - # 结果样式配置 - STYLES = { - 'pass': (3, '00FF00'), # 绿色 - 'fail': (4, 'FF0000'), # 红色 - 'skip': (6, '800080'), # 紫色 - } - - def __init__(self, file_name: str, api_host: str, token: str): - self.file_name = file_name - self._df = None - self.api_host = api_host - self.token = token - - @property - def df(self) -> pd.DataFrame: - if self._df is None: - self._df = pd.read_excel(self.file_name) - logging.info(f"成功读取Excel文件: {self.file_name}") - return self._df - - def _get_cell_value(self, row: int, col_name: str) -> str: - """统一的单元格获取方法""" - value = self.df.iloc[row, self.COLUMNS[col_name]] - return str(value).strip() if pd.notna(value) else '' - - def _write_cell(self, row: int, col_name: str, value: str, style: Optional[int] = None): - """增量更新单元格内容,保留其他数据格式""" - - # 更新DataFrame中的值 - col = self.COLUMNS[col_name] - self.df.iloc[row, col] = value - - # 直接加载工作簿进行更新 - workbook = load_workbook(self.file_name) - worksheet = workbook.active - - # 写入新值 - cell = worksheet.cell(row=row + 2, column=col + 1) - cell.value = value - - # 应用样式 - if style and isinstance(style, (int, str)): - style_config = self.STYLES.get(str(style)) - if style_config and len(style_config) >= 2: - color = style_config[1] - cell.font = Font(bold=True, color=color) - else: - logging.warning(f"无效的样式配置: {style}") - - # 保存工作簿 - workbook.save(self.file_name) - - def load_test_cases(self) -> List[Dict]: - """加载测试用例""" - test_cases = [] - - for row in range(1, len(self.df)): - headers = eval(self._get_cell_value(row, 'header')) if self._get_cell_value(row, 'header') else {} - if self._get_cell_value(row, 'token').lower() == 'yes': - headers['token'] = self.token - - case = { - 'name': self._get_cell_value(row, 'name'), - 'run': self._get_cell_value(row, 'run'), - 'token': self._get_cell_value(row, 'token'), - 'type': self._get_cell_value(row, 'type'), - 'request': { - 'url': f"{self.api_host}{self._get_cell_value(row, 'url')}", - 'method': self._get_cell_value(row, 'method'), - 'headers': headers, - 'data': json.loads(self._get_cell_value(row, 'data')) if self._get_cell_value(row, 'data') else None, - }, - 'expected': { - 'code': int(self._get_cell_value(row, 'code')), - 'isSuccess': bool(self._get_cell_value(row, 'isSuccess')), - }, - 'result': { - 'result': self._get_cell_value(row, 'result'), - }, - 'row': row, - } - test_cases.append(case) - return test_cases - - def write_to_excel(self, row: int, result: Dict) -> None: - """写入结果到Excel""" - try: - # 更新数据 - self._write_cell(row, 'result', result['result'], result['result']) - logging.info(f"测试结果写入成功: {result['name']}") - except Exception as e: - logging.error(f"写入测试结果失败: {str(e)}") - logging.exception(e) diff --git a/backend/tests/tools/manage.py b/backend/tests/tools/manage.py deleted file mode 100644 index 6fae2061..00000000 --- a/backend/tests/tools/manage.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- - -import logging -from typing import Dict - -from common.reponse_tool import Response -from common.request_tool import Requests - - -class Manage: - - def __init__(self): - pass - - - def create_result(self, name: str, status: str = 'skip', response: str = None) -> Dict: - """创建统一的结果对象""" - return { - 'name': name, - 'result': status, - 'response': response - } - - def execute_test_case(self, case: Dict) -> Dict: - """执行测试用例""" - try: - # 构建请求数据 - request_data = { - 'url': case['request']['url'], - 'method': case['request']['method'], - 'data': case['request']['data'], - 'headers': case['request']['headers'], - 'data_type': case.get('type', 'json') - } - - # 发送请求并获取响应 - response = Response().result(Requests().send_request(**request_data)) - - # 断言 - # 情况1:如果响应体包含'isSuccess'字段(按预期格式) - if case['expected']['isSuccess'] and case['expected']['code'] == response['code']: - status = 'pass' - else: - status = 'fail' - - result = self.create_result(case['name'], status, response) - logging.info(f"用例执行完成: {case['name']}, 结果: {status}") - - except Exception as e: - error_msg = f"执行用例异常: {str(e)}" - logging.error(error_msg) - result = self.create_result(case['name'], 'fail', error_msg) - - return result - - diff --git a/backend/tests/tools/qiwei_tool.py b/backend/tests/tools/qiwei_tool.py deleted file mode 100644 index adccb944..00000000 --- a/backend/tests/tools/qiwei_tool.py +++ /dev/null @@ -1,182 +0,0 @@ - - -# -*- coding: utf-8 -*-# - -# 企业微信发送消息模块 -import requests -import json -from typing import Any, List, Optional - - -class QiWeiPack: - """ - 企业微信机器人消息发送封装类 - """ - - def __init__(self, webhook_url: str): - """ - 初始化企业微信机器人 - :param webhook_url: 企业微信机器人的webhook地址 - """ - self.webhook_url = webhook_url - self.headers = {"Content-Type": "application/json"} - - def _send_request(self, data: dict) -> dict: - """ - 发送请求到企业微信API - :param data: 消息数据 - :return: 响应结果 - """ - try: - response = requests.post( - self.webhook_url, - data=json.dumps(data), - headers=self.headers - ) - response.raise_for_status() - return response.json() - except Exception as e: - raise Exception(f"发送企业微信消息失败: {str(e)}") - - def send_text(self, content: str, mentioned_list: Optional[List[str]] = None, mentioned_mobile_list: Optional[List[str]] = None) -> dict: - """ - 发送文本消息 - :param content: 文本内容 - :param mentioned_list: @的用户列表,如["user1", "user2"] - :param mentioned_mobile_list: @的手机号列表,如["13800138000"] - :return: 响应结果 - """ - data = { - "msgtype": "text", - "text": { - "content": content - } - } - - if mentioned_list: - data["text"]["mentioned_list"] = mentioned_list - if mentioned_mobile_list: - data["text"]["mentioned_mobile_list"] = mentioned_mobile_list - - return self._send_request(data) - - def send_markdown(self, content: str) -> dict: - """ - 发送markdown消息 - :param content: markdown内容 - :return: 响应结果 - """ - data = { - "msgtype": "markdown", - "markdown": { - "content": content - } - } - return self._send_request(data) - - def send_image(self, base64: str, md5: str) -> dict: - """ - 发送图片消息 - :param base64: 图片base64编码 - :param md5: 图片md5值 - :return: 响应结果 - """ - data = { - "msgtype": "image", - "image": { - "base64": base64, - "md5": md5 - } - } - return self._send_request(data) - - def send_news(self, articles: List[dict]) -> dict: - """ - 发送图文消息 - :param articles: 图文列表,每个元素包含title、description、url、picurl字段 - :return: 响应结果 - """ - data = { - "msgtype": "news", - "news": { - "articles": articles - } - } - return self._send_request(data) - - def send_file(self, media_id: str) -> dict: - """ - 发送文件消息 - :param media_id: 文件媒体ID(需要先上传文件获取) - :return: 响应结果 - """ - data = { - "msgtype": "file", - "file": { - "media_id": media_id - } - } - return self._send_request(data) - - def send_taskcard(self, title: str, description: str, url: str, btn_json_list: List[dict]) -> dict: - """ - 发送任务卡片消息 - :param title: 标题 - :param description: 描述 - :param url: 点击卡片跳转的URL - :param btn_json_list: 按钮列表 - :return: 响应结果 - """ - data = { - "msgtype": "taskcard", - "taskcard": { - "title": title, - "description": description, - "url": url, - "btn_json_list": btn_json_list - } - } - return self._send_request(data) - - def send_test_report(self, title: str, environment: str, tester: str, total: int, pass_num: int, - fail_num: int, error_num: int, skip_num: int, rate: str, duration: str, - report_url: str, jenkins_url: str) -> dict: - """ - 发送测试报告通知 - :param title: 测试标题 - :param environment: 执行环境 - :param tester: 执行人员 - :param total: 运行总数 - :param pass_num: 通过数量 - :param fail_num: 失败数量 - :param error_num: 异常数量 - :param skip_num: 跳过数量 - :param rate: 成功率 - :param duration: 运行时间 - :param report_url: 报告地址 - :param jenkins_url: Jenkins地址 - :return: 响应结果 - """ - # 企业微信markdown支持的颜色有限,这里使用emoji和加粗来突出显示 - content = ( - f"## {title}测试执行完毕提醒\n\n" + - "📢 **测试执行完成**\n\n" + - "---\n\n" + - f"**执行环境:** {environment}\n\n" + - f"**执行人员:** {tester}\n\n" + - "---\n\n" + - f"**运行总数:** {total}\n\n" + - f"**通过数量:** ✅ {pass_num}\n\n" + - f"**失败数量:** ❌ {fail_num}\n\n" + - f"**异常数量:** ⚠️ {error_num}\n\n" + - f"**跳过数量:** ⏭️ {skip_num}\n\n" + - f"**成功率:** {rate}\n\n" + - f"**运行时间:** {duration}\n\n" + - f"**报告详情:** [点击查看]({report_url})\n\n" + - f"**构建地址:** [点击查看]({jenkins_url})\n\n" + - "详细情况可登录Jenkins平台查看,非相关负责人员可忽略此消息。谢谢。" - ) - - return self.send_markdown(content) - - \ No newline at end of file diff --git a/backend/tests/tools/random_tool.py b/backend/tests/tools/random_tool.py deleted file mode 100644 index 1d37e7e0..00000000 --- a/backend/tests/tools/random_tool.py +++ /dev/null @@ -1,281 +0,0 @@ -# -*- coding: utf-8 -*- - -import string -import random -import datetime -from faker import Faker -from dateutil.relativedelta import relativedelta - - -class ContextPack: - def __init__(self): - self.faker = Faker(locale='zh_CN') - - @property - def get_phone(self) -> int: - """ - :return: 随机生成手机号码 - """ - phone = self.faker.phone_number() - return phone - - @property - def get_id_number(self) -> int: - """ - - :return: 随机生成身份证号码 - """ - - id_number = self.faker.ssn() - return id_number - - @property - def get_female_name(self) -> str: - """ - - :return: 女生姓名 - """ - female_name = self.faker.name_male() - return female_name - - @property - def get_male_name(self) -> str: - """ - - :return: 男生姓名 - """ - male_name = self.faker.name_female() - return male_name - - @property - def get_email(self) -> str: - """ - - :return: 生成邮箱 - """ - email = self.faker.email() - return email - - @property - def merchantSelfOperatedShop(self) -> int: - """ - - :return: 商家端自营店铺ID - """ - SelfOperatedShop = 515 - return SelfOperatedShop - - @property - def get_secend_time(self)->datetime: - """ - 计算当前时间: 年-月-日 时:分:秒:毫秒 - :return: - """ - return datetime.datetime.now() - - @property - def get_day_time(self)->str: - """ - 计算当前时间: 年-月-日 - :return: - """ - return datetime.datetime.now().strftime('%Y-%m-%d') - - def generate_bank_card(self): - """ - 生成随机银行卡号 - """ - return self.faker.credit_card_number() - - def generate_email(self): - """ - 生成随机邮箱号 - """ - return self.random_str(4) + self.faker.free_email() - - @property - def generate_company(self): - """ - 生成随机公司名称 - """ - return self.faker.company_prefix() + self.random_str(5) + '测试' + self.faker.company_suffix() - - @property - def generate_name(self): - """ - 生成随机姓名 - """ - return self.faker.name() - - @property - def generate_unreal_phone(self): - """ - 生成不存在的手机号 - """ - phone = random.choice(['100', '110', '120']) + \ - ''.join(random.choice('0123456789') for _ in range(8)) - return phone - - def random_str(self, str_len: int) -> str: - """从a-zA-Z0-9生成制定数量的随机字符 - - :param str_len: 字符串长度 - :return: - """ - try: - str_len = int(str_len) - except ValueError: - raise Exception("调用随机字符失败,[ %s ]长度参数有误!" % str_len) - strings = ''.join(random.sample(string.hexdigits, +str_len)) - return strings - - def random_int(self, scope) -> int: - """获取随机整型数据 - - :param scope: 数据范围 - :return: - """ - try: - start_num, end_num = scope.split(",") - start_num = int(start_num) - end_num = int(end_num) - except ValueError: - raise Exception("调用随机整数失败,[ %s ]范围参数有误!" % str(scope)) - if start_num <= end_num: - number = random.randint(start_num, end_num) - else: - number = random.randint(end_num, start_num) - return number - - def random_float(self, data) -> float: - """获取随机浮点数据 - - :param data: 数组 - :return: - """ - try: - start_num, end_num, accuracy = data.split(",") - start_num = int(start_num) - end_num = int(end_num) - accuracy = int(accuracy) - except ValueError: - raise Exception("调用随机浮点数失败,[ %s ]范围参数或精度有误!" % data) - - if start_num <= end_num: - number = random.uniform(start_num, end_num) - else: - number = random.uniform(end_num, start_num) - number = round(number, accuracy) - return number - - def random_choice(self, data): - """获取数组随机值 - - :param data: 数组 - :return: - """ - _list = data.split(",") - each = random.choice(_list) - return each - - def get_date_mark(self, now, mark, num): - if 'y' == mark: - return now + relativedelta(years=num) - elif 'm' == mark: - return now + relativedelta(months=num) - elif 'd' == mark: - return now + relativedelta(days=num) - elif 'h' == mark: - return now + relativedelta(hours=num) - elif 'M' == mark: - return now + relativedelta(minutes=num) - elif 's' == mark: - return now + relativedelta(seconds=num) - else: - raise Exception("日期字段标识[ %s ]错误, 请使用[年y,月m,日d,时h,分M,秒s]标识!" % mark) - - def generate_date(self, expr=''): - """生成日期对象(不含时分秒) - - :param expr: 日期表达式,如"d-1"代表日期减1 - :return: - """ - today = datetime.date.today() - if expr: - try: - mark = expr[:1] - num = int(expr[1:]) - except (TypeError, NameError): - raise Exception("调用生成日期失败,日期表达式[ %s ]有误!" % expr) - return self.get_date_mark(today, mark, num) - else: - return today - - def generate_datetime(self, expr=''): - """生成日期时间对象(含时分秒) - - :param expr: 日期表达式,如"d-1"代表日期减1 - :return: - """ - now = datetime.datetime.now().replace(microsecond=0) - if expr: - try: - mark = expr[:1] - num = int(expr[1:]) - except (TypeError, NameError): - raise Exception("调用生成日期失败,日期表达式[ %s ]有误!" % expr) - return self.get_date_mark(now, mark, num) - else: - return now - - def generate_timestamp(self, expr='') -> int: - """生成时间戳(13位) - - :param expr: 日期表达式,如"d-1"代表日期减1 - :return: - """ - datetime_obj = self.generate_datetime(expr) - return int(datetime.datetime.timestamp(datetime_obj)) * 1000 - - def generate_guid(self) -> str: - """基于MAC地址+时间戳+随机数来生成GUID - - :param: - :return: - """ - import uuid - return str(uuid.uuid1()).upper() - - def generate_wxid(self): - """基于AUTO标识+26位英文字母大小写+数字生成伪微信ID - - :param: - :return: - """ - return 'AUTO' + ''.join(random.sample(string.ascii_letters + string.digits, 24)) - - def generate_noid(self, expr=''): - """基于6位随机数字+出生日期+4位随机数生成伪身份证 - - :param expr: 日期表达式,如"d-1"代表日期减1 - :return: - """ - # birthday = generate_date(expr) - # birthday = str(birthday).replace('-', '') - # return int(str(random.randint(100000, 999999)) + birthday + str(random.randint(1000, 9999))) - faker = Faker(locale='zh_CN') - return faker.ssn() - - def generate_phone(self): - """基于三大运营商号段+随机数生成伪手机号 - - :param: - :return: - """ - ctcc = [133, 153, 173, 177, 180, 181, 189, 191, 193, 199] - cucc = [130, 131, 132, 155, 156, 166, 175, 176, 185, 186, 166] - cmcc = [134, 135, 136, 137, 138, 139, 147, 150, 151, 152, 157, 158, 159, 172, 178, 182, 183, 184, 187, 188, 198] - begin = 10 ** 7 - end = 10 ** 8 - 1 - prefix = random.choice(ctcc + cucc + cmcc) - return str(prefix) + str(random.randint(begin, end)) diff --git a/backend/tests/tools/yaml_tool.py b/backend/tests/tools/yaml_tool.py deleted file mode 100644 index caf7c0a5..00000000 --- a/backend/tests/tools/yaml_tool.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- - -from typing import Dict, List -import yaml -from string import Template -import logging - -class YamlPack: - # yaml文件全部内容 - def __init__(self, yaml_path, api_host: str, token: str): - # yaml中的变量 - variable = { - 'host': api_host, - 'token': token - } - self.pass_num = 0 - self.fail_num = 0 - self.yaml_path = yaml_path - with open(self.yaml_path, 'r', encoding="utf-8") as f: - re = Template(f.read()).substitute(variable) - self.data = yaml.safe_load(re) - logging.info(f"成功读取Yaml文件: {self.yaml_path}") - - # Test目录下的全部用例 - def load_test_cases(self) -> List[Dict]: - test_list = [] - case = self.data["Case"] - for a in case: - for k, v in a.items(): - if k == "Test": - test_list.append(v) - return test_list -