api-engine-xin 0.0.21__tar.gz → 0.0.22__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.
Files changed (20) hide show
  1. {api_engine_xin-0.0.21 → api_engine_xin-0.0.22}/ApiEngine/BaseCase.py +20 -10
  2. {api_engine_xin-0.0.21 → api_engine_xin-0.0.22}/ApiEngine/caseLog.py +5 -0
  3. {api_engine_xin-0.0.21 → api_engine_xin-0.0.22}/ApiEngine/core.py +9 -3
  4. {api_engine_xin-0.0.21/api_engine_xin.egg-info → api_engine_xin-0.0.22}/PKG-INFO +2 -15
  5. {api_engine_xin-0.0.21 → api_engine_xin-0.0.22/api_engine_xin.egg-info}/PKG-INFO +3 -16
  6. {api_engine_xin-0.0.21 → api_engine_xin-0.0.22}/setup.py +1 -1
  7. {api_engine_xin-0.0.21 → api_engine_xin-0.0.22}/ApiEngine/__init__.py +0 -0
  8. {api_engine_xin-0.0.21 → api_engine_xin-0.0.22}/ApiEngine/dbClient.py +0 -0
  9. {api_engine_xin-0.0.21 → api_engine_xin-0.0.22}/ApiEngine/global_func.py +0 -0
  10. {api_engine_xin-0.0.21 → api_engine_xin-0.0.22}/ApiEngine/testResult.py +0 -0
  11. {api_engine_xin-0.0.21 → api_engine_xin-0.0.22}/LICENSE +0 -0
  12. {api_engine_xin-0.0.21 → api_engine_xin-0.0.22}/README.md +0 -0
  13. {api_engine_xin-0.0.21 → api_engine_xin-0.0.22}/api_engine_xin.egg-info/SOURCES.txt +0 -0
  14. {api_engine_xin-0.0.21 → api_engine_xin-0.0.22}/api_engine_xin.egg-info/dependency_links.txt +0 -0
  15. {api_engine_xin-0.0.21 → api_engine_xin-0.0.22}/api_engine_xin.egg-info/requires.txt +0 -0
  16. {api_engine_xin-0.0.21 → api_engine_xin-0.0.22}/api_engine_xin.egg-info/top_level.txt +0 -0
  17. {api_engine_xin-0.0.21 → api_engine_xin-0.0.22}/setup.cfg +0 -0
  18. {api_engine_xin-0.0.21 → api_engine_xin-0.0.22}/tests/Tools.py +0 -0
  19. {api_engine_xin-0.0.21 → api_engine_xin-0.0.22}/tests/__init__.py +0 -0
  20. {api_engine_xin-0.0.21 → api_engine_xin-0.0.22}/tests/runTest.py +0 -0
@@ -26,15 +26,17 @@ class BaseCase(CaseLogHandler):
26
26
  db = self._db
27
27
  # 1、读取前置脚本数据
28
28
  setup_scripts = data.get("setup_script")
29
- # 2、执行字符串中有效的python代码
30
- exec(setup_scripts)
29
+ # 2、执行字符串中有效的python代码(空值守卫)
30
+ if setup_scripts and isinstance(setup_scripts, str):
31
+ exec(setup_scripts)
31
32
 
32
33
  response = yield
33
34
 
34
35
  # 1、读取后置脚本数据
35
36
  teardown_scripts = data.get("teardown_script")
36
- # 2、执行字符串中有效的python代码
37
- exec(teardown_scripts)
37
+ # 2、执行字符串中有效的python代码(空值守卫)
38
+ if teardown_scripts and isinstance(teardown_scripts, str):
39
+ exec(teardown_scripts)
38
40
 
39
41
  yield
40
42
 
@@ -165,16 +167,17 @@ class BaseCase(CaseLogHandler):
165
167
  else:
166
168
  request_data["url"] = ENV.get("base_url") + data.get("interface").get("url")
167
169
  request_data["method"] = data.get("interface").get("method")
168
- # 2、处理请求头
169
- request_data["headers"] = ENV.get("headers")
170
- request_data["headers"].update(data.get("headers"))
170
+ # 2、处理请求头(使用副本,避免污染全局 ENV)
171
+ request_data["headers"] = dict(ENV.get("headers") or {})
172
+ request_data["headers"].update(data.get("headers") or {})
171
173
  # 3、处理请求参数
