diff --git a/Runner/API/1.html b/Runner/API/1.html
index 6d00061..8166583 100644
--- a/Runner/API/1.html
+++ b/Runner/API/1.html
@@ -229,7 +229,7 @@
series: [{
name: '通过',
color: '#64bb64',
- data: [6]
+ data: [5]
}, {
name: '失败',
color: '#f16d7e',
@@ -237,7 +237,7 @@
}, {
name: '错误',
color: '#fdc68c',
- data: [0]
+ data: [1]
}]
})
// 增加饼状图
@@ -291,7 +291,7 @@
innerSize: '80%',
name: '比例',
data: [
- ['通过', 6], ['失败', 0], ['错误', 0]
+ ['通过', 5], ['失败', 0], ['错误', 1]
]
}]
}, function(c) {
@@ -438,15 +438,15 @@ function html_escape(s) {
测试报告
-
开始时间 : 2022-08-10 18:32:08
+
开始时间 : 2022-08-12 11:13:49
-
合计耗时 : 00:00:01
+
合计耗时 : 00:00:00
-
测试结果 : 总共 6,通过 6,失败 0,错误 0,通过率 100.00%
+
测试结果 : 总共 6,通过 5,失败 0,错误 1,通过率 83.33%
失败用例 : 无
-
错误用例 : 无
+
错误用例 : 点击查看
- main.测试用例.test0003_None
@@ -457,10 +457,10 @@ function html_escape(s) {
@@ -486,74 +486,121 @@ function html_escape(s) {
详细 |
-
+
测试用例 |
|
6 |
- 6 |
+ 5 |
0 |
- 0 |
- 1.154秒 |
+ 1 |
+ 0.720秒 |
查看全部 |
test0001_None |
- |
- |
- |
-
-
-
- test0002_None |
- |
- |
- |
-
-
-
- test0003_None |
|
-
-
- pt1_3:
-{'var8': 'Windows、Linux修改连接数限制'}
+
+
|
|
+
+ test0002_None |
+ |
+
+
+
+ |
+ |
+
+
+
+ test0003_None |
+ |
+
+
+
+ et1_3:
+Traceback (most recent call last):
+ File "D:\Project\AutoFramework\Runner\API\DefaultRunner.py", line 579, in foo
+ return self._test_(index)
+ File "D:\Project\AutoFramework\Runner\API\DefaultRunner.py", line 564, in test
+ self.test_unit(data=main_case)
+ File "D:\Project\AutoFramework\Runner\API\DefaultRunner.py", line 500, in test_unit
+ self._assert([
+ File "D:\Project\AutoFramework\Runner\API\DefaultRunner.py", line 437, in _assert
+ assert_parm = [eval(form[0]), eval(form[1])]
+ File "<string>", line 1, in <module>
+ File "C:\Program Files\Python310\lib\_sitebuiltins.py", line 26, in __call__
+ raise SystemExit(code)
+SystemExit: None
+
+
+ |
+ |
+
+
test0004_None |
- |
- |
+ |
+
+
+
+ |
|
test0005_None |
- |
- |
+ |
+
+
+
+ pt1_5:
+{'var2': 'nginx'}
+
+
+ |
|
test0006_None |
- |
- |
+ |
+
+
+
+ pt1_6:
+{'var2': 'nginx', 'var3': '101', 'var4': '阿凡\n凡凡', 'var5': '{"name": "小红", "age": 22}'}
+
+
+ |
|
总计 |
6 |
- 6 |
+ 5 |
0 |
- 0 |
- 1.154秒 |
- 通过:100.00% |
+ 1 |
+ 0.720秒 |
+ 通过:83.33% |
diff --git a/Runner/API/DefaultRunner.py b/Runner/API/DefaultRunner.py
index 63d8cd2..d6ed031 100644
--- a/Runner/API/DefaultRunner.py
+++ b/Runner/API/DefaultRunner.py
@@ -1,4 +1,3 @@
-import sys
import unittest
from typing import Any, Text, Dict, List
from Business.Class.HTMLTestRunner import HTMLTestRunner
@@ -33,7 +32,7 @@ class ReList(list):
if isinstance(item, (int, bool)):
try:
r = super().__getitem__(item)
- if isinstance(r, (list, tuple, set)):
+ if isinstance(r, (list, tuple, set)):
return __class__(r)
elif isinstance(r, (dict,)):
return ReDict(r)
@@ -61,7 +60,7 @@ class ReDict(dict):
def __getitem__(self, item):
if item in self:
r = super().__getitem__(item)
- if isinstance(r, (dict,)):
+ if isinstance(r, (dict,)):
return __class__(r)
elif isinstance(r, (list, tuple, set)):
return ReList(r)
@@ -87,10 +86,21 @@ class ReDict(dict):
return self
+class CustomAssert:
+ @staticmethod
+ def assertPreWith(string: str, sub: str):
+ if not string.startswith(sub):
+ raise AssertionError('%s does not starts with %s' % (string, sub))
+
+ @staticmethod
+ def assertSufWith(string: str, sub: str):
+ if not string.endswith(sub):
+ raise AssertionError('%s does not ends with %s' % (string, sub))
+
+
class TestCase:
_g = {}
_l = {}
- unit = unittest.TestCase()
def __init__(
self,
@@ -104,19 +114,21 @@ class TestCase:
tabs = {
"HTTPConf": {
"fixed": {},
- "views": "A4:C*",
+ "views": "A4:F*",
"field": [
"Name",
- "HostWithScheme",
- "DefaultHeader"
+ "Scheme",
+ "Host",
+ "DefaultHeader",
+ "Timeout",
+ "Session"
]
},
"DataBase": {
"fixed": {},
- "views": "E4:L*",
+ "views": "H4:N*",
"field": [
"Name",
- "Type",
"Host",
"Port",
"User",
@@ -137,10 +149,7 @@ class TestCase:
]
},
"TestCase": {
- "fixed": {
- "ItemName": "B3",
- "SessMode": "E3"
- },
+ "fixed": {},
"views": "A7:AH*",
"field": [
"Flag",
@@ -168,11 +177,11 @@ class TestCase:
"HTTPAssertCookie",
"HTTPAssertBody",
"HTTPAssertJson",
- "DatabaseChannel",
- "DatabaseSql",
- "DatabaseExtract",
- "DatabaseAssertData",
- "DatabaseAssertCount",
+ "BaseChannel",
+ "BaseSql",
+ "BaseExtract",
+ "BaseAssertData",
+ "BaseAssertRows",
"DataSet",
"Author",
"TestTime",
@@ -208,21 +217,33 @@ class TestCase:
else:
init.exit()
self.case = testcase
- self.http = Request
self.dirs = os.path.dirname(workbook)
+ self.request = Request()
+ self.session = Session()
- self.i = data['TestCase']['fixed']['ItemName']
- self.m = data['TestCase']['fixed']['SessMode']
self.h = data['HTTPConf']['views']
self.b = data['DataBase']['views']
self.d = data['TestData']['views']
self.c = data['TestCase']['views']
self._set_levels(testlves)
- self._set_request_mode(self.m)
- def _set_request_mode(self, value=None):
- self.http = [Request, Session][self._match_mode(value)]()
+ self.assert_unit = unittest.TestCase()
+ self.assert_cust = CustomAssert()
+ self.assert_list = [
+ {'sign': ' == ', 'method': self.assert_unit.assertEqual, 'reverse': 0, 'cast': 0},
+ {'sign': ' != ', 'method': self.assert_unit.assertNotEqual, 'reverse': 0, 'cast': 0},
+ {'sign': ' >= ', 'method': self.assert_unit.assertGreaterEqual, 'reverse': 0, 'cast': float},
+ {'sign': ' <= ', 'method': self.assert_unit.assertLessEqual, 'reverse': 0, 'cast': float},
+ {'sign': ' =~ ', 'method': self.assert_unit.assertIn, 'reverse': 1, 'cast': str},
+ {'sign': ' !~ ', 'method': self.assert_unit.assertNotIn, 'reverse': 1, 'cast': str},
+ {'sign': ' > ', 'method': self.assert_unit.assertGreater, 'reverse': 0, 'cast': float},
+ {'sign': ' < ', 'method': self.assert_unit.assertLess, 'reverse': 0, 'cast': float},
+ {'sign': ' not in ', 'method': self.assert_unit.assertNotIn, 'reverse': 0, 'cast': 0},
+ {'sign': ' in ', 'method': self.assert_unit.assertIn, 'reverse': 0, 'cast': 0},
+ {'sign': ' *= ', 'method': self.assert_cust.assertPreWith, 'reverse': 0, 'cast': str},
+ {'sign': ' =* ', 'method': self.assert_cust.assertSufWith, 'reverse': 0, 'cast': str},
+ ]
def _set_levels(self, level_list=None):
if isinstance(level_list, (list, tuple, str)):
@@ -238,24 +259,10 @@ class TestCase:
case _:
return 50
- @staticmethod
- def _match_parm_type(value=None):
- match value:
- case 'Json':
- return 10
- case 'Form':
- return 20
- case _:
- return 50
-
@staticmethod
def _match_bool(value=None):
return value in ['是', '开启', '打开', 'True', 'true', 'TRUE', True, 'Yes', 'yes', 'YES', 'Y', 'y', '1', 1, 1.0]
- @staticmethod
- def _match_mode(value=None):
- return value in ['会话模式', 'Session', 'session']
-
def _match_leve_run(self, value=None):
if self.leve_list is None:
return True
@@ -317,7 +324,7 @@ class TestCase:
return False
@staticmethod
- def _sub_variable(text='', vars_dict=None):
+ def _sub_variable(text, vars_dict):
if not isinstance(text, str):
return text
for variable_name in re.findall('\${(.*?)}', text):
@@ -328,7 +335,8 @@ class TestCase:
text = text.replace('${%s}' % variable_name, str(value))
return text
- def _sub_variable_auto(self, data=None, vars_dict=None):
+ def _sub_variable_auto(self, data):
+ vars_dict = [self._g, self._l]
if isinstance(vars_dict, list):
d = {}
for v in vars_dict:
@@ -351,139 +359,184 @@ class TestCase:
return data
@staticmethod
- def _to_string(text):
+ def _to_string(value):
import math
- if text is None:
+ if value is None:
return ''
try:
- if math.isnan(text):
+ if math.isnan(value):
return ''
except:
pass
- if isinstance(text, float) and math.modf(text)[0] == 0:
- return str(int(text))
- return str(text)
+ if isinstance(value, float) and math.modf(value)[0] == 0:
+ return str(int(value))
+ return str(value)
+
+ @staticmethod
+ def _to_number(value):
+ import math
+ if not value:
+ return 0
+ if value is True:
+ return 1
+ try:
+ if math.isnan(value):
+ return 0
+ except:
+ pass
+ return int(float(value))
+
+ def _extract_variable(self, extract, vars_dict: dict, domain: str | list | dict):
+ if not extract: return None
+ if not isinstance(extract, (list, dict)):
+ extract = auto_decode(extract)
+ if isinstance(domain, str):
+ match domain:
+ case 'locals':
+ domain = self._l
+ case 'global':
+ domain = self._g
+ vars_dict = {k if not isinstance(k, str) else k.lower(): v for k, v in vars_dict.items()}
+ import re
+ for k, v in (extract or {}).items():
+ d = re.findall('^([0-9A-Za-z_]+).*?', v)[0]
+ locals().__setitem__('r', vars_dict[d.lower()])
+ domain[str(k)] = str(eval(v.replace(d, 'r', 1)))
+ return True
+
+ def _assert(self, assert_items, assert_lists):
+ for assert_item in assert_items:
+ expect = self._to_string(assert_item['expect'])
+ if not expect: continue
+ actual = assert_item['actual']
+ if isinstance(actual, (int, bool, str)):
+ actual = ReText(actual)
+ if isinstance(actual, (tuple, list, set)):
+ actual = ReList(actual)
+ if isinstance(actual, (dict,)):
+ actual = ReDict(actual)
+ assert_rows = 0
+ assert_real = 0
+ for name, value in {
+ json_encode(None): None,
+ json_encode(bool(0)): bool(0),
+ json_encode(bool(1)): bool(1)
+ }.items():
+ locals().__setitem__(name, value)
+ for line in list(filter(lambda x: x, expect.split("\n"))):
+ assert_rows += 1
+ for oper in assert_lists:
+ form = self._parse_formula(re.compile('(\$)(?!{).*?').sub('actual', line), oper['sign'])
+ if not form:
+ continue
+ for i in range(len(form)):
+ form[i] = self._sub_variable_auto(form[i]).replace("\n", "\\n")
+ assert_real += 1
+ oper['reverse'] and form.reverse()
+ assert_meth = oper['method']
+ assert_parm = [eval(form[0]), eval(form[1])]
+ for i in range(len(assert_parm)):
+ try:
+ if assert_parm[i]['_']: assert_parm[i] = str(assert_parm[i])
+ except TypeError as e:
+ pass
+ cast = oper['cast']
+ if cast: assert_parm[i] = cast(assert_parm[i])
+ assert_meth(assert_parm[0], assert_parm[1])
+ break
+ if 1 <= assert_real != assert_rows:
+ raise Exception('wrong assertion statement')
+ if 0 == assert_real:
+ expect = self._sub_variable_auto(expect)
+ actual = str(actual)
+ assert actual == expect, '%s != %s' % (actual, expect)
def test_unit(self, data):
if data['HTTPUri']:
if not data['HTTPChannel']:
raise Exception('channel not set')
+ # 创建请求
http = self.h.where((self.h['Name'] == data['HTTPChannel']), inplace=False).dropna(how='all').reset_index(drop=True, inplace=False).loc[0].to_dict()
res_kwargs = {
'method': data['HTTPMethod'],
- 'url': self._sub_variable_auto((http['HostWithScheme'] or '') + data['HTTPUri'], [self._g, self._l]),
- 'query': self._sub_variable_auto(auto_decode(data['HTTPQuery']), [self._g, self._l]),
+ 'url': self._sub_variable_auto((http['Scheme'] or 'http').lower() + '://' + (http['Host'] or '') + data['HTTPUri']),
+ 'query': self._sub_variable_auto(auto_decode(data['HTTPQuery'])),
'data': None,
'json': None,
'file': None,
'header': {},
- 'cookie': self._sub_variable_auto(auto_decode(data['HTTPCookie']), [self._g, self._l]),
+ 'cookie': self._sub_variable_auto(auto_decode(data['HTTPCookie'])),
'auth': None,
- 'timeout': 15,
+ 'timeout': self._to_number(http['Timeout']) or 15,
'proxy': None,
'auto_redirect': self._match_bool(data['HTTPRedirect']),
'ignore_cert_error': False,
'debug': True
}
res_kwargs['header'].update((auto_decode(http['DefaultHeader']) or {}))
- res_kwargs['header'].update((self._sub_variable_auto(auto_decode(data['HTTPHeader']), [self._g, self._l]) or {}))
- match self._match_parm_type(data['HTTPParamType']):
- case 10:
+ res_kwargs['header'].update((self._sub_variable_auto(auto_decode(data['HTTPHeader'])) or {}))
+ match str(data['HTTPParamType']).strip().title():
+ case 'Json':
res_kwargs['json'] = self._sub_variable_auto(auto_decode(data['HTTPParamContent']))
- case 20:
+ case 'Form':
res_kwargs['data'] = self._sub_variable_auto(auto_decode(data['HTTPParamContent']))
case _:
res_kwargs['data'] = self._sub_variable_auto(data['HTTPParamContent'])
res_kwargs['file'] = locals().setdefault('f', self._sub_variable_auto(auto_decode(
data['HTTPParamOfFile']))) and {k: open(os.path.abspath(os.path.join(self.dirs, './%s' % v)), 'rb') for k, v in locals().get('f').items()}
- res = self.http.http(**res_kwargs)
- for var_name, var_value in {
- '_Status': ReText(res['status'] or ''),
- '_Reason': ReText(res['reason'] or ''),
- '_Header': ReDict(res['header'] or {}).update({'_': "\n".join([k + ': ' + (v or '') for k, v in dict(res['header'] or {}).items()])}),
- '_Cookie': ReDict(res['cookie'] or {}).update({'_': "; ".join([k + '=' + (v or '') for k, v in dict(res['cookie'] or {}).items()])}),
- '_Body': ReText(res['text'] or ''),
- '_Json': ReDict(res['json'] or {}),
- json_encode(None): None,
- json_encode(bool(0)): bool(0),
- json_encode(bool(1)): bool(1)
- }.items():
- locals().__setitem__(var_name, var_value)
- for k, v in (auto_decode(data['HTTPExtract']) or {}).items():
- d = re.findall('^([0-9A-Za-z_]+).*?', v)[0]
- self._g[str(k)] = str(eval('_' + v.replace(d, d.title(), 1)))
- for assert_item in [
- {'expect': data['HTTPAssertStatus'], 'actual': locals().get('_Status')},
- {'expect': data['HTTPAssertReason'], 'actual': locals().get('_Reason')},
- {'expect': data['HTTPAssertHeader'], 'actual': locals().get('_Header')},
- {'expect': data['HTTPAssertCookie'], 'actual': locals().get('_Cookie')},
- {'expect': data['HTTPAssertBody'], 'actual': locals().get('_Body')},
- {'expect': data['HTTPAssertJson'], 'actual': locals().get('_Json')},
- ]:
- actual = assert_item['actual']
- if isinstance(actual, (int, bool, str)):
- actual = ReText(actual)
- if isinstance(actual, (tuple, list, set)):
- actual = ReList(actual)
- if isinstance(actual, (dict, )):
- actual = ReDict(actual)
- expect = self._to_string(assert_item['expect'])
- if expect:
- expect_nums = 0
- expect_flag = 0
- for line in list(filter(lambda x: x, expect.split("\n"))):
- expect_nums += 1
- for operator in [
- {'sign': ' == ', 'method': 'self.unit.assertEqual', 'reverse': 0},
- {'sign': ' != ', 'method': 'self.unit.assertNotEqual', 'reverse': 0},
- {'sign': ' >= ', 'method': 'self.unit.assertGreaterEqual', 'reverse': 0},
- {'sign': ' <= ', 'method': 'self.unit.assertLessEqual', 'reverse': 0},
- {'sign': ' =~ ', 'method': 'self.unit.assertIn', 'reverse': 1},
- {'sign': ' !~ ', 'method': 'self.unit.assertNotIn', 'reverse': 1},
- {'sign': ' > ', 'method': 'self.unit.assertGreater', 'reverse': 0},
- {'sign': ' < ', 'method': 'self.unit.assertLess', 'reverse': 0},
- {'sign': ' not in ', 'method': 'self.unit.assertNotIn', 'reverse': 0},
- {'sign': ' in ', 'method': 'self.unit.assertIn', 'reverse': 0},
- ]:
- formula_args = self._parse_formula(self._sub_variable_auto(line).replace('$', 'actual'), operator['sign'])
- if formula_args:
- expect_flag += 1
- operator['reverse'] and formula_args.reverse()
- _assert = eval(operator['method'])
- _assert_parm = [eval(formula_args[0]), eval(formula_args[1])]
- for i in range(len(_assert_parm)):
- try:
- if isinstance(_assert_parm[i], ReDict) and _assert_parm[i]['_']: _assert_parm[i] = str(_assert_parm[i])
- except TypeError as e:
- pass
- _assert(_assert_parm[0], _assert_parm[1])
- break
- if 1 <= expect_flag != expect_nums:
- raise Exception('wrong assertion statement')
- if 0 == expect_flag:
- self.unit.assertEqual(str(actual), expect)
- if data['DatabaseSql']:
- if not data['DatabaseChannel']:
+ res = [self.request, self.session][self._match_bool(http['Session'])].http(**res_kwargs)
+ # 创建变量
+ hvar = {
+ 'Status': ReText(res['status'] or ''),
+ 'Reason': ReText(res['reason'] or ''),
+ 'Header': ReDict(res['header'] or {}).update({'_': "\n".join([k + ': ' + (v or '') for k, v in dict(res['header'] or {}).items()])}),
+ 'Cookie': ReDict(res['cookie'] or {}).update({'_': "; ".join([k + '=' + (v or '') for k, v in dict(res['cookie'] or {}).items()])}),
+ 'Body': ReText(res['text'] or ''),
+ 'Json': ReDict(res['json'] or {}),
+ }
+ # 提取变量
+ self._extract_variable(extract=data['HTTPExtract'], vars_dict=hvar, domain='global')
+ # 开始断言
+ self._assert([
+ {'expect': data['HTTPAssertStatus'], 'actual': hvar['Status']},
+ {'expect': data['HTTPAssertReason'], 'actual': hvar['Reason']},
+ {'expect': data['HTTPAssertHeader'], 'actual': hvar['Header']},
+ {'expect': data['HTTPAssertCookie'], 'actual': hvar['Cookie']},
+ {'expect': data['HTTPAssertBody'], 'actual': hvar['Body']},
+ {'expect': data['HTTPAssertJson'], 'actual': hvar['Json']},
+ ], self.assert_list)
+ if data['BaseSql']:
+ if not data['BaseChannel']:
raise Exception('channel not set')
- base = self.b.where((self.b['Name'] == data['DatabaseChannel']), inplace=False).dropna(how='all').reset_index(drop=True, inplace=False).loc[0].to_dict()
- # print(base)
+ base = self.b.where((self.b['Name'] == data['BaseChannel']), inplace=False).dropna(how='all').reset_index(drop=True, inplace=False).loc[0].to_dict()
+ # 创建请求
conn = MySQL().connect(
host=base['Host'], port=int(base['Port']),
user=base['User'], password=base['Password'], database=base['Database'], charset=base['Charset'], cursor='Dict'
)
- conn.execute(data['DatabaseSql'])
- # print(conn.fetchall())
- # print(conn.count())
- for var_name, var_value in {
- '_Data': ReList(conn.fetchall() or [])
- }.items():
- locals().__setitem__(var_name, var_value)
- for k, v in (auto_decode(data['DatabaseExtract']) or {}).items():
- d = re.findall('^([0-9A-Za-z_]+).*?', v)[0]
- self._g[str(k)] = str(eval('_' + v.replace(d, d.title(), 1)))
-
- print(self._g)
+ conn.execute(data['BaseSql'])
+ # 创建变量
+ bvar = {
+ 'Data': ReList(conn.fetchall() or []),
+ 'Rows': conn.count() or 0
+ }
+ # 提取变量
+ self._extract_variable(extract=data['BaseExtract'], vars_dict=bvar, domain='global')
+ # for var_name, var_value in {
+ # '_Data': ReList(conn.fetchall() or []),
+ # '_Rows': conn.count() or 0
+ # }.items():
+ # locals().__setitem__(var_name, var_value)
+ # for k, v in (auto_decode(data['DatabaseExtract']) or {}).items():
+ # d = re.findall('^([0-9A-Za-z_]+).*?', v)[0]
+ # self._g[str(k)] = str(eval('_' + v.replace(d, d.title(), 1)))
+ # 开始断言
+ self._assert([
+ {'expect': data['BaseAssertData'], 'actual': bvar['Data']},
+ {'expect': data['BaseAssertRows'], 'actual': bvar['Rows']},
+ ], self.assert_list)
+ print(self._g)
# expect = self._to_string(data['HTTPAssertStatus'])
# if expect:
diff --git a/main4.py b/main4.py
index cc24b9d..762fee8 100644
--- a/main4.py
+++ b/main4.py
@@ -1,9 +1,5 @@
import unittest
-# ut = unittest.TestCase()
-# ut.assertEqual('1', '1')
-# # ut.assertIn('1234', '123')
-# ut.assertLessEqual('4','3')
-# ut.assert
-l = [1, 2]
-print(l.reverse())
-print(l)
\ No newline at end of file
+
+unit = unittest.TestCase(
+)
+unit.assertEqual("阿凡\n凡凡", '123')
diff --git a/main5.py b/main5.py
new file mode 100644
index 0000000..d9e59e2
--- /dev/null
+++ b/main5.py
@@ -0,0 +1,11 @@
+def assertPreWith(string: str, sub: str):
+ if not string.startswith(sub):
+ raise AssertionError('%s does not starts with %s' % (string, sub))
+
+
+def assertSufWith(string: str, sub: str):
+ if not string.endswith(sub):
+ raise AssertionError('%s does not ends with %s' % (string, sub))
+
+assertSufWith('admin 123456 ...', '...')
+assertPreWith('admin 123456 ...', 'admin 1')
\ No newline at end of file