pytest-platform-adapter 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 blackyau
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,243 @@
1
+ Metadata-Version: 2.2
2
+ Name: pytest_platform_adapter
3
+ Version: 1.0.0
4
+ Summary: Pytest集成自动化平台插件
5
+ Author-email: BlackYau <blackyau426@gmail.com>
6
+ Maintainer-email: BlackYau <blackyau426@gmail.com>
7
+ License: MIT
8
+ Project-URL: Homepage, https://github.com/blackyau/pytest-platform-adapter
9
+ Project-URL: Repository, https://github.com/blackyau/pytest-platform-adapter.git
10
+ Project-URL: Issues, https://github.com/blackyau/pytest-platform-adapter/issues
11
+ Keywords: pytest,platform,adapter,自动化平台,插件
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Environment :: Plugins
14
+ Classifier: Framework :: Pytest
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Natural Language :: Chinese (Simplified)
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.6
20
+ Classifier: Programming Language :: Python :: 3.7
21
+ Classifier: Programming Language :: Python :: 3.8
22
+ Classifier: Programming Language :: Python :: 3.9
23
+ Classifier: Programming Language :: Python :: 3.10
24
+ Classifier: Programming Language :: Python :: 3.11
25
+ Classifier: Topic :: Software Development
26
+ Classifier: Topic :: Software Development :: Testing
27
+ Classifier: Topic :: Software Development :: Testing :: Acceptance
28
+ Classifier: Topic :: Software Development :: Libraries
29
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
30
+ Requires-Python: >=3.6
31
+ Description-Content-Type: text/markdown
32
+ License-File: LICENSE
33
+ Requires-Dist: pytest>=6.2.5
34
+ Requires-Dist: allure-pytest>=2.9.45
35
+
36
+ # pytest-platform-adapter
37
+
38
+ [![Release
39
+ Status](https://img.shields.io/pypi/v/pytest-platform-adapter)](https://pypi.python.org/pypi/pytest-platform-adapter)
40
+ [![Downloads](https://img.shields.io/pypi/dm/pytest-platform-adapter)](https://pypi.python.org/pypi/pytest-platform-adapter)
41
+ [![Build](https://github.com/blackyau/pytest-platform-adapter/actions/workflows/deploy.yml/badge.svg)](https://github.com/allure-framework/allure-python/actions/workflows/build.yaml)
42
+
43
+ Pytest集成自动化平台插件
44
+
45
+ ## 功能
46
+
47
+ - 根据用例 `allure.title` 中的 ID 筛选执行的用例
48
+ - 跳过所有用例的执行,快速生成 Allure 报告
49
+ - 周期性的通过 RESTApi 上报执行用例的整体进度
50
+
51
+ ## 环境依赖
52
+
53
+ - Python >= 3.6
54
+ - pytest >= 7.1.2
55
+ - allure-pytest >= 2.9.45
56
+
57
+ ## 安装
58
+
59
+ 执行以下命令即可完成安装
60
+
61
+ ```shell
62
+ pip install pytest-platform-adapter
63
+ ```
64
+
65
+ ## 使用方法
66
+
67
+ ### 根据 Allure title 中的 ID 筛选用例
68
+
69
+ 假设有以下测试用例
70
+
71
+ ```python
72
+ import allure
73
+
74
+ @allure.title('19936-测试用例1')
75
+ def test_case1():
76
+ assert True
77
+
78
+ @allure.title('19930-测试用例2')
79
+ def test_case2():
80
+ assert True
81
+
82
+ @allure.title('19939-测试用例3')
83
+ def test_case3():
84
+ assert True
85
+ ```
86
+
87
+ 该插件提供两种方式来筛选测试用例:
88
+
89
+ 1. 通过命令行参数指定测试ID:
90
+ ```bash
91
+ pytest tests/ -v --case-ids="19936,19930"
92
+ ```
93
+
94
+ 2. 通过文件指定测试ID:
95
+ ```bash
96
+ pytest tests/ -v --case-ids-file="test_ids.txt"
97
+ ```
98
+
99
+ 其中 test_ids.txt 的内容格式如下:
100
+ ```
101
+ 19936
102
+ 19930
103
+ ```
104
+
105
+ ### 跳过所有用例
106
+
107
+ 使用 `--scan` 参数,启用扫描模式。在扫描模式下,所有用例都会被 skip 不会执行,可以用于快速生成 Allure 报告。使用方法如下:
108
+
109
+ ```shell
110
+ pytest --scan --alluredir allure-results
111
+ ```
112
+
113
+ ### 将执行进展通过RESTApi回报
114
+
115
+ 在 pytest.ini 中配置以下内容
116
+
117
+ ```ini
118
+ [pytest]
119
+ platform_ip = 127.0.0.1
120
+ platform_port = 8080
121
+ platform_path = /api/autoplatform/task/refresh_data_count
122
+ platform_use_https = False
123
+ ```
124
+
125
+ 执行用例的过程中(前 10 个用例的时候执行完就立刻回报,之后就是每隔 10 个用例才回报一次) 会将测试用例的整体进展以 `POST` 的方式回报给 `http://127.0.0.1:8080/api/autoplatform/task/refresh_data_count` 请求体如下:
126
+
127
+ ```json
128
+ {
129
+ "pipeline": "JOB_NAME",
130
+ "build_number": "BUILD_NUMBER",
131
+ "passed_case_count": 29,
132
+ "skipped_case_count": 1,
133
+ "failed_case_count": 0,
134
+ "selected_case": 100
135
+ }
136
+ ```
137
+
138
+ 其中 `JOB_NAME` 和 `BUILD_NUMBER` 是通过环境变量获取的。
139
+
140
+
141
+ ## 调试方法
142
+
143
+ ### 部署虚拟环境
144
+
145
+ 新建一个虚拟环境,建议使用 Python 3.11.9 。
146
+
147
+ 安装依赖
148
+
149
+ ```shell
150
+ pip install -r requirements.txt
151
+ ```
152
+
153
+ 在项目根目录(与pyproject.toml同一路径)执行以下命令安装本插件
154
+
155
+ ```shell
156
+ pip install -e .
157
+ ```
158
+
159
+ 执行 `pytest --help` 检查返回的内容是否包含以下文本,如果有的话就说明安装成功了
160
+
161
+ ```
162
+ usage: pytest [options] [file_or_dir] [file_or_dir] [...]
163
+
164
+ positional arguments:
165
+ file_or_dir
166
+
167
+ general:
168
+
169
+ ..... 省略中间
170
+
171
+ 自动化平台插件:
172
+ --case_ids=CASE_IDS 要执行的测试用例ID列表,使用逗号分隔,例如:19936,19930
173
+ --case_ids_file=CASE_IDS_FILE
174
+ 包含测试用例ID的文件路径,文件中每行一个ID
175
+ --scan 扫描模式:快速生成 Allure 报告而不实际执行测试
176
+
177
+ [pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg|pyproject.toml file found:
178
+
179
+ ..... 省略中间
180
+
181
+ platform_ip (string): 自动化平台IP
182
+ platform_port (string):
183
+ 自动化平台端口
184
+
185
+ ```
186
+
187
+ ### 创建测试用例
188
+
189
+ 在项目根目录新建 tests 目录,然后在 tests 目录中新建以 test_ 开头的 py 文件,作为测试用例。
190
+
191
+ 例如新建 `test_aaa.py`,在文件中写入以下内容
192
+
193
+ ```python
194
+ import allure
195
+
196
+
197
+ class TestAbc:
198
+ @allure.title('19936-用例1')
199
+ def test_abc(self):
200
+ print('test_abc 用例执行中')
201
+
202
+ # @pytest.mark.xfail(reason='跳过')
203
+ class TestAbd:
204
+ @allure.title('19930-用例2')
205
+ def test_abd(self):
206
+ with allure.step('步骤1'):
207
+ pass
208
+
209
+ # @pytest.mark.xfail(reason='跳过')
210
+ class TestAbe:
211
+ @allure.title('19932-用例3')
212
+ def test_abe(self):
213
+ pass
214
+ # raise AssertionError()
215
+ # pytest.fail('用例失败')
216
+ ```
217
+
218
+ ### 配置执行入口
219
+
220
+ 然后再项目根目录新建 main.py 作为执行的入口,写入以下内容。
221
+
222
+ ```python
223
+ import pytest
224
+ import os
225
+ import shutil
226
+
227
+ if __name__ == '__main__':
228
+ pytest_options = [
229
+ 'tests/',
230
+ # '--case_ids', '456456',
231
+ # '--scan',
232
+ '-v',
233
+ '--alluredir', 'allure-results']
234
+ # shutil.rmtree('allure-results') # 如果需要清空 allure-results 目录就解除本行注释
235
+ pytest.main(pytest_options)
236
+ os.system('allure generate allure-results -o ./report --clean')
237
+ ```
238
+
239
+ 通过为 main.py 中的 pytest_options 列表,添加 `--case_ids` 参数可以排除指定的用例,添加或删除 `--scan` 参数可以启用或禁用扫描模式。
240
+
241
+ > 注意:如果发现 pytest_runtest_logreport 中的逻辑没有被执行。那可能是没有用例被选中(也就是本次没有执行任何用例)
242
+
243
+ 修改 `plugin.py` 的话不用重新 `pip install` 安装本插件,它是实时生效的。因为 `pip install -e` 的本质是在 Python 解释器的 `site-packages` 里面做了一个软链接到了本项目。
@@ -0,0 +1,208 @@
1
+ # pytest-platform-adapter
2
+
3
+ [![Release
4
+ Status](https://img.shields.io/pypi/v/pytest-platform-adapter)](https://pypi.python.org/pypi/pytest-platform-adapter)
5
+ [![Downloads](https://img.shields.io/pypi/dm/pytest-platform-adapter)](https://pypi.python.org/pypi/pytest-platform-adapter)
6
+ [![Build](https://github.com/blackyau/pytest-platform-adapter/actions/workflows/deploy.yml/badge.svg)](https://github.com/allure-framework/allure-python/actions/workflows/build.yaml)
7
+
8
+ Pytest集成自动化平台插件
9
+
10
+ ## 功能
11
+
12
+ - 根据用例 `allure.title` 中的 ID 筛选执行的用例
13
+ - 跳过所有用例的执行,快速生成 Allure 报告
14
+ - 周期性的通过 RESTApi 上报执行用例的整体进度
15
+
16
+ ## 环境依赖
17
+
18
+ - Python >= 3.6
19
+ - pytest >= 7.1.2
20
+ - allure-pytest >= 2.9.45
21
+
22
+ ## 安装
23
+
24
+ 执行以下命令即可完成安装
25
+
26
+ ```shell
27
+ pip install pytest-platform-adapter
28
+ ```
29
+
30
+ ## 使用方法
31
+
32
+ ### 根据 Allure title 中的 ID 筛选用例
33
+
34
+ 假设有以下测试用例
35
+
36
+ ```python
37
+ import allure
38
+
39
+ @allure.title('19936-测试用例1')
40
+ def test_case1():
41
+ assert True
42
+
43
+ @allure.title('19930-测试用例2')
44
+ def test_case2():
45
+ assert True
46
+
47
+ @allure.title('19939-测试用例3')
48
+ def test_case3():
49
+ assert True
50
+ ```
51
+
52
+ 该插件提供两种方式来筛选测试用例:
53
+
54
+ 1. 通过命令行参数指定测试ID:
55
+ ```bash
56
+ pytest tests/ -v --case-ids="19936,19930"
57
+ ```
58
+
59
+ 2. 通过文件指定测试ID:
60
+ ```bash
61
+ pytest tests/ -v --case-ids-file="test_ids.txt"
62
+ ```
63
+
64
+ 其中 test_ids.txt 的内容格式如下:
65
+ ```
66
+ 19936
67
+ 19930
68
+ ```
69
+
70
+ ### 跳过所有用例
71
+
72
+ 使用 `--scan` 参数,启用扫描模式。在扫描模式下,所有用例都会被 skip 不会执行,可以用于快速生成 Allure 报告。使用方法如下:
73
+
74
+ ```shell
75
+ pytest --scan --alluredir allure-results
76
+ ```
77
+
78
+ ### 将执行进展通过RESTApi回报
79
+
80
+ 在 pytest.ini 中配置以下内容
81
+
82
+ ```ini
83
+ [pytest]
84
+ platform_ip = 127.0.0.1
85
+ platform_port = 8080
86
+ platform_path = /api/autoplatform/task/refresh_data_count
87
+ platform_use_https = False
88
+ ```
89
+
90
+ 执行用例的过程中(前 10 个用例的时候执行完就立刻回报,之后就是每隔 10 个用例才回报一次) 会将测试用例的整体进展以 `POST` 的方式回报给 `http://127.0.0.1:8080/api/autoplatform/task/refresh_data_count` 请求体如下:
91
+
92
+ ```json
93
+ {
94
+ "pipeline": "JOB_NAME",
95
+ "build_number": "BUILD_NUMBER",
96
+ "passed_case_count": 29,
97
+ "skipped_case_count": 1,
98
+ "failed_case_count": 0,
99
+ "selected_case": 100
100
+ }
101
+ ```
102
+
103
+ 其中 `JOB_NAME` 和 `BUILD_NUMBER` 是通过环境变量获取的。
104
+
105
+
106
+ ## 调试方法
107
+
108
+ ### 部署虚拟环境
109
+
110
+ 新建一个虚拟环境,建议使用 Python 3.11.9 。
111
+
112
+ 安装依赖
113
+
114
+ ```shell
115
+ pip install -r requirements.txt
116
+ ```
117
+
118
+ 在项目根目录(与pyproject.toml同一路径)执行以下命令安装本插件
119
+
120
+ ```shell
121
+ pip install -e .
122
+ ```
123
+
124
+ 执行 `pytest --help` 检查返回的内容是否包含以下文本,如果有的话就说明安装成功了
125
+
126
+ ```
127
+ usage: pytest [options] [file_or_dir] [file_or_dir] [...]
128
+
129
+ positional arguments:
130
+ file_or_dir
131
+
132
+ general:
133
+
134
+ ..... 省略中间
135
+
136
+ 自动化平台插件:
137
+ --case_ids=CASE_IDS 要执行的测试用例ID列表,使用逗号分隔,例如:19936,19930
138
+ --case_ids_file=CASE_IDS_FILE
139
+ 包含测试用例ID的文件路径,文件中每行一个ID
140
+ --scan 扫描模式:快速生成 Allure 报告而不实际执行测试
141
+
142
+ [pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg|pyproject.toml file found:
143
+
144
+ ..... 省略中间
145
+
146
+ platform_ip (string): 自动化平台IP
147
+ platform_port (string):
148
+ 自动化平台端口
149
+
150
+ ```
151
+
152
+ ### 创建测试用例
153
+
154
+ 在项目根目录新建 tests 目录,然后在 tests 目录中新建以 test_ 开头的 py 文件,作为测试用例。
155
+
156
+ 例如新建 `test_aaa.py`,在文件中写入以下内容
157
+
158
+ ```python
159
+ import allure
160
+
161
+
162
+ class TestAbc:
163
+ @allure.title('19936-用例1')
164
+ def test_abc(self):
165
+ print('test_abc 用例执行中')
166
+
167
+ # @pytest.mark.xfail(reason='跳过')
168
+ class TestAbd:
169
+ @allure.title('19930-用例2')
170
+ def test_abd(self):
171
+ with allure.step('步骤1'):
172
+ pass
173
+
174
+ # @pytest.mark.xfail(reason='跳过')
175
+ class TestAbe:
176
+ @allure.title('19932-用例3')
177
+ def test_abe(self):
178
+ pass
179
+ # raise AssertionError()
180
+ # pytest.fail('用例失败')
181
+ ```
182
+
183
+ ### 配置执行入口
184
+
185
+ 然后再项目根目录新建 main.py 作为执行的入口,写入以下内容。
186
+
187
+ ```python
188
+ import pytest
189
+ import os
190
+ import shutil
191
+
192
+ if __name__ == '__main__':
193
+ pytest_options = [
194
+ 'tests/',
195
+ # '--case_ids', '456456',
196
+ # '--scan',
197
+ '-v',
198
+ '--alluredir', 'allure-results']
199
+ # shutil.rmtree('allure-results') # 如果需要清空 allure-results 目录就解除本行注释
200
+ pytest.main(pytest_options)
201
+ os.system('allure generate allure-results -o ./report --clean')
202
+ ```
203
+
204
+ 通过为 main.py 中的 pytest_options 列表,添加 `--case_ids` 参数可以排除指定的用例,添加或删除 `--scan` 参数可以启用或禁用扫描模式。
205
+
206
+ > 注意:如果发现 pytest_runtest_logreport 中的逻辑没有被执行。那可能是没有用例被选中(也就是本次没有执行任何用例)
207
+
208
+ 修改 `plugin.py` 的话不用重新 `pip install` 安装本插件,它是实时生效的。因为 `pip install -e` 的本质是在 Python 解释器的 `site-packages` 里面做了一个软链接到了本项目。
@@ -0,0 +1,50 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pytest_platform_adapter"
7
+ version = "1.0.0"
8
+ dependencies = [
9
+ "pytest>=6.2.5",
10
+ "allure-pytest>=2.9.45"
11
+ ]
12
+ requires-python = ">=3.6"
13
+ authors = [
14
+ { name = "BlackYau", email = "blackyau426@gmail.com" }
15
+ ]
16
+ maintainers = [
17
+ { name = "BlackYau", email = "blackyau426@gmail.com" }
18
+ ]
19
+ description = "Pytest集成自动化平台插件"
20
+ readme = "README.md"
21
+ license = { text = "MIT" }
22
+ keywords = ["pytest", "platform", "adapter", "自动化平台", "插件"]
23
+ classifiers = [
24
+ 'Development Status :: 5 - Production/Stable',
25
+ 'Environment :: Plugins',
26
+ 'Framework :: Pytest',
27
+ 'Intended Audience :: Developers',
28
+ 'License :: OSI Approved :: MIT License',
29
+ 'Natural Language :: Chinese (Simplified)',
30
+ 'Programming Language :: Python :: 3',
31
+ 'Programming Language :: Python :: 3.6',
32
+ 'Programming Language :: Python :: 3.7',
33
+ 'Programming Language :: Python :: 3.8',
34
+ 'Programming Language :: Python :: 3.9',
35
+ 'Programming Language :: Python :: 3.10',
36
+ 'Programming Language :: Python :: 3.11',
37
+ 'Topic :: Software Development',
38
+ 'Topic :: Software Development :: Testing',
39
+ 'Topic :: Software Development :: Testing :: Acceptance',
40
+ 'Topic :: Software Development :: Libraries',
41
+ 'Topic :: Software Development :: Libraries :: Python Modules'
42
+ ]
43
+
44
+ [project.urls]
45
+ Homepage = "https://github.com/blackyau/pytest-platform-adapter"
46
+ Repository = "https://github.com/blackyau/pytest-platform-adapter.git"
47
+ Issues = "https://github.com/blackyau/pytest-platform-adapter/issues"
48
+
49
+ [project.entry-points.pytest11]
50
+ platform-adapter = "pytest_platform_adapter.plugin"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,265 @@
1
+ import asyncio
2
+ import json
3
+ import os
4
+ from typing import List, Optional
5
+
6
+ import requests
7
+ from allure_pytest.utils import allure_title
8
+ import logging
9
+ import pytest
10
+
11
+ logger = logging.getLogger('pytest-platform-adapter')
12
+ logger.setLevel(logging.INFO)
13
+
14
+ # 全局变量用于统计测试用例状态
15
+ test_stats = {
16
+ 'total': 0,
17
+ 'passed': 0,
18
+ 'failed': 0,
19
+ 'skipped': 0,
20
+ 'current': 0 # 当前已执行的用例数
21
+ }
22
+ milestone_counter = 10 # 里程碑计数器
23
+ failed_cases = set() # 记录已标记为失败的用例
24
+ skipped_cases = set() # 记录已标记为跳过的用例
25
+ cases_ids = set() # 存放所有用例ID,用来检查是否有重复的
26
+ scan_enable = False # 记录是否扫描模式,默认为False非扫描模式,True为扫描模式
27
+ platform_ip = None
28
+ platform_port = None
29
+ platform_path = None
30
+ pipeline_name = None
31
+ build_number = None
32
+ platform_use_https = False
33
+
34
+
35
+ def pytest_addoption(parser):
36
+ group = parser.getgroup('platform-adapter', '自动化平台插件')
37
+ group.addoption(
38
+ '--case_ids',
39
+ action='store',
40
+ default=None,
41
+ help='要执行的测试用例ID列表,使用逗号分隔,例如:19936,19930'
42
+ )
43
+ group.addoption(
44
+ '--case_ids_file',
45
+ action='store',
46
+ default=None,
47
+ help='包含测试用例ID的文件路径,文件中每行一个ID'
48
+ )
49
+ group.addoption(
50
+ '--scan',
51
+ action='store_true',
52
+ default=False,
53
+ help='扫描模式:快速生成 Allure 报告而不实际执行测试'
54
+ )
55
+ parser.addini(
56
+ 'platform_ip',
57
+ help='自动化平台API IP',
58
+ default=None
59
+ )
60
+ parser.addini(
61
+ 'platform_port',
62
+ help='自动化平台API端口',
63
+ default=None
64
+ )
65
+ parser.addini(
66
+ 'platform_path',
67
+ help='自动化平台API Path',
68
+ default='/api/autoplatform/task/refresh_data_count'
69
+ )
70
+ parser.addini(
71
+ 'platform_use_https',
72
+ help='上报自动化平台时启用HTTPS,默认不启用',
73
+ default=False
74
+ )
75
+
76
+
77
+ def pytest_collection_modifyitems(config, items):
78
+ """
79
+ hook收集用例的过程,给--case_ids和--case_ids_file提供支持
80
+ 修改测试用例集合,根据提供的测试用例ID过滤测试用例
81
+ """
82
+ target_ids = get_target_test_ids(config)
83
+ if not target_ids:
84
+ test_stats['total'] = len(items) # 更新总用例数
85
+ return
86
+ selected = []
87
+ deselected = []
88
+ for item in items:
89
+ title = allure_title(item)
90
+ test_id = get_test_id_from_title(title)
91
+ if test_id in target_ids:
92
+ selected.append(item)
93
+ else:
94
+ deselected.append(item)
95
+ # 检测 ID 是否有重复,只是单纯的检查一下,不影响执行
96
+ if test_id in cases_ids:
97
+ # 为 None 在这里就不用打印log了,因为在 get_test_id_from_title 里面就会报错一次
98
+ if test_id is None:
99
+ continue
100
+ logger.warning(f"测试用例ID {test_id} 重复")
101
+ else:
102
+ cases_ids.add(test_id)
103
+ if deselected:
104
+ config.hook.pytest_deselected(items=deselected)
105
+ items[:] = selected
106
+ selected_ids = [get_test_id_from_title(allure_title(item)) for item in selected]
107
+ test_stats['total'] = len(selected) # 更新总用例数
108
+ logger.info("目标测试用例ID (%d个): %s", len(target_ids), target_ids)
109
+ logger.info("实际执行用例ID (%d个): %s", len(selected_ids), selected_ids)
110
+
111
+
112
+ @pytest.hookimpl(hookwrapper=True)
113
+ def pytest_runtest_call(item):
114
+ if item.config.getoption('--scan'):
115
+ # logger.info(f"扫描模式:跳过执行测试用例 {allure_title(item)}")
116
+ pytest.skip("扫描模式已启动,跳过执行测试用例")
117
+ yield
118
+
119
+
120
+ @pytest.hookimpl(hookwrapper=True)
121
+ def pytest_runtest_setup(item):
122
+ if item.config.getoption('--scan'):
123
+ # logger.info(f"扫描模式:跳过测试用例 {allure_title(item)}前置")
124
+ pytest.skip("扫描模式已启动,跳过测试用例前置")
125
+ yield
126
+
127
+
128
+ @pytest.hookimpl(hookwrapper=True)
129
+ def pytest_runtest_teardown(item):
130
+ if item.config.getoption('--scan'):
131
+ # logger.info(f"扫描模式:跳过测试用例 {allure_title(item)}后置")
132
+ pytest.skip("扫描模式已启动,跳过测试用例后置")
133
+ yield
134
+
135
+
136
+ @pytest.hookimpl
137
+ def pytest_runtest_logreport(report):
138
+ """
139
+ hook测试用例执行结果的输出过程
140
+ """
141
+ global milestone_counter, scan_enable, platform_ip, platform_port, platform_path, platform_use_https
142
+ global pipeline_name, build_number
143
+ # config = pytest.Config
144
+ # my_option = config.getini("my_option")
145
+ # 使用 report.nodeid 作为唯一标识
146
+ nodeid = report.nodeid
147
+
148
+ if report.when in ['setup', 'call', 'teardown']:
149
+ # 记录失败状态,如果该用例任意阶段失败,则添加到 failed_cases
150
+ if report.failed:
151
+ failed_cases.add(nodeid)
152
+
153
+ # 记录跳过状态,仅在 setup 阶段处理
154
+ if report.skipped and report.when == 'setup':
155
+ skipped_cases.add(nodeid)
156
+ test_stats['skipped'] += 1
157
+
158
+ # 仅在 teardown 阶段完成统计更新
159
+ if report.when == 'teardown':
160
+ test_stats['current'] += 1
161
+ # 检查用例是否为失败、跳过或通过
162
+ if nodeid in failed_cases:
163
+ # TODO Xfail 应该计算为失败,但是不太对
164
+ test_stats['failed'] += 1
165
+ elif nodeid in skipped_cases:
166
+ # 如果用例已跳过,不计为通过
167
+ pass
168
+ else:
169
+ test_stats['passed'] += 1
170
+
171
+ # 打印进度和统计信息
172
+ progress = (test_stats['current'] / test_stats['total']) * 100 if test_stats['total'] != 0 else 0
173
+ pass_rate = (test_stats['passed'] / (test_stats['total'] - test_stats['skipped'])) * 100 if (
174
+ (test_stats['total'] - test_stats['skipped']) != 0) else 0
175
+ logger.info(
176
+ f"pipeline_name:{pipeline_name}, build_number: {build_number}"
177
+ f"用例进度: 总数 {test_stats['total']}, 跳过 {test_stats['skipped']}, 已执行 {test_stats['current']}, "
178
+ f"失败 {test_stats['failed']}, 通过 {test_stats['passed']}, 进度 {progress:.2f}%, 通过率 {pass_rate:.2f}%"
179
+ )
180
+
181
+ # 当没有启用扫描模式、同时配置了平台的IP和端口的时候才回报给自动化平台
182
+ if not scan_enable and platform_ip and platform_port:
183
+ # 在执行前10个用例的时候,进度立即回报给平台进度。之后就是每隔10个用例再回报一次
184
+ if test_stats['current'] <= 10 or test_stats['current'] % 10 == 0:
185
+ json_data = None
186
+ url = f"http://{platform_ip}:{platform_port}{platform_path}" if not platform_use_https \
187
+ else f"https://{platform_ip}:{platform_port}{platform_path}"
188
+ try:
189
+ json_data = {
190
+ 'pipeline': pipeline_name,
191
+ 'build_number': build_number,
192
+ 'passed_case_count': test_stats['passed'],
193
+ 'skipped_case_count': test_stats['skipped'],
194
+ 'failed_case_count': test_stats['failed'],
195
+ 'selected_case': test_stats['total'] }
196
+ headers = {'Content-Type': 'application/json'}
197
+ response = requests.post(url, data=json.dumps(json_data), headers=headers, timeout=3)
198
+ logger.info(f"已将数据回报给 {url},"
199
+ f"平台返回状态码:{response.status_code},"
200
+ f"响应体:{response.text},请求体:{json_data}")
201
+ except Exception as e:
202
+ logger.info(f"将请求发送到 {url}"
203
+ f",请求体:{json_data} 错误信息:{e}")
204
+
205
+
206
+ def pytest_configure(config):
207
+ global scan_enable, platform_ip, platform_port, platform_path
208
+ global pipeline_name, build_number
209
+ scan_enable, platform_ip, platform_port, platform_path, platform_use_https = config.getoption(
210
+ '--scan'), config.getini('platform_ip'), config.getini('platform_port'), config.getini(
211
+ 'platform_path'), config.getini('platform_use_https')
212
+ pipeline_name = os.environ.get("JOB_NAME")
213
+ build_number = os.environ.get("BUILD_NUMBER")
214
+ handler = logging.StreamHandler()
215
+ handler.setLevel(logging.INFO)
216
+ formatter = logging.Formatter('自动化平台插件 - %(message)s')
217
+ handler.setFormatter(formatter)
218
+ logger.addHandler(handler)
219
+ config.addinivalue_line(
220
+ "markers",
221
+ "allure_title: 使用allure标题标记测试用例"
222
+ )
223
+
224
+
225
+ def get_test_ids_from_file(file_path: str) -> List[str]:
226
+ with open(file_path, 'r') as f:
227
+ return [line.strip() for line in f if line.strip()]
228
+
229
+
230
+ def get_test_ids_from_option(ids_string: str) -> List[str]:
231
+ case_id = []
232
+ for id_ in ids_string.strip(',').split(','):
233
+ id_strip = id_.strip()
234
+ if not id_strip.isdigit():
235
+ logger.error(f'存在无效的测试用例ID:{id_}')
236
+ continue
237
+ else:
238
+ case_id.append(id_strip)
239
+ return case_id
240
+
241
+
242
+ def get_target_test_ids(config) -> Optional[List[str]]:
243
+ case_ids = config.getoption('--case_ids')
244
+ case_ids_file = config.getoption('--case_ids_file')
245
+ if case_ids:
246
+ logger.info(f"接收到case_ids入参为:'{case_ids}'")
247
+ return get_test_ids_from_option(case_ids)
248
+ elif case_ids_file:
249
+ logger.info(f"接收到case_ids_file入参为:'{case_ids_file}'")
250
+ return get_test_ids_from_file(case_ids_file)
251
+ return None
252
+
253
+
254
+ def get_test_id_from_title(title: Optional[str]) -> Optional[str]:
255
+ if not title:
256
+ return None
257
+ match = title.split('-', 1)
258
+ if len(match) != 2:
259
+ logger.error(f'存在无法解析用例ID的用例,用例标题为:{title}')
260
+ return None
261
+ if match[0].isdigit():
262
+ return match[0]
263
+ else:
264
+ logger.error(f'存在无法解析用例ID的用例,用例标题为:{title}')
265
+ return None
@@ -0,0 +1,243 @@
1
+ Metadata-Version: 2.2
2
+ Name: pytest_platform_adapter
3
+ Version: 1.0.0
4
+ Summary: Pytest集成自动化平台插件
5
+ Author-email: BlackYau <blackyau426@gmail.com>
6
+ Maintainer-email: BlackYau <blackyau426@gmail.com>
7
+ License: MIT
8
+ Project-URL: Homepage, https://github.com/blackyau/pytest-platform-adapter
9
+ Project-URL: Repository, https://github.com/blackyau/pytest-platform-adapter.git
10
+ Project-URL: Issues, https://github.com/blackyau/pytest-platform-adapter/issues
11
+ Keywords: pytest,platform,adapter,自动化平台,插件
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Environment :: Plugins
14
+ Classifier: Framework :: Pytest
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Natural Language :: Chinese (Simplified)
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.6
20
+ Classifier: Programming Language :: Python :: 3.7
21
+ Classifier: Programming Language :: Python :: 3.8
22
+ Classifier: Programming Language :: Python :: 3.9
23
+ Classifier: Programming Language :: Python :: 3.10
24
+ Classifier: Programming Language :: Python :: 3.11
25
+ Classifier: Topic :: Software Development
26
+ Classifier: Topic :: Software Development :: Testing
27
+ Classifier: Topic :: Software Development :: Testing :: Acceptance
28
+ Classifier: Topic :: Software Development :: Libraries
29
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
30
+ Requires-Python: >=3.6
31
+ Description-Content-Type: text/markdown
32
+ License-File: LICENSE
33
+ Requires-Dist: pytest>=6.2.5
34
+ Requires-Dist: allure-pytest>=2.9.45
35
+
36
+ # pytest-platform-adapter
37
+
38
+ [![Release
39
+ Status](https://img.shields.io/pypi/v/pytest-platform-adapter)](https://pypi.python.org/pypi/pytest-platform-adapter)
40
+ [![Downloads](https://img.shields.io/pypi/dm/pytest-platform-adapter)](https://pypi.python.org/pypi/pytest-platform-adapter)
41
+ [![Build](https://github.com/blackyau/pytest-platform-adapter/actions/workflows/deploy.yml/badge.svg)](https://github.com/allure-framework/allure-python/actions/workflows/build.yaml)
42
+
43
+ Pytest集成自动化平台插件
44
+
45
+ ## 功能
46
+
47
+ - 根据用例 `allure.title` 中的 ID 筛选执行的用例
48
+ - 跳过所有用例的执行,快速生成 Allure 报告
49
+ - 周期性的通过 RESTApi 上报执行用例的整体进度
50
+
51
+ ## 环境依赖
52
+
53
+ - Python >= 3.6
54
+ - pytest >= 7.1.2
55
+ - allure-pytest >= 2.9.45
56
+
57
+ ## 安装
58
+
59
+ 执行以下命令即可完成安装
60
+
61
+ ```shell
62
+ pip install pytest-platform-adapter
63
+ ```
64
+
65
+ ## 使用方法
66
+
67
+ ### 根据 Allure title 中的 ID 筛选用例
68
+
69
+ 假设有以下测试用例
70
+
71
+ ```python
72
+ import allure
73
+
74
+ @allure.title('19936-测试用例1')
75
+ def test_case1():
76
+ assert True
77
+
78
+ @allure.title('19930-测试用例2')
79
+ def test_case2():
80
+ assert True
81
+
82
+ @allure.title('19939-测试用例3')
83
+ def test_case3():
84
+ assert True
85
+ ```
86
+
87
+ 该插件提供两种方式来筛选测试用例:
88
+
89
+ 1. 通过命令行参数指定测试ID:
90
+ ```bash
91
+ pytest tests/ -v --case-ids="19936,19930"
92
+ ```
93
+
94
+ 2. 通过文件指定测试ID:
95
+ ```bash
96
+ pytest tests/ -v --case-ids-file="test_ids.txt"
97
+ ```
98
+
99
+ 其中 test_ids.txt 的内容格式如下:
100
+ ```
101
+ 19936
102
+ 19930
103
+ ```
104
+
105
+ ### 跳过所有用例
106
+
107
+ 使用 `--scan` 参数,启用扫描模式。在扫描模式下,所有用例都会被 skip 不会执行,可以用于快速生成 Allure 报告。使用方法如下:
108
+
109
+ ```shell
110
+ pytest --scan --alluredir allure-results
111
+ ```
112
+
113
+ ### 将执行进展通过RESTApi回报
114
+
115
+ 在 pytest.ini 中配置以下内容
116
+
117
+ ```ini
118
+ [pytest]
119
+ platform_ip = 127.0.0.1
120
+ platform_port = 8080
121
+ platform_path = /api/autoplatform/task/refresh_data_count
122
+ platform_use_https = False
123
+ ```
124
+
125
+ 执行用例的过程中(前 10 个用例的时候执行完就立刻回报,之后就是每隔 10 个用例才回报一次) 会将测试用例的整体进展以 `POST` 的方式回报给 `http://127.0.0.1:8080/api/autoplatform/task/refresh_data_count` 请求体如下:
126
+
127
+ ```json
128
+ {
129
+ "pipeline": "JOB_NAME",
130
+ "build_number": "BUILD_NUMBER",
131
+ "passed_case_count": 29,
132
+ "skipped_case_count": 1,
133
+ "failed_case_count": 0,
134
+ "selected_case": 100
135
+ }
136
+ ```
137
+
138
+ 其中 `JOB_NAME` 和 `BUILD_NUMBER` 是通过环境变量获取的。
139
+
140
+
141
+ ## 调试方法
142
+
143
+ ### 部署虚拟环境
144
+
145
+ 新建一个虚拟环境,建议使用 Python 3.11.9 。
146
+
147
+ 安装依赖
148
+
149
+ ```shell
150
+ pip install -r requirements.txt
151
+ ```
152
+
153
+ 在项目根目录(与pyproject.toml同一路径)执行以下命令安装本插件
154
+
155
+ ```shell
156
+ pip install -e .
157
+ ```
158
+
159
+ 执行 `pytest --help` 检查返回的内容是否包含以下文本,如果有的话就说明安装成功了
160
+
161
+ ```
162
+ usage: pytest [options] [file_or_dir] [file_or_dir] [...]
163
+
164
+ positional arguments:
165
+ file_or_dir
166
+
167
+ general:
168
+
169
+ ..... 省略中间
170
+
171
+ 自动化平台插件:
172
+ --case_ids=CASE_IDS 要执行的测试用例ID列表,使用逗号分隔,例如:19936,19930
173
+ --case_ids_file=CASE_IDS_FILE
174
+ 包含测试用例ID的文件路径,文件中每行一个ID
175
+ --scan 扫描模式:快速生成 Allure 报告而不实际执行测试
176
+
177
+ [pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg|pyproject.toml file found:
178
+
179
+ ..... 省略中间
180
+
181
+ platform_ip (string): 自动化平台IP
182
+ platform_port (string):
183
+ 自动化平台端口
184
+
185
+ ```
186
+
187
+ ### 创建测试用例
188
+
189
+ 在项目根目录新建 tests 目录,然后在 tests 目录中新建以 test_ 开头的 py 文件,作为测试用例。
190
+
191
+ 例如新建 `test_aaa.py`,在文件中写入以下内容
192
+
193
+ ```python
194
+ import allure
195
+
196
+
197
+ class TestAbc:
198
+ @allure.title('19936-用例1')
199
+ def test_abc(self):
200
+ print('test_abc 用例执行中')
201
+
202
+ # @pytest.mark.xfail(reason='跳过')
203
+ class TestAbd:
204
+ @allure.title('19930-用例2')
205
+ def test_abd(self):
206
+ with allure.step('步骤1'):
207
+ pass
208
+
209
+ # @pytest.mark.xfail(reason='跳过')
210
+ class TestAbe:
211
+ @allure.title('19932-用例3')
212
+ def test_abe(self):
213
+ pass
214
+ # raise AssertionError()
215
+ # pytest.fail('用例失败')
216
+ ```
217
+
218
+ ### 配置执行入口
219
+
220
+ 然后再项目根目录新建 main.py 作为执行的入口,写入以下内容。
221
+
222
+ ```python
223
+ import pytest
224
+ import os
225
+ import shutil
226
+
227
+ if __name__ == '__main__':
228
+ pytest_options = [
229
+ 'tests/',
230
+ # '--case_ids', '456456',
231
+ # '--scan',
232
+ '-v',
233
+ '--alluredir', 'allure-results']
234
+ # shutil.rmtree('allure-results') # 如果需要清空 allure-results 目录就解除本行注释
235
+ pytest.main(pytest_options)
236
+ os.system('allure generate allure-results -o ./report --clean')
237
+ ```
238
+
239
+ 通过为 main.py 中的 pytest_options 列表,添加 `--case_ids` 参数可以排除指定的用例,添加或删除 `--scan` 参数可以启用或禁用扫描模式。
240
+
241
+ > 注意:如果发现 pytest_runtest_logreport 中的逻辑没有被执行。那可能是没有用例被选中(也就是本次没有执行任何用例)
242
+
243
+ 修改 `plugin.py` 的话不用重新 `pip install` 安装本插件,它是实时生效的。因为 `pip install -e` 的本质是在 Python 解释器的 `site-packages` 里面做了一个软链接到了本项目。
@@ -0,0 +1,11 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/pytest_platform_adapter/__init__.py
5
+ src/pytest_platform_adapter/plugin.py
6
+ src/pytest_platform_adapter.egg-info/PKG-INFO
7
+ src/pytest_platform_adapter.egg-info/SOURCES.txt
8
+ src/pytest_platform_adapter.egg-info/dependency_links.txt
9
+ src/pytest_platform_adapter.egg-info/entry_points.txt
10
+ src/pytest_platform_adapter.egg-info/requires.txt
11
+ src/pytest_platform_adapter.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [pytest11]
2
+ platform-adapter = pytest_platform_adapter.plugin
@@ -0,0 +1,2 @@
1
+ pytest>=6.2.5
2
+ allure-pytest>=2.9.45