172
174
  request_data["params"] = data.get("request").get("params")
173
175
  content_type = request_data["headers"].get("Content-Type", "")
176
+ _req = data.get("request") or {}
174
177
  if "application/json" in content_type:
175
- request_data["json"] = data.get("request").get("json")
178
+ request_data["json"] = _req.get("json") or _req.get("data")
176
179
  if "application/x-www-form-urlencoded" in content_type or "multipart/form-data" in content_type:
177
- request_data["data"] = data.get("request").get("data")
180
+ request_data["data"] = _req.get("data") or _req.get("json")
178
181
  if "multipart/form-data" in content_type:
179
182
  # 注意:这里不直接把 files 放进需要做变量替换的数据里,避免 open 文件对象被 eval 破坏
180
183
  request_data["files"] = data.get("request").get("files")
@@ -223,6 +226,7 @@ class BaseCase(CaseLogHandler):
223
226
  from urllib.parse import urlencode
224
227
  query_string = urlencode(params)
225
228
  full_url = f"{self.url}?{query_string}"
229
+ self.url = full_url # 更新 self.url 为完整URL(含已替换的查询参数)
226
230
  self.info_log("请求地址:", full_url)
227
231
  self.info_log("请求方法:", self.method)
228
232
  self.info_log("请求头:", self.request_headers)
@@ -353,8 +357,11 @@ class BaseCase(CaseLogHandler):
353
357
  self.error_log("响应体非JSON格式,无法提取数据")
354
358
  # 3、通过 JSONPath 提取值
355
359
  extracted_value = self.json_extract(resp_json, extract_expr)
360
+ # 记录实际值(供 __execute_assertions 读取,即使断言失败也能获取)
361
+ self._last_actual = extracted_value
356
362
  # 4、断言
357
363
  self.assertion(assertion_type, assertion_content, extracted_value)
364
+ return extracted_value
358
365
 
359
366
  # 遍历所有assertions项,依次执行断言
360
367
  def __execute_assertions(self, data, response):
@@ -368,12 +375,14 @@ class BaseCase(CaseLogHandler):
368
375
  expected = assertion.get("expected", "未知")
369
376
  assert_type = assertion.get("type", "eq")
370
377
  passed = True
378
+ actual_value = None
371
379
  try:
372
380
  self.info_log(f" [{idx}/{total}] 执行断言: field={field}, expected={expected}")
373
- self.__assert_data(assertion, response)
381
+ actual_value = self.__assert_data(assertion, response)
374
382
  self.info_log(f" [{idx}/{total}] ✅ 断言通过: {field}")
375
383
  except AssertionError as e:
376
384
  passed = False
385
+ actual_value = getattr(self, '_last_actual', None)
377
386
  self.error_log(f" [{idx}/{total}] ❌ 断言失败: {field} — {e}")
378
387
  assertion_errors.append({
379
388
  "index": idx,
@@ -387,6 +396,7 @@ class BaseCase(CaseLogHandler):
387
396
  "field": field,
388
397
  "type": assert_type,
389
398
  "expected": expected,
399
+ "actual": actual_value,
390
400
  "passed": passed,
391
401
  })
392
402
  # 所有断言执行完毕后输出汇总
@@ -1,7 +1,12 @@
1
+ import time
2
+
3
+
1
4
  class CaseLogHandler:
2
5
  """用例日志处理类"""
3
6
  def save_log(self,msg,level):
4
7
  """保存日志"""
8
+ ts = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
9
+ msg = ts + " | " + msg
5
10
  # 1、判断当前实例是否有日志属性
6
11
  if not hasattr(self,"log_data"):
7
12
  setattr(self,"log_data",[])
@@ -32,7 +32,9 @@ class TestRunner:
32
32
  ENV.clear()
33
33
  ENV.update(self.env_data)
34
34
  # 将tools中的函数(用户自定义),通过exec执行(字符串中的python函数),加载到TestTools模块的命名空间中
35
- exec(ENV.get("global_func"), global_func.__dict__)
35
+ _gf = ENV.get("global_func")
36
+ if _gf and isinstance(_gf, str):
37
+ exec(_gf, global_func.__dict__)
36
38
  # 创建测试结果的记录器
37
39
  test_result = TestResult(all=len(testcases["cases"]),name=testcases["name"])
