diff --git a/UnitAuto-Admin/apijson/CodeUtil.js b/UnitAuto-Admin/apijson/CodeUtil.js index d8b2299..9472f68 100644 --- a/UnitAuto-Admin/apijson/CodeUtil.js +++ b/UnitAuto-Admin/apijson/CodeUtil.js @@ -6036,6 +6036,7 @@ var CodeUtil = { return valuesIsNotString ? ' ! value必须是String类型!且必须符合 countArray 这种方法名格式!' : (isWarning ? '' : CodeUtil.getComment('被调用方法名', false, ' ')); case 'constructor': return valuesIsNotString ? ' ! value必须是String类型!且必须符合 getInstance 这种方法名格式!' : (isWarning ? '' : CodeUtil.getComment('获取类实例的方法名,一般用于单例模式类', false, ' ')); + case 'args': case 'methodArgs': case 'classArgs': if (value == null || value instanceof Array) { @@ -6535,6 +6536,7 @@ var CodeUtil = { if (CodeUtil.getType4Request(value) == 'string') { return isWarning ? '' : '获取类实例的方法名,一般用于单例模式类'; } + case 'args': case 'methodArgs': case 'classArgs': if (value == null || value instanceof Array) { diff --git a/UnitAuto-Admin/index.html b/UnitAuto-Admin/index.html index 4dcb39a..6e6ea51 100755 --- a/UnitAuto-Admin/index.html +++ b/UnitAuto-Admin/index.html @@ -315,11 +315,11 @@ { "static": true, "methodArgs": [ - { // 可省略来自动判断的 type: Boolean,Integer,BigDecimal,String,JSONObject,JSONArray + { // 可省略来自动判断的 type: Boolean,Integer,BigDecimal,String,JSONObject,JSONArray "type": "long", "value": 1 }, - { // 这个对象还可以简化为 "int:2" 这种字符串形式,方便手动调试。在键值对后按 Enter 回车键自动智能生成补全提示 + { // 这个对象还可以简化为 "int:2" 这种字符串形式,方便手动调试。在键值对后按 Enter 回车键自动智能生成补全提示 "type": "long", "value": 2 } @@ -978,14 +978,25 @@ var vRemember = document.getElementById("vRemember"); var lang = App == null ? null : App.language; - vUrl.value = lang == CodeUtil.LANGUAGE_GO ? 'test.Divide.Divide' : 'unitauto.test.TestUtil.divide' //main.js里访问不到,可能是script引用顺序问题 + vUrl.value = lang == CodeUtil.LANGUAGE_GO ? 'test.Divide' : (lang == CodeUtil.LANGUAGE_PYTHON ? 'unitauto.test.testutil.divide' : 'unitauto.test.TestUtil.divide') //main.js里访问不到,可能是script引用顺序问题 vInput.value = (lang == CodeUtil.LANGUAGE_GO ? `{ "methodArgs": [ - { // 可省略来自动判断的 type: bool,float64,string,map[string]any,[]any + { // 可省略来自动判断的 type: bool,float64,string,map[string]any,[]any "type": "int", "value": 1 }, - { // 这个对象还可以简化为 "int:2" 这种字符串形式,方便手动调试。在键值对后按 Enter 回车键自动智能生成补全提示 + { // 这个对象还可以简化为 "int:2" 这种字符串形式,方便手动调试。在键值对后按 Enter 回车键自动智能生成补全提示 + "type": "int", + "value": 2 + } + ] +}` : (lang == CodeUtil.LANGUAGE_PYTHON ? `{ + "args": [ + { // 可省略来自动判断的 type: bool,int,float,str,dict,list + "type": "int", + "value": 1 + }, + { // 这个对象还可以简化为 "int:2" 这种字符串形式,方便手动调试。在键值对后按 Enter 回车键自动智能生成补全提示 "type": "int", "value": 2 } @@ -993,16 +1004,16 @@ }` : `{ "static": true, "methodArgs": [ - { // 可省略来自动判断的 type: Boolean,Integer,BigDecimal,String,JSONObject,JSONArray + { // 可省略来自动判断的 type: Boolean,Integer,BigDecimal,String,JSONObject,JSONArray "type": "long", "value": 1 }, - { // 这个对象还可以简化为 "int:2" 这种字符串形式,方便手动调试。在键值对后按 Enter 回车键自动智能生成补全提示 + { // 这个对象还可以简化为 "int:2" 这种字符串形式,方便手动调试。在键值对后按 Enter 回车键自动智能生成补全提示 "type": "long", "value": 2 } ] -}`) + ` +}`)) + ` /* 以上 JSON 文本支持 JSON5 格式。清空文本内容可查看规则。 diff --git a/UnitAuto-Go/README.md b/UnitAuto-Go/README.md index c38cd95..96baf52 100644 --- a/UnitAuto-Go/README.md +++ b/UnitAuto-Go/README.md @@ -9,6 +9,8 @@ UnitAuto Go Library for remote dependencies with GitHub repo, etc. image image +**Demo:** https://github.com/TommyLemon/unitauto-go-demo + #### 1. 在 go.mod 中添加 GitHub 仓库 #### 1. Add the GitHub repository to go.mod ```go @@ -42,3 +44,32 @@ https://github.com/TommyLemon/unitauto-go/blob/main/main.go image image +
+ +### 4. 关于作者 +### 4. Author +[https://github.com/TommyLemon](https://github.com/TommyLemon)
+image + +如果有什么问题或建议可以 [去 APIAuto 提 issue](https://github.com/TommyLemon/APIAuto/issues),交流技术,分享经验。
+如果你解决了某些 bug,或者新增了一些功能,欢迎 [提 PR 贡献代码](https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md),感激不尽。 +
+If you have any questions or suggestions, you can [create an issue](https://github.com/TommyLemon/APIAuto/issues).
+If you can added a feature or fixed a bug, please [create a pull request](https://github.com/TommyLemon/unitauto-go/pulls), thank you~ + + +### 5. 其它项目 +### 5. Link +创作不易、坚持更难,右上角点 ⭐ Star 支持下吧,谢谢 ^_^ + +[UnitAuto](https://github.com/TommyLemon/UnitAuto) 机器学习零代码单元测试平台,零代码、全方位、自动化 测试 方法/函数 的正确性、可用性和性能 + +[unitauto-go-demo](https://github.com/TommyLemon/unitauto-go-demo) UnitAuto Go Demo,提供用来做单元测试的业务函数 + +[unitauto-py](https://github.com/TommyLemon/unitauto-py) UnitAuto Python 库,可通过 pip 仓库等远程依赖 + +[APIJSON](https://github.com/Tencent/APIJSON) 🚀 腾讯零代码、全功能、强安全 ORM 库 🏆 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构 + +[APIAuto](https://github.com/TommyLemon/APIAuto) 敏捷开发最强大易用的 HTTP 接口工具,机器学习零代码测试、生成代码与静态检查、生成文档与光标悬浮注释,集 文档、测试、Mock、调试、管理 于一体的一站式体验 + +[SQLAuto](https://github.com/TommyLemon/SQLAuto) 智能零代码自动化测试 SQL 语句执行结果的数据库工具,任意增删改查、任意 SQL 模板变量、一键批量生成参数组合、快速构造大量测试数据 diff --git a/UnitAuto-Go/unitauto/method_util.go b/UnitAuto-Go/unitauto/method_util.go index d4dc4f4..05f0e46 100644 --- a/UnitAuto-Go/unitauto/method_util.go +++ b/UnitAuto-Go/unitauto/method_util.go @@ -76,6 +76,7 @@ var KEY_RETURN = "return" var KEY_TIME_DETAIL = "time:start|duration|end" var KEY_CLASS_ARGS = "classArgs" var KEY_METHOD_ARGS = "methodArgs" +var KEY_ARGS = "args" var KEY_CALLBACK = "callback" var KEY_GLOBAL = "global" @@ -565,6 +566,16 @@ func InvokeMethod(req map[string]any, instance any, listener Listener[any]) erro var this_ = GetMap(req, KEY_THIS) var clsArgs = GetArgList(req, KEY_CLASS_ARGS) var methodArgs = GetArgList(req, KEY_METHOD_ARGS) + var args = GetArgList(req, KEY_ARGS) + if len(args) > 0 { + if methodArgs != nil { + err := errors.New(KEY_ARGS + " 和 " + KEY_METHOD_ARGS + " 不能同时传!") + completeWithError(pkgName, clsName, methodName, startTime, err, listener) + return err + } + + methodArgs = args + } if IsEmpty(cttName, true) && len(clsArgs) > 0 { err := errors.New("Go 没有构造函数,不允许单独传 " + KEY_CLASS_ARGS + " ,必须配合 " + KEY_CONSTRUCTOR + " 一起用!") diff --git a/UnitAuto-Python/README.md b/UnitAuto-Python/README.md index 7f950f2..e3b8b41 100644 --- a/UnitAuto-Python/README.md +++ b/UnitAuto-Python/README.md @@ -54,3 +54,32 @@ if you cannot run the command successfully, try python3: #### 3. Test by following the main repo https://github.com/TommyLemon/UnitAuto + +
+ +### 4. 关于作者 +### 4. Author +[https://github.com/TommyLemon](https://github.com/TommyLemon)
+image + +如果有什么问题或建议可以 [去 APIAuto 提 issue](https://github.com/TommyLemon/APIAuto/issues),交流技术,分享经验。
+如果你解决了某些 bug,或者新增了一些功能,欢迎 [提 PR 贡献代码](https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md),感激不尽。 +
+If you have any questions or suggestions, you can [create an issue](https://github.com/TommyLemon/APIAuto/issues).
+If you can added a feature or fixed a bug, please [create a pull request](https://github.com/TommyLemon/unitauto-py/pulls), thank you~ + +
+ +### 5. 其它项目 +### 5. Link +创作不易、坚持更难,右上角点 ⭐ Star 支持下吧,谢谢 ^_^ + +[UnitAuto](https://github.com/TommyLemon/UnitAuto) 机器学习零代码单元测试平台,零代码、全方位、自动化 测试 方法/函数 的正确性、可用性和性能 + +[unitauto-go](https://github.com/TommyLemon/unitauto-go) UnitAuto Go 库,可通过 GitHub 仓库等远程依赖 + +[APIJSON](https://github.com/Tencent/APIJSON) 🚀 腾讯零代码、全功能、强安全 ORM 库 🏆 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构 + +[APIAuto](https://github.com/TommyLemon/APIAuto) 敏捷开发最强大易用的 HTTP 接口工具,机器学习零代码测试、生成代码与静态检查、生成文档与光标悬浮注释,集 文档、测试、Mock、调试、管理 于一体的一站式体验 + +[SQLAuto](https://github.com/TommyLemon/SQLAuto) 智能零代码自动化测试 SQL 语句执行结果的数据库工具,任意增删改查、任意 SQL 模板变量、一键批量生成参数组合、快速构造大量测试数据 diff --git a/UnitAuto-Python/main.py b/UnitAuto-Python/main.py index 9f7ceb2..8120315 100644 --- a/UnitAuto-Python/main.py +++ b/UnitAuto-Python/main.py @@ -6,6 +6,7 @@ def callback(): pass + # methodutil.config.DEFAULT_MODULE_PATH = "unitauto" # methodutil.listener.callback = callback test() diff --git a/UnitAuto-Python/unitauto/methodutil.py b/UnitAuto-Python/unitauto/methodutil.py index a4bc7b3..ecec893 100644 --- a/UnitAuto-Python/unitauto/methodutil.py +++ b/UnitAuto-Python/unitauto/methodutil.py @@ -87,9 +87,19 @@ MILLIS_TIME = 1000000 PATTERN_ALPHABET = re.compile('^[A-Za-z]+$') +PATTERN_UPPER_ALPHABET = re.compile('^[A-Z]+$') +PATTERN_LOWER_ALPHABET = re.compile('^[a-z]+$') PATTERN_NUMBER = re.compile('^[0-9]+$') PATTERN_NAME = re.compile('^[A-Za-z0-9_]+$') + +class Config: + DEFAULT_MODULE_PATH: str = '' + + +config = Config() + + PRIMITIVE_CLASS_MAP = { None: any, 'None': any, @@ -339,7 +349,7 @@ def list_method(req, import_fun: callable = null) -> dict: if is_file: continue - mtd = parse_method(cls) if is_all_mtd or cls.__name__ == method else null + mtd = parse_method(cls, import_fun=import_fun) if is_all_mtd or cls.__name__ == method else null if is_empty(mtd): continue @@ -413,7 +423,7 @@ def list_method(req, import_fun: callable = null) -> dict: mtd_list = [] for mtd in ml: - m = parse_method(mtd) + m = parse_method(mtd, import_fun=import_fun) if not_empty(m): mtd_list.append(m) @@ -462,21 +472,68 @@ def list_method(req, import_fun: callable = null) -> dict: } -def parse_method(func) -> dict: +def get_type_str_by_str(s: str, keep_prefix: bool = false, import_fun: callable = null) -> str: + start = s.index("'") if is_contain(s, "'") else -1 + if start >= 0: + s = s[start + 1:] + end = s.index("'") if is_contain(s, "'") else -1 + if end >= 0: + s = s[:end] + s = s.strip() + + if s in (null, '', '_empty', 'inspect._empty', 'NoneType', 'POSITIONAL_OR_KEYWORD'): + return null + + dmp = null if keep_prefix else config.DEFAULT_MODULE_PATH + if not_empty(dmp) and s.startswith(dmp): + s = s[len(dmp) + 1:] + + # if s.startswith('__init__.'): + # return s[len('__init__.'):] + + s = s.replace('.__init__.', '$') + if '$' not in s and '.' in s: + ks = split(s, '.') + # util.Test.Test2 会误判为 util.Test$Test2,实际应该为 util$Test.Test2 s = '.'.join(ks[:-1]) + '$' + ks[-1] + + ns = ks[0] + ks = ks[1:] + is_inner_cls = false + for k in ks: + if (not is_inner_cls) and not is_module_path(ns + '.' + k, import_fun): # is_big_name(k) + is_inner_cls = true + + ns += ('$' if is_inner_cls else '.') + k + + return ns + + return s + + +def is_module_path(path: str, import_fun: callable = null) -> bool: + try: + import_fun = import_fun or __import__ + m = import_fun(path) + return not_none(m) + except Exception as e: + print(e) + return false + + +def get_type_str(return_annotation, instance: any = null, keep_prefix: bool = false, import_fun: callable = null) -> str: + rt = get_type_str_by_str(str(return_annotation), keep_prefix=keep_prefix, import_fun=import_fun) + if is_empty(rt): + rt = get_type_str_by_str(str(type(instance)), keep_prefix=keep_prefix, import_fun=import_fun) + return rt + + +def parse_method(func, import_fun: callable = null) -> dict: signature = null if func is None else inspect.signature(func) if signature is None: return {} return_annotation = signature.return_annotation - rt = null - if return_annotation is not None: - try: - rt = return_annotation.__name__ - except Exception as e: - rt = str(return_annotation) - - if rt in ('_empty', 'POSITIONAL_OR_KEYWORD'): - rt = null + rt = get_type_str(return_annotation, keep_prefix=true, import_fun=import_fun) types = [] for param in signature.parameters.values(): @@ -508,21 +565,13 @@ def parse_method(func) -> dict: def wrap_result( instance, func, method_args, ma_types, ma_values, result, start_time, - json_dumps: callable = null, json_loads: callable = null + json_dumps: callable = null, json_loads: callable = null, import_fun: callable = null ): time_detail = get_time_detail(start_time) signature = inspect.signature(func) return_annotation = null if signature is None else signature.return_annotation - rt = null - if return_annotation is not None: - try: - rt = return_annotation.__name__ - if rt in ('_empty', 'POSITIONAL_OR_KEYWORD'): - rt = type(result).__name__ - except Exception as e: - print(e) - rt = type(result).__name__ # str(return_annotation) + rt = get_type_str(return_annotation, result, import_fun=import_fun) mal = size(method_args) mas = [null] * mal @@ -551,6 +600,16 @@ def wrap_result( } if is_none(instance): + if result is None and is_empty(rt): + return { + KEY_LANGUAGE: LANGUAGE, + KEY_OK: true, + KEY_CODE: CODE_SUCCESS, + KEY_MSG: MSG_SUCCESS, + KEY_METHOD_ARGS: mas, + KEY_TIME_DETAIL: time_detail + } + return { KEY_LANGUAGE: LANGUAGE, KEY_OK: true, @@ -584,6 +643,17 @@ def wrap_result( this[KEY_VALUE] = str(instance) this[KEY_WARN] = str(e) + if result is None and is_empty(rt): + return { + KEY_LANGUAGE: LANGUAGE, + KEY_OK: true, + KEY_CODE: CODE_SUCCESS, + KEY_MSG: MSG_SUCCESS, + KEY_METHOD_ARGS: mas, + KEY_THIS: this, + KEY_TIME_DETAIL: time_detail + } + return { KEY_LANGUAGE: LANGUAGE, KEY_OK: true, @@ -678,7 +748,10 @@ def invoke_method( def final_callback(*args, **kwargs): if not_none(callback): # callable(callback): - callback(wrap_result(final_result.get(KEY_THIS), func, method_args, ma_types, ma_values, final_result.get(KEY_VALUE), start_time)) + callback(wrap_result( + final_result.get(KEY_THIS), func, method_args, ma_types, ma_values, + final_result.get(KEY_VALUE), start_time, import_fun=import_fun + )) is_wait = init_args(method_args, ma_keys, ma_types, ma_values, m_kwargs, true, final_callback, import_fun=import_fun) @@ -691,11 +764,19 @@ def final_callback(*args, **kwargs): cls: any = null if l > 1: i = -1 + m = module for n in fl: i += 1 if i <= 0: continue - cls = getattr(module, n) + # if i > 1: + # n = 'Test$InnerTest' # 'Test.InnerTest' + # # 'Test' object has no attribute 'InnerTest' # m = m() + m = getattr(m, n) # FIXME Test.InnerTest raise Exception: type object 'Test' has no attribute 'InnerTest' + + cls = m + # 'testutil' object has no attribute 'Test.InnerTest' cls = getattr(module, '.'.join(fl[1:])) + # 'testutil' object has no attribute 'Test.InnerTest' cls = getattr(module, '$'.join(fl[1:])) if cls is None: func = getattr(module, method) @@ -724,7 +805,7 @@ def final_callback(*args, **kwargs): final_result[KEY_VALUE] = result res = wrap_result( instance, func, method_args, ma_types, ma_values, result, start_time, - json_dumps=json_dumps, json_loads=json_loads + json_dumps=json_dumps, json_loads=json_loads, import_fun=import_fun ) except Exception as e: res = { @@ -1017,6 +1098,12 @@ def is_name(s: str) -> bool: return not_none(PATTERN_NAME.match(s)) +def is_big_name(s: str) -> bool: + if is_empty(s) or not PATTERN_UPPER_ALPHABET.match(s[:1]): + return false + return not_none(PATTERN_NAME.match(s)) + + def size(obj) -> int: if obj is None: return 0 diff --git a/UnitAuto-Python/unitauto/test/testutil.py b/UnitAuto-Python/unitauto/test/testutil.py index 257d75a..ff3f96f 100644 --- a/UnitAuto-Python/unitauto/test/testutil.py +++ b/UnitAuto-Python/unitauto/test/testutil.py @@ -136,6 +136,18 @@ def is_female(self) -> bool: def get_sex_str(self) -> str: return 'Male' if self.is_male() else 'Female' + class InnerTest: + id: int = 0 + name: str = 'InnerTest' + + def __init__(self, id: int = 0, name: str = ''): + self.id = id + self.name = name + + @staticmethod + def get_instance(id: int = 0, name: str = '') -> 'Test.InnerTest': + return Test.InnerTest(id=id, name=name) + def get_test_instance(id: int = 0, sex: int = 0, name: str = '') -> Test: return Test(id=id, sex=sex, name=name)