38
40
  # 运行测试用例
@@ -48,7 +50,9 @@ class TestRunner:
48
50
  ENV.clear()
49
51
  ENV.update(self.env_data)
50
52
  # 将tools中的函数(用户自定义),通过exec执行(字符串中的python函数),加载到TestTools模块的命名空间中
51
- exec(ENV.get("global_func"), global_func.__dict__)
53
+ _gf = ENV.get("global_func")
54
+ if _gf and isinstance(_gf, str):
55
+ exec(_gf, global_func.__dict__)
52
56
  # 创建测试结果的记录器
53
57
  test_result = TestResult(all=1)
54
58
  # log.info_log("执行测试用例:",testcases)
@@ -65,7 +69,9 @@ class TestRunner:
65
69
  ENV.clear()
66
70
  ENV.update(self.env_data)
67
71
  # 将tools中的函数(用户自定义),通过exec执行(字符串中的python函数),加载到TestTools模块的命名空间中
68
- exec(ENV.get("global_func"), global_func.__dict__)
72
+ _gf = ENV.get("global_func")
73
+ if _gf and isinstance(_gf, str):
74
+ exec(_gf, global_func.__dict__)
69
75
  # 新增检测日志
70
76
  log.info_log(f"gen_random_num 是否存在:{hasattr(global_func, 'gen_random_num')}")
71
77
  log.info_log(f"gen_random_num 是否可调用:{callable(getattr(global_func, 'gen_random_num', None))}")
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: api_engine_xin
3
- Version: 0.0.21
3
+ Version: 0.0.22
4
4
  Summary: 接口测试平台测试用例执行引擎
5
5
  Home-page: https://pypi.org/project/api_engine_xin/
6
6
  Author: Shawn
@@ -19,19 +19,6 @@ Classifier: Operating System :: OS Independent
19
19
  Requires-Python: >=3.6
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
- Requires-Dist: pymysql>=1.0.0
23
- Requires-Dist: requests>=2.26.0
24
- Dynamic: author
25
- Dynamic: author-email
26
- Dynamic: classifier
27
- Dynamic: description
28
- Dynamic: description-content-type
29
- Dynamic: home-page
30
- Dynamic: keywords
31
- Dynamic: license-file
32
- Dynamic: requires-dist
33
- Dynamic: requires-python
34
- Dynamic: summary
35
22
 
36
23
 
37
24
  # api_engine_xin
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
2
- Name: api_engine_xin
3
- Version: 0.0.21
1
+ Metadata-Version: 2.1
2
+ Name: api-engine-xin
3
+ Version: 0.0.22
4
4
  Summary: 接口测试平台测试用例执行引擎
5
5
  Home-page: https://pypi.org/project/api_engine_xin/
6
6
  Author: Shawn
@@ -19,19 +19,6 @@ Classifier: Operating System :: OS Independent
19
19
  Requires-Python: >=3.6
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
- Requires-Dist: pymysql>=1.0.0
23
- Requires-Dist: requests>=2.26.0
24
- Dynamic: author
25
- Dynamic: author-email
26
- Dynamic: classifier
27
- Dynamic: description
28
- Dynamic: description-content-type
29
- Dynamic: home-page
30
- Dynamic: keywords
31
- Dynamic: license-file
32
- Dynamic: requires-dist
33
- Dynamic: requires-python
34
- Dynamic: summary
35
22
 
36
23
 
37
24
  # api_engine_xin
@@ -12,7 +12,7 @@ with codecs.open(os.path.join(here, "README.md"), encoding="utf-8") as fh:
12
12
  # 核心配置
13
13
  setup(
14
14
  name="api_engine_xin", # ✅【必须改】pip install 这个名字!全网唯一,不能和PyPI上已有的包名重复
15
- version="0.0.21", # ✅【必须改】版本号,每次更新包都要升级版本(如0.0.2、0.1.0)
15
+ version="0.0.22", # ✅【必须改】版本号,每次更新包都要升级版本(如0.0.2、0.1.0)
16
16
  author="Shawn",# ✅【必须改】你的名字/昵称
17
17
  author_email="xiaoh0525@xiaoh.com",# ✅【必须改】你的注册PyPI的邮箱
18
18
  description="接口测试平台测试用例执行引擎", # ✅【必须改】一句话说明你的包是干嘛的
File without changes