From facd86beb6ceaffc2adb1476e40ac4d594afac0b Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Sat, 18 Sep 2021 18:03:18 +0800 Subject: [PATCH 01/30] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=96=AD=E8=A8=80=20?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E7=B1=BB=E5=9E=8B=20=E5=A2=9E=E5=8A=A0=20?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E5=85=B3=E8=81=94=E7=9B=B8=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/checkResult.py | 2 ++ scripts/randomData.py | 6 +++--- scripts/readYamlFile.py | 5 +++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/common/checkResult.py b/common/checkResult.py index 839219f..248f60e 100644 --- a/common/checkResult.py +++ b/common/checkResult.py @@ -94,6 +94,7 @@ def assert_text(hope_res, real_res,third_data=None): raise elif h_res["asserttype"] == "!=": try: + h_res["value"] = replace_random(str(h_res["value"]), res=third_data) with allure.step("json断言判断不等"): allure.attach(name="json期望结果", body=str(h_res)) allure.attach(name='json实际实际结果', body=str(r_res)) @@ -105,6 +106,7 @@ def assert_text(hope_res, real_res,third_data=None): elif h_res["asserttype"] == "in": r_res = str(r_res) try: + h_res["value"] = replace_random(str(h_res["value"]), res=third_data) with allure.step("json断言判断包含"): allure.attach(name="期望结果", body=str(h_res)) allure.attach(name='实际实际结果', body=str(r_res)) diff --git a/scripts/randomData.py b/scripts/randomData.py index a9e6288..db688e3 100644 --- a/scripts/randomData.py +++ b/scripts/randomData.py @@ -298,10 +298,10 @@ from alarm {'time': '2021-09-11', 'number': 7}, {'time': '2021-09-12', 'number': 4}, {'time': '2021-09-13', 'number': 22}, {'time': '2021-09-14', 'number': 19}, {'time': '2021-09-15', 'number': 38}, {'time': '2021-09-16', 'number': 39}]} - print(replace_random(js, res=res1)) - t = "$GetTime(time_type=past,layout=%Y-%m-%d 00:00:00,unit=0,0,0,1,4)$" + # print(replace_random(js, res=res1)) + t = "$GetTime(time_type=past,layout=10timestamp,unit=0,0,0,0,0)$" # print(replace_random(choice_num)) - # print(replace_random(t)) + print(replace_random(t)) # pattern = re.compile(r'\$json\(' + '$.data.alarm[1].number'.replace('$',"\$").replace('[','\[') + r'\)\$') # # key = str(sql_json(i)) # key = "123" diff --git a/scripts/readYamlFile.py b/scripts/readYamlFile.py index ded3c20..ead1d1d 100644 --- a/scripts/readYamlFile.py +++ b/scripts/readYamlFile.py @@ -7,7 +7,8 @@ datapath = dir_manage("${pro_dir}$") + dir_manage("${test_suite}$")+dir_manage(" def ini_yaml(filename, path=datapath): - with open(path + "/" + filename, 'r', encoding="utf-8") as f: + # encoding="utf-8" 视情况加 + with open(path + "/" + filename, 'r') as f: file_data = f.read() data = yaml.load(file_data, Loader=yaml.FullLoader) @@ -16,7 +17,7 @@ def ini_yaml(filename, path=datapath): if __name__ == '__main__': # get_yaml_data(r"F:\api2.0\config\runConfig.yml") - runConfig_dict = ini_yaml("thirdData.yml") + runConfig_dict = ini_yaml("123.yml") # case_level = runConfig_dict[0]["address"].format(**{"home_id": "123"}) print(runConfig_dict) # print(case_level) -- Gitee From 38ce07d5178e1a81c1f201429d79a070810d05e0 Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Wed, 22 Sep 2021 11:22:53 +0800 Subject: [PATCH 02/30] =?UTF-8?q?=E5=A2=9E=E5=8A=A0host=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/basePage.py | 8 ++++---- config/confManage.py | 4 +++- config/confRead.py | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/common/basePage.py b/common/basePage.py index 1fd2c94..681745c 100644 --- a/common/basePage.py +++ b/common/basePage.py @@ -18,8 +18,8 @@ Log() class apiSend(object): - def __init__(self): - self.host = host_manage(hos="${host}$") + def __init__(self,host="host"): + self.host = host_manage(hos="${{{}}}$".format(host)) self.http_type = host_manage(hos="${http_type}$") @staticmethod @@ -222,5 +222,5 @@ class apiSend(object): apisend = apiSend() if __name__ == '__main__': - apisend.get() - apisend() \ No newline at end of file + a = apiSend() + print(a.host) \ No newline at end of file diff --git a/config/confManage.py b/config/confManage.py index c7982f2..4eff3c8 100644 --- a/config/confManage.py +++ b/config/confManage.py @@ -91,4 +91,6 @@ if __name__ == '__main__': # print(db_manage("${database}$")) # print(db_manage("${charset}$")) # print(int(db_manage("${port}$"))) - print(dingding_manage("${webhook}$")) + print(host_manage("${host}$")) + print("${{{haha}}}$".format(**{"haha":"123"})) + print(host_manage("${{{haha}}}$".format(**{"haha":"host2"}))) diff --git a/config/confRead.py b/config/confRead.py index 0c8fd0a..eaf76f6 100644 --- a/config/confRead.py +++ b/config/confRead.py @@ -48,4 +48,4 @@ class Config(object): if __name__ == '__main__': c = Config() - print(c.read_db()["host"]) + print(c.read_host()["host"]) -- Gitee From ca1174d411e28e6c9677bb51740b9244716c8c1a Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Wed, 22 Sep 2021 11:44:56 +0800 Subject: [PATCH 03/30] =?UTF-8?q?=E5=A2=9E=E5=8A=A0host=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/basePage.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/common/basePage.py b/common/basePage.py index 681745c..5e31546 100644 --- a/common/basePage.py +++ b/common/basePage.py @@ -18,8 +18,7 @@ Log() class apiSend(object): - def __init__(self,host="host"): - self.host = host_manage(hos="${{{}}}$".format(host)) + def __init__(self): self.http_type = host_manage(hos="${http_type}$") @staticmethod @@ -32,9 +31,10 @@ class apiSend(object): logging.info("请求参数: %s" % str(dataran)) return dataran - def post(self, address, header, request_parameter_type="json", timeout=8, data=None, files=None): + def post(self, address, header, request_parameter_type="json", timeout=8, data=None, files=None,host="host"): """ post请求 + :param host: :param address: 请求地址 :param header: 请求头 :param request_parameter_type: 请求参数格式(form_data,raw) @@ -44,7 +44,7 @@ class apiSend(object): :return: """ - url = str(self.http_type) + "://" + self.host + address + url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + address logging.info("请求地址:%s" % "" + str(address)) logging.info("请求头: %s" % str(header)) @@ -100,9 +100,10 @@ class apiSend(object): logging.error(e) raise - def get(self, address, header, data, timeout=8): + def get(self, address, header, data, timeout=8,host="host"): """ get请求 + :param host: :param address: :param header: 请求头 :param data: 请求参数 @@ -110,7 +111,7 @@ class apiSend(object): :return: """ data_random = self.iniDatas(data) - url = str(self.http_type) + "://" + host_manage(hos='${host}$') + address + url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + address logging.info("请求地址:%s" % "" + str(address)) logging.info("请求头: %s" % str(header)) logging.info("请求参数: %s" % str(data_random)) @@ -134,9 +135,10 @@ class apiSend(object): logging.error(e) raise - def put(self, address, header, request_parameter_type="json", timeout=8, data=None, files=None): + def put(self, address, header, request_parameter_type="json", timeout=8, data=None, files=None,host="host"): """ put请求 + :param host: :param address: :param header: 请求头 :param request_parameter_type: 请求参数格式(form_data,raw) @@ -146,7 +148,7 @@ class apiSend(object): :return: """ - url = str(self.http_type) + "://" + host_manage(hos='${host}$') + address + url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + address logging.info("请求地址:%s" % "" + str(address)) logging.info("请求头: %s" % str(header)) @@ -172,9 +174,10 @@ class apiSend(object): logging.error(e) raise - def delete(self, address, header, data, timeout=8): + def delete(self, address, header, data, timeout=8,host="host"): """ get请求 + :param host: :param address: :param header: 请求头 :param data: 请求参数 @@ -182,7 +185,7 @@ class apiSend(object): :return: """ data_random = self.iniDatas(data) - url = str(self.http_type) + "://" + host_manage(hos='${host}$') + address + url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + address logging.info("请求地址:%s" % "" + str(address)) logging.info("请求头: %s" % str(header)) @@ -217,10 +220,11 @@ class apiSend(object): else: raise TypeError(f"请求异常,检查yml文件method") except Exception: - raise TypeError(f"请求异常,检查yml文件method") + raise apisend = apiSend() if __name__ == '__main__': - a = apiSend() - print(a.host) \ No newline at end of file + apisend("/123123","get",data="er",headers={ + "Content-Type": "application/json" + },host="host2") \ No newline at end of file -- Gitee From f1c7c5b29c5f25463e4e6cd2a5eb08112d444572 Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Wed, 22 Sep 2021 15:41:02 +0800 Subject: [PATCH 04/30] =?UTF-8?q?=E5=A2=9E=E5=8A=A0host=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + common/basePage.py | 7 ++-- scripts/randomData.py | 16 ++++++-- scripts/readYamlFile.py | 2 +- scripts/writePage.py | 2 +- test_suite/page/saasApp_pages.py | 42 ++++++++++++++------- test_suite/page/saasApp_pages_1.py | 42 ++++++++++++++------- test_suite/page/third_pages.py | 9 ++++- test_suite/testcase/saasApp/test_power.py | 46 +++++++++++++++++++++++ 9 files changed, 131 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 69c3773..52f3b5f 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ login: name: '登录' + host: 'host' address: '/v1/apps/login/' method: 'post diff --git a/common/basePage.py b/common/basePage.py index 5e31546..4a09e1a 100644 --- a/common/basePage.py +++ b/common/basePage.py @@ -28,7 +28,6 @@ class apiSend(object): if data is None: return data dataran = replace_random(data) - logging.info("请求参数: %s" % str(dataran)) return dataran def post(self, address, header, request_parameter_type="json", timeout=8, data=None, files=None,host="host"): @@ -112,7 +111,7 @@ class apiSend(object): """ data_random = self.iniDatas(data) url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + address - logging.info("请求地址:%s" % "" + str(address)) + logging.info("请求地址:%s" % "" + url) logging.info("请求头: %s" % str(header)) logging.info("请求参数: %s" % str(data_random)) @@ -149,7 +148,7 @@ class apiSend(object): """ url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + address - logging.info("请求地址:%s" % "" + str(address)) + logging.info("请求地址:%s" % "" + url) logging.info("请求头: %s" % str(header)) with allure.step("PUT请求接口"): @@ -186,7 +185,7 @@ class apiSend(object): """ data_random = self.iniDatas(data) url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + address - logging.info("请求地址:%s" % "" + str(address)) + logging.info("请求地址:%s" % "" + url) logging.info("请求头: %s" % str(header)) with allure.step("DELETE请求接口"): diff --git a/scripts/randomData.py b/scripts/randomData.py index db688e3..058d224 100644 --- a/scripts/randomData.py +++ b/scripts/randomData.py @@ -148,16 +148,26 @@ def get_time(time_type, layout, unit="0,0,0,0,0"): hours=int(resolution[2]), days=int(resolution[3]), weeks=int(resolution[4])) except ValueError: raise Exception("获取时间错误,时间单位%s" % unit) - if layout == "10timestamp": + if layout == "10timestampNOW": ti = ti.strftime('%Y-%m-%d %H:%M:%S') ti = int(time.mktime(time.strptime(ti, "%Y-%m-%d %H:%M:%S"))) return ti - elif layout == "13timestamp": + elif layout == "13timestampNOW": ti = ti.strftime('%Y-%m-%d %H:%M:%S') ti = int(time.mktime(time.strptime(ti, '%Y-%m-%d %H:%M:%S'))) # round()是四舍五入 ti = int(round(ti * 1000)) return ti + elif layout == "13timestampDAY": + ti = ti.strftime('%Y-%m-%d 00:00:00') + ti = int(time.mktime(time.strptime(ti, '%Y-%m-%d %H:%M:%S'))) + # round()是四舍五入 + ti = int(round(ti * 1000)) + return ti + elif layout == "10timestampDAYA": + ti = ti.strftime('%Y-%m-%d 00:00:00') + ti = int(time.mktime(time.strptime(ti, "%Y-%m-%d %H:%M:%S"))) + return ti else: ti = ti.strftime(layout) return ti @@ -299,7 +309,7 @@ from alarm {'time': '2021-09-13', 'number': 22}, {'time': '2021-09-14', 'number': 19}, {'time': '2021-09-15', 'number': 38}, {'time': '2021-09-16', 'number': 39}]} # print(replace_random(js, res=res1)) - t = "$GetTime(time_type=past,layout=10timestamp,unit=0,0,0,0,0)$" + t = "$GetTime(time_type=past,layout=13timestampDAY,unit=0,0,0,1,4)$" # print(replace_random(choice_num)) print(replace_random(t)) # pattern = re.compile(r'\$json\(' + '$.data.alarm[1].number'.replace('$',"\$").replace('[','\[') + r'\)\$') diff --git a/scripts/readYamlFile.py b/scripts/readYamlFile.py index ead1d1d..6fa7b92 100644 --- a/scripts/readYamlFile.py +++ b/scripts/readYamlFile.py @@ -17,7 +17,7 @@ def ini_yaml(filename, path=datapath): if __name__ == '__main__': # get_yaml_data(r"F:\api2.0\config\runConfig.yml") - runConfig_dict = ini_yaml("123.yml") + runConfig_dict = ini_yaml("loginData.yml") # case_level = runConfig_dict[0]["address"].format(**{"home_id": "123"}) print(runConfig_dict) # print(case_level) diff --git a/scripts/writePage.py b/scripts/writePage.py index 9773179..f153223 100644 --- a/scripts/writePage.py +++ b/scripts/writePage.py @@ -35,7 +35,7 @@ def {testtitle}(casedata):""".format(testtitle=item[0])) f.write( """ logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime\n\n""" ) diff --git a/test_suite/page/saasApp_pages.py b/test_suite/page/saasApp_pages.py index 840c219..b8f14c5 100644 --- a/test_suite/page/saasApp_pages.py +++ b/test_suite/page/saasApp_pages.py @@ -7,7 +7,7 @@ urlData = ini_yaml("urlData.yml") def login(casedata): data = urlData["login"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime @@ -15,7 +15,7 @@ def login(casedata): def forgetPassword(casedata): data = urlData["forgetPassword"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime @@ -23,7 +23,7 @@ def forgetPassword(casedata): def mobileCode(casedata): data = urlData["mobileCode"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime @@ -31,7 +31,7 @@ def mobileCode(casedata): def todayTask(casedata): data = urlData["todayTask"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime @@ -39,7 +39,7 @@ def todayTask(casedata): def companyName(casedata): data = urlData["companyName"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime @@ -47,7 +47,7 @@ def companyName(casedata): def companyPower(casedata): data = urlData["companyPower"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime @@ -55,7 +55,7 @@ def companyPower(casedata): def deviceState(casedata): data = urlData["deviceState"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime @@ -63,7 +63,7 @@ def deviceState(casedata): def companyAlarm(casedata): data = urlData["companyAlarm"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime @@ -71,7 +71,7 @@ def companyAlarm(casedata): def todayTrend(casedata): data = urlData["todayTrend"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime @@ -79,7 +79,7 @@ def todayTrend(casedata): def alarmStatistic(casedata): data = urlData["alarmStatistic"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime @@ -87,7 +87,7 @@ def alarmStatistic(casedata): def alarmTrend(casedata): data = urlData["alarmTrend"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime @@ -95,7 +95,7 @@ def alarmTrend(casedata): def alarmRank(casedata): data = urlData["alarmRank"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime @@ -103,7 +103,23 @@ def alarmRank(casedata): def alarmDistribute(casedata): data = urlData["alarmDistribute"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"]) + return res, restime + + +def powerToday(casedata): + data = urlData["powerToday"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"]) + return res, restime + + +def powerFees(casedata): + data = urlData["powerFees"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime diff --git a/test_suite/page/saasApp_pages_1.py b/test_suite/page/saasApp_pages_1.py index 840c219..b8f14c5 100644 --- a/test_suite/page/saasApp_pages_1.py +++ b/test_suite/page/saasApp_pages_1.py @@ -7,7 +7,7 @@ urlData = ini_yaml("urlData.yml") def login(casedata): data = urlData["login"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime @@ -15,7 +15,7 @@ def login(casedata): def forgetPassword(casedata): data = urlData["forgetPassword"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime @@ -23,7 +23,7 @@ def forgetPassword(casedata): def mobileCode(casedata): data = urlData["mobileCode"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime @@ -31,7 +31,7 @@ def mobileCode(casedata): def todayTask(casedata): data = urlData["todayTask"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime @@ -39,7 +39,7 @@ def todayTask(casedata): def companyName(casedata): data = urlData["companyName"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime @@ -47,7 +47,7 @@ def companyName(casedata): def companyPower(casedata): data = urlData["companyPower"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime @@ -55,7 +55,7 @@ def companyPower(casedata): def deviceState(casedata): data = urlData["deviceState"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime @@ -63,7 +63,7 @@ def deviceState(casedata): def companyAlarm(casedata): data = urlData["companyAlarm"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime @@ -71,7 +71,7 @@ def companyAlarm(casedata): def todayTrend(casedata): data = urlData["todayTrend"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime @@ -79,7 +79,7 @@ def todayTrend(casedata): def alarmStatistic(casedata): data = urlData["alarmStatistic"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime @@ -87,7 +87,7 @@ def alarmStatistic(casedata): def alarmTrend(casedata): data = urlData["alarmTrend"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime @@ -95,7 +95,7 @@ def alarmTrend(casedata): def alarmRank(casedata): data = urlData["alarmRank"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime @@ -103,7 +103,23 @@ def alarmRank(casedata): def alarmDistribute(casedata): data = urlData["alarmDistribute"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"]) + return res, restime + + +def powerToday(casedata): + data = urlData["powerToday"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"]) + return res, restime + + +def powerFees(casedata): + data = urlData["powerFees"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime diff --git a/test_suite/page/third_pages.py b/test_suite/page/third_pages.py index 25b145d..6e31065 100644 --- a/test_suite/page/third_pages.py +++ b/test_suite/page/third_pages.py @@ -12,6 +12,13 @@ urlData = ini_yaml("thirdUrl.yml") def webalarm(casedata): data = urlData["webalarm"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(address=data["address"], method=data["method"], headers=casedata["headers"], + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"]) + return res, restime + +def webFees(casedata): + data = urlData["webFees"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) return res, restime \ No newline at end of file diff --git a/test_suite/testcase/saasApp/test_power.py b/test_suite/testcase/saasApp/test_power.py index 4a4636d..6a2819d 100644 --- a/test_suite/testcase/saasApp/test_power.py +++ b/test_suite/testcase/saasApp/test_power.py @@ -5,3 +5,49 @@ @file: test_power.py @time: 2021/9/17 16:56 """ +from test_suite.page.saasApp_pages import * +from test_suite.page.third_pages import * + +paramData = ini_yaml("powerData.yml") +thirdData = ini_yaml("thirdData.yml") + + +class Test_power(object): + # def setup(self): + # self.re = saasPages() + # ids=[i["info"] for i in paramData["login"]] + @allure.story("Test_powerToday") + @pytest.mark.parametrize('casedata', paramData["powerToday"], ids=[i["info"] for i in paramData["powerToday"]]) + @pytest.mark.flaky(reruns=1, reruns_delay=1) + @pytest.mark.run(order=1) + def test_powerToday(self, setup_login, casedata): + casedata["headers"]["Authorization"] = "JWT " + setup_login["data"]["token"] + res, restime = powerToday(casedata) + asserting(hope_res=casedata["assert"], real_res=res, re_time=restime) + + @allure.story("Test_powerFees") + @pytest.mark.parametrize('casedata', paramData["powerFees"], ids=[i["info"] for i in paramData["powerFees"]]) + @pytest.mark.flaky(reruns=1, reruns_delay=1) + @pytest.mark.run(order=1) + def test_powerFees_Fees(self, third_login, setup_login, casedata): + + casedata["headers"]["Authorization"] = "JWT " + setup_login["data"]["token"] + webdata = thirdData["webFees"][0] + webdata["headers"]["Authorization"] = "JWT " + third_login["data"]["token"] + webres = webFees(webdata)[0] + # 电费断言 + res, restime = powerFees(casedata) + asserting(hope_res=casedata["assert"], real_res=res, re_time=restime,third_data=webres) + + @allure.story("Test_powerFees") + @pytest.mark.parametrize('casedata', paramData["powerFees"], ids=[i["info"] for i in paramData["powerFees"]]) + @pytest.mark.flaky(reruns=1, reruns_delay=1) + @pytest.mark.run(order=1) + def test_powerFees_Power(self, third_login, setup_login, casedata): + casedata["headers"]["Authorization"] = "JWT " + setup_login["data"]["token"] + webdata = thirdData["webFees"][0] + webdata["headers"]["Authorization"] = "JWT " + third_login["data"]["token"] + webres = webFees(webdata)[0] + # 电费断言 + res, restime = powerFees(casedata) + asserting(hope_res=casedata["assert"], real_res=res, re_time=restime, third_data=webres) \ No newline at end of file -- Gitee From f789fd4e0217c9fec80cf21fd6a029380d81ac68 Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Thu, 23 Sep 2021 17:24:56 +0800 Subject: [PATCH 05/30] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E5=85=B3=E8=81=94=20=E6=94=AF=E6=8C=81=E5=A4=9A=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E7=BB=93=E6=9E=9C=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 25 ++++++++++----- common/basePage.py | 1 + common/checkResult.py | 37 +++++++++++++++++------ scripts/writePage.py | 28 ++++++++++++----- test_suite/page/third_pages.py | 27 ++++++++++++----- test_suite/testcase/saasApp/test_power.py | 23 +++++--------- 6 files changed, 93 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 52f3b5f..b6b4fd1 100644 --- a/README.md +++ b/README.md @@ -21,25 +21,28 @@ "Content-Type": "application/json" } data: { - "username": "finsiot", - "password": "finsiot" + "username": "name", + "password": "*****" } assert: jsonpath: - { - "path": "$.msg", - "value": "Success.", - "asserttype": "==" + "path": "$.data.expense_trend[0].peak_hour.peak_hour", + "value": "$json($.data.trend[0].data.peak_hour)$", + "asserttype": "==", + "relevance": "web电费统计" } - { "path": "$.code", "value": 0, - "asserttype": "==" + "asserttype": "==", + "relevance": } - { "path": "$.data.id", "value": 196, - "asserttype": "==" + "asserttype": "==", + "relevance": } sqlassert: - { @@ -63,8 +66,14 @@ host: 'host' address: '/v1/apps/login/' method: 'post + relevance: true # 关联验证 - +###关联验证 +多接口 +![img_2.png](img_2.png) + 多接口结果合并成一个字典传给 third_datas +单接口 + 两种方法 同多接口类似 或者直接传结果给third_data ### 其他 jsonpath断言中value支持根据正则关联其他接口数据 ![img.png](img.png) diff --git a/common/basePage.py b/common/basePage.py index 4a09e1a..305c4e6 100644 --- a/common/basePage.py +++ b/common/basePage.py @@ -44,6 +44,7 @@ class apiSend(object): """ url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + address + logging.info("请求地址:%s" % "" + url) logging.info("请求地址:%s" % "" + str(address)) logging.info("请求头: %s" % str(header)) diff --git a/common/checkResult.py b/common/checkResult.py index 248f60e..dc39138 100644 --- a/common/checkResult.py +++ b/common/checkResult.py @@ -69,9 +69,10 @@ def assert_json(data, key=None, value=None, asserttype="KEY"): logging.error("断言类型错误, 请选择断言类型.") -def assert_text(hope_res, real_res,third_data=None): +def assert_text(hope_res, real_res,third_data=None,third_datas=None): """ 文本判断 + :param third_datas: :param third_data: :param hope_res: 期望结果 :param real_res: 实际结果 @@ -83,12 +84,21 @@ def assert_text(hope_res, real_res,third_data=None): r_res = jsonpath.jsonpath(real_res, h_res["path"])[0] if h_res["asserttype"] == "==": try: - h_res["value"] = replace_random(str(h_res["value"]),res=third_data) - with allure.step("json断言判断相等"): - allure.attach(name="期望结果", body=str(h_res)) - allure.attach(name='实际实际结果', body=str(r_res)) - assert str(r_res) == str(h_res["value"]) - logging.info("json断言通过, 期望结果{0}, 实际结果{1}".format(h_res, r_res)) + if not third_datas: + h_res["value"] = replace_random(str(h_res["value"]),res=third_data) + with allure.step("json断言判断相等"): + allure.attach(name="期望结果", body=str(h_res)) + allure.attach(name='实际实际结果', body=str(r_res)) + assert str(r_res) == str(h_res["value"]) + logging.info("json断言通过, 期望结果{0}, 实际结果{1}".format(h_res, r_res)) + elif third_datas: + if h_res["relevance"]: + h_res["value"] = replace_random(str(h_res["value"]), res=third_datas[h_res["relevance"]]) + with allure.step("json断言判断相等"): + allure.attach(name="期望结果", body=str(h_res)) + allure.attach(name='实际实际结果', body=str(r_res)) + assert str(r_res) == str(h_res["value"]) + logging.info("json断言通过, 期望结果{0}, 实际结果{1}".format(h_res, r_res)) except AssertionError: logging.error("json断言未通过, 期望结果{0}, 实际结果{1}".format(h_res, r_res)) raise @@ -166,9 +176,18 @@ def assert_sql(hope_res, real_res): db.close() -def asserting(hope_res, real_res, re_time=None,third_data=None): +def asserting(hope_res, real_res, re_time=None,third_data=None,third_datas=None): + """ + + :param hope_res: 期望结果 + :param real_res: 实际结果 + :param re_time: 实际响应时间 + :param third_data: 依赖数据 + :param third_datas: 依赖数据组 + :return: + """ if hope_res["jsonpath"]: - assert_text(hope_res, real_res,third_data) + assert_text(hope_res, real_res,third_data,third_datas) if hope_res["sqlassert"]: assert_sql(hope_res, real_res) if hope_res["time"]: diff --git a/scripts/writePage.py b/scripts/writePage.py index f153223..03819bb 100644 --- a/scripts/writePage.py +++ b/scripts/writePage.py @@ -18,11 +18,12 @@ def getFilePathList(path, filetype): return pathList -def write_case(_file): +def write_case(_file,pagename): path = dir_manage(dir_manage('${pro_dir}$') + dir_manage('${test_suite}$') + dir_manage('${page_dir}$')) - - with open(path + r"saasApp_pages_1.py", 'w+', encoding='utf-8') as f: - f.write("""# coding:utf-8\nfrom test_suite.page import *\n\nurlData = ini_yaml("urlData.yml")\n\n""") + with open(path + r"{}".format(pagename), 'w+', encoding='utf-8') as f: + # with open(path + r"saasApp_pages_1.py", 'w+', encoding='utf-8') as f: + # f.write("""# coding:utf-8\nfrom test_suite.page import *\n\nurlData = ini_yaml("urlData.yml")\n\n""") + f.write("""# coding:utf-8\nfrom test_suite.page import *\n\nurlData = ini_yaml("{}")\n\n""".format(_file)) yml_data = ini_yaml(_file) for item in yml_data.items(): @@ -36,11 +37,22 @@ def {testtitle}(casedata):""".format(testtitle=item[0])) """ logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) - return res, restime\n\n""" + data=casedata["data"])""" + ) + if item[1]["relevance"]: + f.write( +""" + return {casedata["info"]:res}, restime\n\n""" + ) + else : + f.write( +""" + return res, restime\n\n""" + ) if __name__ == '__main__': - ym_path = r'urlData.yml' - write_case(ym_path) + ym_path = r'thirdUrl.yml' + pagenames = "third_pages_1.py" + write_case(ym_path,pagenames) diff --git a/test_suite/page/third_pages.py b/test_suite/page/third_pages.py index 6e31065..8d44569 100644 --- a/test_suite/page/third_pages.py +++ b/test_suite/page/third_pages.py @@ -1,14 +1,17 @@ # coding:utf-8 -""" -@author: 井松 -@contact: 529548204@qq.com -@file: third_pages.py -@time: 2021/9/17 13:48 -""" from test_suite.page import * + urlData = ini_yaml("thirdUrl.yml") +def weblogin(casedata): + data = urlData["weblogin"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"]) + return res, restime + + def webalarm(casedata): data = urlData["webalarm"] logging.info("{}".format(casedata["info"])) @@ -16,9 +19,19 @@ def webalarm(casedata): data=casedata["data"]) return res, restime + def webFees(casedata): data = urlData["webFees"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], data=casedata["data"]) - return res, restime \ No newline at end of file + return {casedata["info"]:res}, restime + + +def webPower(casedata): + data = urlData["webPower"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"]) + return {casedata["info"]:res}, restime + diff --git a/test_suite/testcase/saasApp/test_power.py b/test_suite/testcase/saasApp/test_power.py index 6a2819d..e9d5277 100644 --- a/test_suite/testcase/saasApp/test_power.py +++ b/test_suite/testcase/saasApp/test_power.py @@ -32,22 +32,13 @@ class Test_power(object): def test_powerFees_Fees(self, third_login, setup_login, casedata): casedata["headers"]["Authorization"] = "JWT " + setup_login["data"]["token"] - webdata = thirdData["webFees"][0] - webdata["headers"]["Authorization"] = "JWT " + third_login["data"]["token"] - webres = webFees(webdata)[0] - # 电费断言 - res, restime = powerFees(casedata) - asserting(hope_res=casedata["assert"], real_res=res, re_time=restime,third_data=webres) - @allure.story("Test_powerFees") - @pytest.mark.parametrize('casedata', paramData["powerFees"], ids=[i["info"] for i in paramData["powerFees"]]) - @pytest.mark.flaky(reruns=1, reruns_delay=1) - @pytest.mark.run(order=1) - def test_powerFees_Power(self, third_login, setup_login, casedata): - casedata["headers"]["Authorization"] = "JWT " + setup_login["data"]["token"] - webdata = thirdData["webFees"][0] - webdata["headers"]["Authorization"] = "JWT " + third_login["data"]["token"] - webres = webFees(webdata)[0] + thirdData["webFees"][0]["headers"]["Authorization"] = "JWT " + third_login["data"]["token"] + thirdData["webPower"][0]["headers"]["Authorization"] = "JWT " + third_login["data"]["token"] + webFeesRes = webFees(thirdData["webFees"][0])[0] + webPowerRes = webPower(thirdData["webPower"][0])[0] + third_datas = {**webFeesRes,**webPowerRes} # 电费断言 res, restime = powerFees(casedata) - asserting(hope_res=casedata["assert"], real_res=res, re_time=restime, third_data=webres) \ No newline at end of file + asserting(hope_res=casedata["assert"], real_res=res, re_time=restime,third_datas=third_datas) + -- Gitee From e10a6381bdd13a87500d0b696a3528ba5993de34 Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Fri, 24 Sep 2021 15:12:21 +0800 Subject: [PATCH 06/30] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E5=85=B3=E8=81=94=20=E6=94=AF=E6=8C=81=E5=A4=9A=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E7=BB=93=E6=9E=9C=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- img_2.png | Bin 0 -> 38960 bytes test_suite/page/third_pages_1.py | 37 +++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 img_2.png create mode 100644 test_suite/page/third_pages_1.py diff --git a/img_2.png b/img_2.png new file mode 100644 index 0000000000000000000000000000000000000000..62b6b16c6d370faf64fba27c8922dac13602f267 GIT binary patch literal 38960 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 t1Fii(Ujopu=vFhiZ%STr?y*~f| literal 0 HcmV?d00001 diff --git a/test_suite/page/third_pages_1.py b/test_suite/page/third_pages_1.py new file mode 100644 index 0000000..8d44569 --- /dev/null +++ b/test_suite/page/third_pages_1.py @@ -0,0 +1,37 @@ +# coding:utf-8 +from test_suite.page import * + +urlData = ini_yaml("thirdUrl.yml") + + +def weblogin(casedata): + data = urlData["weblogin"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"]) + return res, restime + + +def webalarm(casedata): + data = urlData["webalarm"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"]) + return res, restime + + +def webFees(casedata): + data = urlData["webFees"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"]) + return {casedata["info"]:res}, restime + + +def webPower(casedata): + data = urlData["webPower"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"]) + return {casedata["info"]:res}, restime + -- Gitee From 4f6cdf493d0e803c4d7bd9fe2e6457f48b4554d0 Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Mon, 25 Oct 2021 16:48:45 +0800 Subject: [PATCH 07/30] =?UTF-8?q?=E4=B8=8D=E8=AE=B0=E5=BE=97=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E4=BA=86=E5=95=A5=E4=BA=86=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E4=B8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/basePage.py | 59 ++++---- common/checkResult.py | 22 +-- scripts/randomData.py | 136 ++++++++++--------- scripts/writePage.py | 6 +- test_suite/page/saasApp_pages.py | 16 +++ test_suite/page/saasApp_pages_1.py | 16 +++ test_suite/testcase/saasApp/test_alarm.py | 7 +- test_suite/testcase/saasApp/test_function.py | 21 +++ test_suite/testcase/saasApp/test_login.py | 4 +- test_suite/testcase/saasApp/test_power.py | 2 +- test_suite/testcase/saasApp/test_region.py | 21 +++ 11 files changed, 203 insertions(+), 107 deletions(-) create mode 100644 test_suite/testcase/saasApp/test_function.py create mode 100644 test_suite/testcase/saasApp/test_region.py diff --git a/common/basePage.py b/common/basePage.py index 305c4e6..670927f 100644 --- a/common/basePage.py +++ b/common/basePage.py @@ -3,6 +3,7 @@ import json import logging import os import random +import time import allure import requests @@ -30,7 +31,7 @@ class apiSend(object): dataran = replace_random(data) return dataran - def post(self, address, header, request_parameter_type="json", timeout=8, data=None, files=None,host="host"): + def post(self, address, header, request_parameter_type="json", timeout=8, data=None, files=None, host="host"): """ post请求 :param host: @@ -43,16 +44,17 @@ class apiSend(object): :return: """ - url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + address + iniaddress = replace_random(address, param=data["urlparam"]) + url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress + data_random = self.iniDatas(data["param"]) logging.info("请求地址:%s" % "" + url) - logging.info("请求地址:%s" % "" + str(address)) logging.info("请求头: %s" % str(header)) - + logging.info("请求参数: %s" % str(data_random)) if 'form-data' in request_parameter_type: with allure.step("POST上传文件"): allure.attach(name="请求地址", body=url) allure.attach(name="请求头", body=str(header)) - allure.attach(name="请求参数", body=str(data)) + allure.attach(name="请求参数", body=str(data_random)) if files is not None: for i in files: value = files[i] @@ -78,7 +80,7 @@ class apiSend(object): response = requests.post(url=url, data=multipart, headers={'Content-Type': multipart.content_type}, timeout=timeout) else: - data_random = self.iniDatas(data) + with allure.step("POST请求接口"): allure.attach(name="请求地址", body=url) allure.attach(name="请求头", body=str(header)) @@ -100,7 +102,7 @@ class apiSend(object): logging.error(e) raise - def get(self, address, header, data, timeout=8,host="host"): + def get(self, address, header, data, timeout=8, host="host"): """ get请求 :param host: @@ -110,8 +112,12 @@ class apiSend(object): :param timeout: 超时时间 :return: """ - data_random = self.iniDatas(data) - url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + address + iniaddress = replace_random(address, param=data["urlparam"]) + # if isinstance(data, dict): + # if "urlparam" in data.keys(): + # address = replace_random(address, param=data["urlparam"]) + data_random = self.iniDatas(data["param"]) + url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress logging.info("请求地址:%s" % "" + url) logging.info("请求头: %s" % str(header)) logging.info("请求参数: %s" % str(data_random)) @@ -135,7 +141,7 @@ class apiSend(object): logging.error(e) raise - def put(self, address, header, request_parameter_type="json", timeout=8, data=None, files=None,host="host"): + def put(self, address, header, request_parameter_type="json", timeout=8, data=None, files=None, host="host"): """ put请求 :param host: @@ -147,21 +153,22 @@ class apiSend(object): :param files: 文件路径 :return: """ - - url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + address + iniaddress = replace_random(address, param=data["urlparam"]) + url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress logging.info("请求地址:%s" % "" + url) logging.info("请求头: %s" % str(header)) + if request_parameter_type == 'raw': + data_random = self.iniDatas(data["param"]) + else: + data_random = data + logging.info("请求参数: %s" % str(data_random)) with allure.step("PUT请求接口"): allure.attach(name="请求地址", body=url) allure.attach(name="请求头", body=str(header)) - allure.attach(name="请求参数", body=str(data)) + allure.attach(name="请求参数", body=str(data_random)) - if request_parameter_type == 'raw': - data = self.iniDatas(data) - else: - data = data - response = requests.put(url=url, data=data, headers=header, timeout=timeout, files=files) + response = requests.put(url=url, data=data_random, headers=header, timeout=timeout, files=files) try: logging.info("请求接口结果: %s" % str(response.json())) return response.json(), response.elapsed.total_seconds() @@ -174,7 +181,7 @@ class apiSend(object): logging.error(e) raise - def delete(self, address, header, data, timeout=8,host="host"): + def delete(self, address, header, data, timeout=8, host="host"): """ get请求 :param host: @@ -184,11 +191,12 @@ class apiSend(object): :param timeout: 超时时间 :return: """ - data_random = self.iniDatas(data) - url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + address + iniaddress = replace_random(address, param=data["urlparam"]) + data_random = self.iniDatas(data["param"]) + url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress logging.info("请求地址:%s" % "" + url) logging.info("请求头: %s" % str(header)) - + logging.info("请求参数: %s" % str(data_random)) with allure.step("DELETE请求接口"): allure.attach(name="请求地址", body=url) allure.attach(name="请求头", body=str(header)) @@ -225,6 +233,7 @@ class apiSend(object): apisend = apiSend() if __name__ == '__main__': - apisend("/123123","get",data="er",headers={ - "Content-Type": "application/json" - },host="host2") \ No newline at end of file + ress = apisend("/$url(home_id)$", "get", data={"param":None,"urlparam": {"home_id": 123}}, headers={ + "Content-Type": "application/json" + }, host="host") + print(ress) diff --git a/common/checkResult.py b/common/checkResult.py index dc39138..3abb3a6 100644 --- a/common/checkResult.py +++ b/common/checkResult.py @@ -90,7 +90,7 @@ def assert_text(hope_res, real_res,third_data=None,third_datas=None): allure.attach(name="期望结果", body=str(h_res)) allure.attach(name='实际实际结果', body=str(r_res)) assert str(r_res) == str(h_res["value"]) - logging.info("json断言通过, 期望结果{0}, 实际结果{1}".format(h_res, r_res)) + logging.info("json断言通过, 期望结果'{0}', 实际结果'{1}'".format(h_res, r_res)) elif third_datas: if h_res["relevance"]: h_res["value"] = replace_random(str(h_res["value"]), res=third_datas[h_res["relevance"]]) @@ -98,9 +98,9 @@ def assert_text(hope_res, real_res,third_data=None,third_datas=None): allure.attach(name="期望结果", body=str(h_res)) allure.attach(name='实际实际结果', body=str(r_res)) assert str(r_res) == str(h_res["value"]) - logging.info("json断言通过, 期望结果{0}, 实际结果{1}".format(h_res, r_res)) + logging.info("json断言通过, 期望结果'{0}', 实际结果'{1}'".format(h_res, r_res)) except AssertionError: - logging.error("json断言未通过, 期望结果{0}, 实际结果{1}".format(h_res, r_res)) + logging.error("json断言未通过, 期望结果'{0}', 实际结果'{1}'".format(h_res, r_res)) raise elif h_res["asserttype"] == "!=": try: @@ -109,9 +109,9 @@ def assert_text(hope_res, real_res,third_data=None,third_datas=None): allure.attach(name="json期望结果", body=str(h_res)) allure.attach(name='json实际实际结果', body=str(r_res)) assert str(r_res) != str(h_res["value"]) - logging.info("json断言通过, 期望结果{0}, 实际结果{1}".format(h_res, r_res)) + logging.info("json断言通过, 期望结果'{0}', 实际结果'{1}'".format(h_res, r_res)) except AssertionError: - logging.error("json断言未通过, 期望结果{0}, 实际结果{1}".format(h_res, r_res)) + logging.error("json断言未通过, 期望结果'{0}', 实际结果'{1}'".format(h_res, r_res)) raise elif h_res["asserttype"] == "in": r_res = str(r_res) @@ -121,9 +121,9 @@ def assert_text(hope_res, real_res,third_data=None,third_datas=None): allure.attach(name="期望结果", body=str(h_res)) allure.attach(name='实际实际结果', body=str(r_res)) assert str(r_res) in str(h_res["value"]) - logging.info("json断言通过, 期望结果{0}, 实际结果{1}".format(h_res, real_res)) + logging.info("json断言通过, 期望结果'{0}', 实际结果'{1}'".format(h_res, real_res)) except AssertionError: - logging.error("json断言未通过, 期望结果{0}, 实际结果{1}".format(h_res, real_res)) + logging.error("json断言未通过, 期望结果'{0}', 实际结果'{1}'".format(h_res, real_res)) raise else: raise TypeError("asserttype方法错误") @@ -138,9 +138,9 @@ def assert_time(hope_res, real_res): allure.attach(name="期望响应时间", body=str(hope_time)) allure.attach(name='实际响应时间', body=str(real_res)) assert real_res <= hope_time - logging.info("time断言通过, 期望响应时间{0}, 实际响应时间{1}".format(hope_time, real_res)) + logging.info("time断言通过, 期望响应时间'{0}', 实际响应时间'{1}'".format(hope_time, real_res)) except AssertionError: - logging.error("请求响应时间过长, 期望时间{0}, 实际时间{1}".format(hope_time, real_res)) + logging.error("请求响应时间过长, 期望时间'{0}', 实际时间'{1}'".format(hope_time, real_res)) raise @@ -167,9 +167,9 @@ def assert_sql(hope_res, real_res): d = d.decode("utf-8") assert str(r_res) == str(d) logging.info( - "sql断言通过, 期望结果{0}, 实际结果{1},sql耗时{2:.5f}秒".format(datas, r_res, time.time() - t1)) + "sql断言通过, 期望结果'{0}', 实际结果'{1}',sql耗时{2:.5f}秒".format(datas, r_res, time.time() - t1)) except AssertionError: - logging.error("sql断言未通过, 期望结果{0}, 实际结果{1},sql耗时{2:.5f}秒".format(datas, r_res, time.time() - t1)) + logging.error("sql断言未通过, 期望结果'{0}', 实际结果'{1}',sql耗时{2:.5f}秒".format(datas, r_res, time.time() - t1)) raise else: raise ValueError("获取json值失败,请检查jsonpath") diff --git a/scripts/randomData.py b/scripts/randomData.py index 058d224..36b02cb 100644 --- a/scripts/randomData.py +++ b/scripts/randomData.py @@ -57,6 +57,10 @@ def sql_json(jspath, res): return jsonpath.jsonpath(res, jspath)[0] +def url_param(paramname, param): + return param[paramname] + + def random_int(scope): """ 获取随机整型数据 @@ -173,10 +177,11 @@ def get_time(time_type, layout, unit="0,0,0,0,0"): return ti -def replace_random(value, res=None): +def replace_random(value, res=None, param=None): """ 调用定义方法替换字符串 - :param res: + :param param: 路径参数数据 + :param res: jsonpath使用的返回结果 :param value: :return: """ @@ -187,6 +192,7 @@ def replace_random(value, res=None): time_list = re.findall(r"\$GetTime\(time_type=(.*?),layout=(.*?),unit=([0-9],[0-9],[0-9],[0-9],[0-9])\)\$", value) choice_list = re.findall(r"\$Choice\((.*?)\)\$", value) sqljson_list = re.findall(r"\$json\((.*?)\)\$", value) + urlparam_list = re.findall(r"\$url\((.*?)\)\$", value) if len(int_list): # 获取整型数据替换 @@ -204,26 +210,26 @@ def replace_random(value, res=None): value = replace_random(value, res) elif len(string_list): # 获取字符串数据替换 - for j in string_list: - pattern = re.compile(r'\$RandomString\(' + j + r'\)\$') - key = str(random_string(j)) + for i in string_list: + pattern = re.compile(r'\$RandomString\(' + i + r'\)\$') + key = str(random_string(i)) value = re.sub(pattern, key, value, count=1) value = replace_random(value, res) elif len(float_list): # 获取浮点数数据替换 - for n in float_list: - pattern = re.compile(r'\$RandomFloat\(' + n + r'\)\$') - key = str(random_float(n)) + for i in float_list: + pattern = re.compile(r'\$RandomFloat\(' + i + r'\)\$') + key = str(random_float(i)) value = re.sub(pattern, key, value, count=1) value = replace_random(value, res) elif len(time_list): # 获取时间替换 - for m in time_list: - if len(m[0]) and len(m[1]): - pattern = re.compile(r'\$GetTime\(time_type=' + m[0] + ',layout=' + m[1] + ',unit=' + m[2] + r'\)\$') - key = str(get_time(m[0], m[1], m[2])) + for i in time_list: + if len(i[0]) and len(i[1]): + pattern = re.compile(r'\$GetTime\(time_type=' + i[0] + ',layout=' + i[1] + ',unit=' + i[2] + r'\)\$') + key = str(get_time(i[0], i[1], i[2])) value = re.sub(pattern, key, value, count=1) else: print("$GetTime$参数错误,time_type, layout为必填") @@ -236,6 +242,7 @@ def replace_random(value, res=None): key = str(choice_data(i)) value = re.sub(pattern, key, value, count=1) value = replace_random(value, res) + elif len(sqljson_list): for i in sqljson_list: pattern = re.compile(r'\$json\(' + i.replace('$', "\$").replace('[', '\[') + r'\)\$') @@ -243,6 +250,12 @@ def replace_random(value, res=None): value = re.sub(pattern, key, value, count=1) value = replace_random(value, res) + elif len(urlparam_list): + for i in urlparam_list: + pattern = re.compile(r'\$url\(' + i + r'\)\$') + key = str(url_param(i, param)) + value = re.sub(pattern, key, value, count=1) + value = replace_random(value, param) else: pass return value @@ -250,68 +263,69 @@ def replace_random(value, res=None): if __name__ == '__main__': int_num = "$RandomPosInt(1,333)$" - str_num = '$RandomString($RandomPosInt(2,23)$)$' + str_num = '$RandomString($RandomPosInt(2,23)$)$$RandomPosInt(1,333)$' float_num = '$RandomFloat($RandomPosInt(2,13)$,$RandomPosInt(2,13)$,$RandomPosInt(2,13)$)$' time_num = '$GetTime(time_type=else,layout=%Y-%m-%d %H:%M:%S,unit=0,0,0,0,0)$' choice_num = '$Choice($RandomPosInt(2,13)$)$' '$json($.data[-$RandomPosInt(1,5)$:])$' - jsons = """ -select count(*) '$json($.data[-$RandomPosInt(1,5)$:])$' -$RandomString($RandomPosInt(2,23)$)$ - ) as ad - """ - js = """ - select count(*) '$GetTime(time_type=past,layout=%Y-%m-%d,unit=0,0,0,1,0)$' from ( -select - dr.qr_code, - alarm.device_id, - alarm.grade, - alarm.create_time, - alarm.handler_status, - alarm.work_order - -from alarm - inner join ( - select device.id, - device.qr_code, - device.region_id, - region.enterprise_id, - device.status, - device.category_id - from device - inner join region - on device.region_id = region.id - and region.enterprise_id = 88 -) as dr - on dr.qr_code = alarm.device_id and alarm.create_time >= '$GetTime(time_type=past,layout=%Y-%m-%d 00:00:00,unit=0,0,0,1,0)$' and alarm.create_time < '$GetTime(time_type=past,layout=%Y-%m-%d 00:00:00,unit=0,0,0,0,0)$' - ) as ad - """ +# jsons = """ +# select count(*) '$json($.data[-$RandomPosInt(1,5)$:])$' +# $RandomString($RandomPosInt(2,23)$)$ +# ) as ad +# """ +# js = """ +# select count(*) '$GetTime(time_type=past,layout=%Y-%m-%d,unit=0,0,0,1,0)$' from ( +# select +# dr.qr_code, +# alarm.device_id, +# alarm.grade, +# alarm.create_time, +# alarm.handler_status, +# alarm.work_order +# +# from alarm +# inner join ( +# select device.id, +# device.qr_code, +# device.region_id, +# region.enterprise_id, +# device.status, +# device.category_id +# from device +# inner join region +# on device.region_id = region.id +# and region.enterprise_id = 88 +# ) as dr +# on dr.qr_code = alarm.device_id and alarm.create_time >= '$GetTime(time_type=past,layout=%Y-%m-%d 00:00:00,unit=0,0,0,1,0)$' and alarm.create_time < '$GetTime(time_type=past,layout=%Y-%m-%d 00:00:00,unit=0,0,0,0,0)$' +# ) as ad +# """ # a = json.dumps(ini_yaml("homePageData.yml")["companyAlarm"]) # print(type(ini_yaml("家庭详情.yml")[0]["data"])) # print(replace_random(int_num)) # print(replace_random(str_num)) # print(replace_random(float_num)) # print(replace_random(time_num)) - res1 = {'code': 0, 'msg': 'ok', - 'data': [{'time': '2021-08-18', 'number': None}, {'time': '2021-08-19', 'number': None}, - {'time': '2021-08-20', 'number': 1}, {'time': '2021-08-21', 'number': None}, - {'time': '2021-08-22', 'number': None}, {'time': '2021-08-23', 'number': 9}, - {'time': '2021-08-24', 'number': 7}, {'time': '2021-08-25', 'number': 11}, - {'time': '2021-08-26', 'number': 2}, {'time': '2021-08-27', 'number': 1}, - {'time': '2021-08-28', 'number': None}, {'time': '2021-08-29', 'number': None}, - {'time': '2021-08-30', 'number': None}, {'time': '2021-08-31', 'number': 1}, - {'time': '2021-09-01', 'number': 2}, {'time': '2021-09-02', 'number': 4}, - {'time': '2021-09-03', 'number': 3}, {'time': '2021-09-04', 'number': 3}, - {'time': '2021-09-05', 'number': 1}, {'time': '2021-09-06', 'number': 7}, - {'time': '2021-09-07', 'number': 13}, {'time': '2021-09-08', 'number': 23}, - {'time': '2021-09-09', 'number': 21}, {'time': '2021-09-10', 'number': 11}, - {'time': '2021-09-11', 'number': 7}, {'time': '2021-09-12', 'number': 4}, - {'time': '2021-09-13', 'number': 22}, {'time': '2021-09-14', 'number': 19}, - {'time': '2021-09-15', 'number': 38}, {'time': '2021-09-16', 'number': 39}]} + # res1 = {'code': 0, 'msg': 'ok', + # 'data': [{'time': '2021-08-18', 'number': None}, {'time': '2021-08-19', 'number': None}, + # {'time': '2021-08-20', 'number': 1}, {'time': '2021-08-21', 'number': None}, + # {'time': '2021-08-22', 'number': None}, {'time': '2021-08-23', 'number': 9}, + # {'time': '2021-08-24', 'number': 7}, {'time': '2021-08-25', 'number': 11}, + # {'time': '2021-08-26', 'number': 2}, {'time': '2021-08-27', 'number': 1}, + # {'time': '2021-08-28', 'number': None}, {'time': '2021-08-29', 'number': None}, + # {'time': '2021-08-30', 'number': None}, {'time': '2021-08-31', 'number': 1}, + # {'time': '2021-09-01', 'number': 2}, {'time': '2021-09-02', 'number': 4}, + # {'time': '2021-09-03', 'number': 3}, {'time': '2021-09-04', 'number': 3}, + # {'time': '2021-09-05', 'number': 1}, {'time': '2021-09-06', 'number': 7}, + # {'time': '2021-09-07', 'number': 13}, {'time': '2021-09-08', 'number': 23}, + # {'time': '2021-09-09', 'number': 21}, {'time': '2021-09-10', 'number': 11}, + # {'time': '2021-09-11', 'number': 7}, {'time': '2021-09-12', 'number': 4}, + # {'time': '2021-09-13', 'number': 22}, {'time': '2021-09-14', 'number': 19}, + # {'time': '2021-09-15', 'number': 38}, {'time': '2021-09-16', 'number': 39}]} # print(replace_random(js, res=res1)) t = "$GetTime(time_type=past,layout=13timestampDAY,unit=0,0,0,1,4)$" # print(replace_random(choice_num)) - print(replace_random(t)) + urls = "$url(home_id)$" + print(replace_random(str_num)) # pattern = re.compile(r'\$json\(' + '$.data.alarm[1].number'.replace('$',"\$").replace('[','\[') + r'\)\$') # # key = str(sql_json(i)) # key = "123" diff --git a/scripts/writePage.py b/scripts/writePage.py index 03819bb..47cd6ff 100644 --- a/scripts/writePage.py +++ b/scripts/writePage.py @@ -53,6 +53,8 @@ def {testtitle}(casedata):""".format(testtitle=item[0])) if __name__ == '__main__': - ym_path = r'thirdUrl.yml' - pagenames = "third_pages_1.py" + # ym_path = r'thirdUrl.yml' + # pagenames = "third_pages_1.py" + ym_path = r'urlData.yml' + pagenames = "saasApp_pages_1.py" write_case(ym_path,pagenames) diff --git a/test_suite/page/saasApp_pages.py b/test_suite/page/saasApp_pages.py index b8f14c5..da9bbb0 100644 --- a/test_suite/page/saasApp_pages.py +++ b/test_suite/page/saasApp_pages.py @@ -123,3 +123,19 @@ def powerFees(casedata): data=casedata["data"]) return res, restime + +def functionList(casedata): + data = urlData["functionList"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"]) + return res, restime + + +def regionList(casedata): + data = urlData["regionList"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"]) + return res, restime + diff --git a/test_suite/page/saasApp_pages_1.py b/test_suite/page/saasApp_pages_1.py index b8f14c5..da9bbb0 100644 --- a/test_suite/page/saasApp_pages_1.py +++ b/test_suite/page/saasApp_pages_1.py @@ -123,3 +123,19 @@ def powerFees(casedata): data=casedata["data"]) return res, restime + +def functionList(casedata): + data = urlData["functionList"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"]) + return res, restime + + +def regionList(casedata): + data = urlData["regionList"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"]) + return res, restime + diff --git a/test_suite/testcase/saasApp/test_alarm.py b/test_suite/testcase/saasApp/test_alarm.py index 777c315..0cf0ef0 100644 --- a/test_suite/testcase/saasApp/test_alarm.py +++ b/test_suite/testcase/saasApp/test_alarm.py @@ -36,13 +36,10 @@ class Test_alarm(object): @pytest.mark.parametrize('casedata', paramData["alarmRank"], ids=[i["info"] for i in paramData["alarmRank"]]) @pytest.mark.flaky(reruns=1, reruns_delay=1) @pytest.mark.run(order=1) - def test_alarmRank(self,third_login ,setup_login, casedata): + def test_alarmRank(self ,setup_login, casedata): casedata["headers"]["Authorization"] = "JWT " + setup_login["data"]["token"] - webdata = thirdData["webalarm"][0] - webdata["headers"]["Authorization"] = "JWT " + third_login["data"]["token"] - webres=webalarm(webdata)[0] res, restime = alarmRank(casedata) - asserting(hope_res=casedata["assert"], real_res=res, re_time=restime,third_data=webres) + asserting(hope_res=casedata["assert"], real_res=res, re_time=restime) @allure.story("Test_alarmDistribute") @pytest.mark.parametrize('casedata', paramData["alarmDistribute"], ids=[i["info"] for i in paramData["alarmDistribute"]]) diff --git a/test_suite/testcase/saasApp/test_function.py b/test_suite/testcase/saasApp/test_function.py new file mode 100644 index 0000000..93f4b4e --- /dev/null +++ b/test_suite/testcase/saasApp/test_function.py @@ -0,0 +1,21 @@ +# coding:utf-8 +""" +@author: 井松 +@contact: 529548204@qq.com +@file: test_function.py +@time: 2021/10/8 15:02 +""" +from test_suite.page.saasApp_pages import * +# from test_suite.page.third_pages import * +paramData = ini_yaml("functionData.yml") +# thirdData = ini_yaml("thirdData.yml") + +class Test_function(object): + @allure.story("Test_functionList") + @pytest.mark.parametrize('casedata', paramData["functionList"], ids=[i["info"] for i in paramData["functionList"]]) + @pytest.mark.flaky(reruns=1, reruns_delay=1) + @pytest.mark.run(order=1) + def test_functionList(self, setup_login, casedata): + casedata["headers"]["Authorization"] = "JWT " + setup_login["data"]["token"] + res, restime = functionList(casedata) + asserting(hope_res=casedata["assert"], real_res=res, re_time=restime) \ No newline at end of file diff --git a/test_suite/testcase/saasApp/test_login.py b/test_suite/testcase/saasApp/test_login.py index 20252a5..fcfddcb 100644 --- a/test_suite/testcase/saasApp/test_login.py +++ b/test_suite/testcase/saasApp/test_login.py @@ -29,8 +29,8 @@ class Test_login(object): @pytest.mark.parametrize('casedata', paramData["forgetPassword"], ids=[i["info"] for i in paramData["forgetPassword"]]) @pytest.mark.flaky(reruns=1, reruns_delay=1) @pytest.mark.run(order=1) - def test_forgetPassword(self, casedata): + def test_forgetPassword(self, casedata,cache): res, restime = mobileCode(paramData["mobileCode"][1]) - casedata['data']['session_key'] = res["data"]['session_id'] + casedata['data']["param"]['session_key'] = cache.get('k',None) res, restime = forgetPassword(casedata) asserting(hope_res=casedata["assert"], real_res=res,re_time=restime) diff --git a/test_suite/testcase/saasApp/test_power.py b/test_suite/testcase/saasApp/test_power.py index e9d5277..1a76d8b 100644 --- a/test_suite/testcase/saasApp/test_power.py +++ b/test_suite/testcase/saasApp/test_power.py @@ -29,7 +29,7 @@ class Test_power(object): @pytest.mark.parametrize('casedata', paramData["powerFees"], ids=[i["info"] for i in paramData["powerFees"]]) @pytest.mark.flaky(reruns=1, reruns_delay=1) @pytest.mark.run(order=1) - def test_powerFees_Fees(self, third_login, setup_login, casedata): + def test_powerFees(self, third_login, setup_login, casedata): casedata["headers"]["Authorization"] = "JWT " + setup_login["data"]["token"] diff --git a/test_suite/testcase/saasApp/test_region.py b/test_suite/testcase/saasApp/test_region.py new file mode 100644 index 0000000..1bb74c2 --- /dev/null +++ b/test_suite/testcase/saasApp/test_region.py @@ -0,0 +1,21 @@ +# coding:utf-8 +""" +@author: 井松 +@contact: 529548204@qq.com +@file: test_region.py +@time: 2021/10/8 16:11 +""" +from test_suite.page.saasApp_pages import * +# from test_suite.page.third_pages import * +paramData = ini_yaml("regionData.yml") +# thirdData = ini_yaml("thirdData.yml") + +class Test_function(object): + @allure.story("Test_functionList") + @pytest.mark.parametrize('casedata', paramData["functionList"], ids=[i["info"] for i in paramData["functionList"]]) + @pytest.mark.flaky(reruns=1, reruns_delay=1) + @pytest.mark.run(order=1) + def test_functionList(self, setup_login, casedata): + casedata["headers"]["Authorization"] = "JWT " + setup_login["data"]["token"] + res, restime = functionList(casedata) + asserting(hope_res=casedata["assert"], real_res=res, re_time=restime) \ No newline at end of file -- Gitee From 94990cb35a3f6236976adf8c83d9b63e0777157a Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Thu, 11 Nov 2021 15:43:22 +0800 Subject: [PATCH 08/30] =?UTF-8?q?=E4=BC=98=E5=8C=96md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- scripts/readYamlFile.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b6b4fd1..0007143 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ "path": "$.data.expense_trend[0].peak_hour.peak_hour", "value": "$json($.data.trend[0].data.peak_hour)$", "asserttype": "==", - "relevance": "web电费统计" + "relevance": "web电费统计" # 根据特定返回结果获取 为被关联接口的info } - { "path": "$.code", @@ -66,7 +66,7 @@ host: 'host' address: '/v1/apps/login/' method: 'post - relevance: true # 关联验证 + relevance: true # 关联验证 true: 为被关联接口 生成page时会返回特定格式结果{"web电费统计":{"code":0,"msg":"success"}} 根据这个key 去yaml assert中验证 ###关联验证 多接口 diff --git a/scripts/readYamlFile.py b/scripts/readYamlFile.py index 6fa7b92..131ed28 100644 --- a/scripts/readYamlFile.py +++ b/scripts/readYamlFile.py @@ -10,7 +10,7 @@ def ini_yaml(filename, path=datapath): # encoding="utf-8" 视情况加 with open(path + "/" + filename, 'r') as f: file_data = f.read() - data = yaml.load(file_data, Loader=yaml.FullLoader) + data = yaml.safe_load(file_data) return data -- Gitee From 68e8c20b0476d81bea358797a176a864735f3b06 Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Mon, 15 Nov 2021 17:58:18 +0800 Subject: [PATCH 09/30] =?UTF-8?q?=E5=85=B3=E8=81=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/checkResult.py | 4 ++-- scripts/relevance.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 scripts/relevance.py diff --git a/common/checkResult.py b/common/checkResult.py index 3abb3a6..c74e84b 100644 --- a/common/checkResult.py +++ b/common/checkResult.py @@ -92,8 +92,8 @@ def assert_text(hope_res, real_res,third_data=None,third_datas=None): assert str(r_res) == str(h_res["value"]) logging.info("json断言通过, 期望结果'{0}', 实际结果'{1}'".format(h_res, r_res)) elif third_datas: - if h_res["relevance"]: - h_res["value"] = replace_random(str(h_res["value"]), res=third_datas[h_res["relevance"]]) + if h_res["relevanceCheck"]: + h_res["value"] = replace_random(str(h_res["value"]), res=third_datas[h_res["relevanceCheck"]]) with allure.step("json断言判断相等"): allure.attach(name="期望结果", body=str(h_res)) allure.attach(name='实际实际结果', body=str(r_res)) diff --git a/scripts/relevance.py b/scripts/relevance.py new file mode 100644 index 0000000..30100e9 --- /dev/null +++ b/scripts/relevance.py @@ -0,0 +1,16 @@ +# coding:utf-8 +""" +@author: 井松 +@contact: 529548204@qq.com +@file: relevance.py +@time: 2021/11/15 17:13 +""" +from cacheout import Cache +class Relevance(object): + def __init__(self): + self.caches = Cache() + # def relevance(self,data): + + + + -- Gitee From 309be1f16a92d848ccb26be614178ca12369e3af Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Mon, 15 Nov 2021 17:59:49 +0800 Subject: [PATCH 10/30] =?UTF-8?q?=E5=85=B3=E8=81=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/relevance.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/scripts/relevance.py b/scripts/relevance.py index 30100e9..144e3c3 100644 --- a/scripts/relevance.py +++ b/scripts/relevance.py @@ -5,6 +5,33 @@ @file: relevance.py @time: 2021/11/15 17:13 """ + +""" +# coding:utf-8 +import jsonpath +r = { + "code":False +} +a = 2 +s = "code=2&hehe=aaaa" + + +if jsonpath.jsonpath(r,"$.code"): + + print(jsonpath.jsonpath(r,"$.code")) +else: + print(12312313) +# def valueHandle(data :str): +# l = data.split("&") +# for i in l: +# print(i.split("=")[0]) +# print(i.split("=")[1]) +# print(l) +# +# if __name__ == '__main__': +# valueHandle(s) +""" + from cacheout import Cache class Relevance(object): def __init__(self): -- Gitee From 7dbc052eac0875e1f1881665bcb2f576690fb7ba Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Tue, 16 Nov 2021 17:57:58 +0800 Subject: [PATCH 11/30] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=E7=AE=80=E5=8D=95=E7=9A=84=E6=9C=AC=E5=9C=B0=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E6=96=B9=E6=B3=95=20=E7=B1=BB=E4=BC=BCpytest=E7=9A=84?= =?UTF-8?q?cache?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- scripts/relevance.py | 131 +++++++++++++++++++----- setupMain.py | 14 +-- test_suite/testcase/saasApp/conftest.py | 12 ++- test_suite/testcase/saasApp/test_ca.py | 23 +++++ test_suite/testcase/saasApp/test_ca2.py | 21 ++++ 6 files changed, 169 insertions(+), 35 deletions(-) create mode 100644 test_suite/testcase/saasApp/test_ca.py create mode 100644 test_suite/testcase/saasApp/test_ca2.py diff --git a/.gitignore b/.gitignore index 4f8fef4..1445dc7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ logs/ report/ .pytest_cache/ config/config.ini -test_suite/datas \ No newline at end of file +test_suite/datas +caches \ No newline at end of file diff --git a/scripts/relevance.py b/scripts/relevance.py index 144e3c3..d14bf1c 100644 --- a/scripts/relevance.py +++ b/scripts/relevance.py @@ -6,38 +6,117 @@ @time: 2021/11/15 17:13 """ -""" -# coding:utf-8 +import os +import shutil + import jsonpath -r = { - "code":False -} -a = 2 -s = "code=2&hehe=aaaa" - - -if jsonpath.jsonpath(r,"$.code"): - - print(jsonpath.jsonpath(r,"$.code")) -else: - print(12312313) -# def valueHandle(data :str): -# l = data.split("&") -# for i in l: -# print(i.split("=")[0]) -# print(i.split("=")[1]) -# print(l) -# -# if __name__ == '__main__': -# valueHandle(s) +from scripts.mkDir import mk_dir +from scripts.readYamlFile import ini_yaml + +""" +response: 执行接口,根据jsonpath 读取返回结果的指定值 存入缓存 +body: 执行前 把body转化成json格式 根据jsonpath 读取指定参数值 存入缓存 +url: 执行前 将url参数 正则匹配出来 存入缓存 + +取 +body: get请求 根据name replace参数 post 根据path 更新json数据 +url: 根据缓存name 更新urlparam字段 """ -from cacheout import Cache + +class Cache(object): + def __init__(self, path): + self.path = path + mk_dir(path) + self.del_list = os.listdir(self.path) + + def set(self, key, value): + with open(self.path + key + ".text", 'w', encoding="utf-8") as f: + f.write(value) + + def get(self, key): + + if key + ".text" in self.del_list: + with open(self.path + "/" + key + ".text", 'r', encoding="utf-8") as f: + value = f.read() + return value + else: + raise ValueError("{}不存在".format(key)) + + def set_many(self, data: dict): + for i in data: + with open(self.path + "/" + i + ".text", 'w', encoding="utf-8") as f: + f.write(str(data[i])) + + def clear_all_cache(self): + + del_list = os.listdir(self.path) + for f in del_list: + file_path = os.path.join(self.path, f) + if os.path.isfile(file_path): + os.remove(file_path) + elif os.path.isdir(file_path): + shutil.rmtree(file_path) + + def clear_cache(self, key: str): + file_path = os.path.join(self.path, key + ".text") + if os.path.isfile(file_path): + os.remove(file_path) + elif os.path.isdir(file_path): + shutil.rmtree(file_path) + + +def valueHandle(data: str): + param_dict = {} + param_list = data.split("&") + for param in param_list: + param_dict[param.split("=")[0]] = param.split("=")[1] + return param_dict + + +s = {'regionList': {'name': '区域列表', 'host': 'host', 'address': '/v1/apps/region/', 'method': 'get', 'relevance': [ + {'relevancetype': 'set', 'datasfrom': 'reponse', 'path': '$.data', 'value': None, 'name': None}, + {'relevancetype': 'set', 'datasfrom': 'body', 'path': None, 'value': None, 'name': None}, + {'relevancetype': 'set', 'datasfrom': 'url', 'path': None, 'value': None, 'name': None}], 'relevanceCheck': None}} +res = { + "data": { + "haha": 123 + } +} + + class Relevance(object): + def __init__(self): - self.caches = Cache() - # def relevance(self,data): + self.caches = Cache(path=r"D:\apitest\caches") + + def relevance(self, data: dict): + rel = data['regionList']['relevance'] + for i in rel: + if i["datasfrom"] == 'body': + print(1) + elif i["datasfrom"] == 'reponse': + + values = jsonpath.jsonpath(res, i['path']) + if not values: + raise SyntaxError("path错误") + self.caches.set_many(values[0]) + # self.caches.clear_cache("haha") + + self.caches.get("haha1") + + + elif i["datasfrom"] == 'url': + print(3) + else: + raise TypeError("datasfrom错误") + + return rel +if __name__ == '__main__': + s1 = ini_yaml("urltest.yml", r"D:\apitest\test_suite\datas\saasApp") + rels = Relevance() + rels.relevance(s) diff --git a/setupMain.py b/setupMain.py index 51dd561..8e703da 100644 --- a/setupMain.py +++ b/setupMain.py @@ -28,19 +28,21 @@ def run(): mk_dir(temp_path) mk_dir(html_path) # 执行命令行 - args = ['-s', '-q', test_case_path, '--alluredir', temp_path] + # args = ['-s', '-q', test_case_path, '--alluredir', temp_path] + args = ['-s', '-q', test_case_path,] pytest.main(args) - cmd = 'allure generate %s -o %s -c' % (temp_path, html_path) - os.system(cmd) + + # cmd = 'allure generate %s -o %s -c' % (temp_path, html_path) + # os.system(cmd) # 发送报告 # send_email(localtime + "测试报告", "http://192.168.1.2:9999") # 钉钉发送 # ding = DingTalkSendMsg() # ding.send_text("点击链接打开测试报告 http://192.168.1.2:9999",[13688400244]) # 生成html报告 - os.system(r'allure generate {0} -o {1} '.format(temp_path, html_path)) - # 打开报告服务 并指定端口 - os.system(r'allure serve {0} -p 9999'.format(temp_path)) + # os.system(r'allure generate {0} -o {1} '.format(temp_path, html_path)) + # # 打开报告服务 并指定端口 + # os.system(r'allure serve {0} -p 9999'.format(temp_path)) if __name__ == '__main__': diff --git a/test_suite/testcase/saasApp/conftest.py b/test_suite/testcase/saasApp/conftest.py index 7fb5d24..38f68d3 100644 --- a/test_suite/testcase/saasApp/conftest.py +++ b/test_suite/testcase/saasApp/conftest.py @@ -1,7 +1,9 @@ # coding:utf-8 +import pytest from test_suite.page.saasApp_pages import * - +from scripts.relevance import Relevance +import cacheout paramData = ini_yaml("loginData.yml")["login"][0] thirdParamData = ini_yaml("loginData.yml")["login"][0] @@ -26,4 +28,10 @@ def third_login(): logging.info("{}".format(paramData["info"])) res, restime = apisend(address=data["address"], method=data["method"], headers=paramData["headers"], data=paramData["data"]) - return res \ No newline at end of file + return res +@pytest.fixture(scope="module") +def relv(): + global haha + haha=1 + ca = Relevance() + return haha \ No newline at end of file diff --git a/test_suite/testcase/saasApp/test_ca.py b/test_suite/testcase/saasApp/test_ca.py new file mode 100644 index 0000000..c73ca81 --- /dev/null +++ b/test_suite/testcase/saasApp/test_ca.py @@ -0,0 +1,23 @@ +# coding:utf-8 +""" +@author: 井松 +@contact: 529548204@qq.com +@file: test_ca.py +@time: 2021/11/16 14:15 +""" + +import cacheout + +class Test_login(object): + # def setup(self): + # self.re = saasPages() + # ids=[i["info"] for i in paramData["login"]] + + def test_login(self,cache): + cache.set() + # relv.caches.set_many({ + # "haha123": 1234 + # }) + # print(relv.caches.get("haha123")) + def test_login2(self,relv): + print(relv.caches.get("haha123")) \ No newline at end of file diff --git a/test_suite/testcase/saasApp/test_ca2.py b/test_suite/testcase/saasApp/test_ca2.py new file mode 100644 index 0000000..ab00d25 --- /dev/null +++ b/test_suite/testcase/saasApp/test_ca2.py @@ -0,0 +1,21 @@ +# coding:utf-8 +""" +@author: 井松 +@contact: 529548204@qq.com +@file: test_ca.py +@time: 2021/11/16 14:15 +""" + +import cacheout + +class Test_login(object): + # def setup(self): + # self.re = saasPages() + # ids=[i["info"] for i in paramData["login"]] + + def test_login(self,relv,cache): + cache.set() + + print(relv.caches.get("haha123")) + def test_login2(self,relv): + print(relv.caches.get("haha123")) \ No newline at end of file -- Gitee From df7fe9e9b1bd09754dea2b4a707d073e195f74ba Mon Sep 17 00:00:00 2001 From: js <529548204@qq.com> Date: Tue, 16 Nov 2021 21:19:07 +0800 Subject: [PATCH 12/30] 1 --- scripts/relevance.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/scripts/relevance.py b/scripts/relevance.py index d14bf1c..8a57c65 100644 --- a/scripts/relevance.py +++ b/scripts/relevance.py @@ -75,9 +75,10 @@ def valueHandle(data: str): s = {'regionList': {'name': '区域列表', 'host': 'host', 'address': '/v1/apps/region/', 'method': 'get', 'relevance': [ - {'relevancetype': 'set', 'datasfrom': 'reponse', 'path': '$.data', 'value': None, 'name': None}, - {'relevancetype': 'set', 'datasfrom': 'body', 'path': None, 'value': None, 'name': None}, - {'relevancetype': 'set', 'datasfrom': 'url', 'path': None, 'value': None, 'name': None}], 'relevanceCheck': None}} + {'relevancetype': 'set', 'datasfrom': 'reponse', 'path': '$.data', 'name': "ceshi"}, +{'relevancetype': 'get', 'datasfrom': 'reponse', 'path': '$.data', 'name': "ceshi"}, + {'relevancetype': 'set', 'datasfrom': 'body', 'path': None, 'name': None}, + {'relevancetype': 'set', 'datasfrom': 'url', 'path': None, 'name': None}], 'relevanceCheck': None}} res = { "data": { "haha": 123 @@ -96,14 +97,15 @@ class Relevance(object): if i["datasfrom"] == 'body': print(1) elif i["datasfrom"] == 'reponse': - - values = jsonpath.jsonpath(res, i['path']) - if not values: - raise SyntaxError("path错误") - self.caches.set_many(values[0]) - # self.caches.clear_cache("haha") - - self.caches.get("haha1") + if i["relevancetype"] == "set": + values = jsonpath.jsonpath(res, i['path']) + if not values: + raise SyntaxError("path错误") + + elif i["relevancetype"] == "get": + # self.caches.clear_cache("haha") + self.caches.get(i["name"]) + # self.caches.get("haha1") elif i["datasfrom"] == 'url': -- Gitee From d3f78ec714d53d1752ea3ce8ddfff2fcafad370b Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Wed, 17 Nov 2021 11:42:47 +0800 Subject: [PATCH 13/30] =?UTF-8?q?=E5=AE=8C=E5=96=84=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=20=E8=AF=BB=E5=8F=96=E7=BC=93=E5=AD=98=20?= =?UTF-8?q?=E9=87=87=E7=94=A8=E6=AD=A3=E5=88=99=E5=8C=B9=E9=85=8D=20?= =?UTF-8?q?=E8=AF=A6=E6=83=85randomData.py=E4=B8=AD=E6=9F=A5=E7=9C=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/randomData.py | 30 ++++++++++++-- scripts/relevance.py | 93 ++++++++++++++++++++++--------------------- 2 files changed, 75 insertions(+), 48 deletions(-) diff --git a/scripts/randomData.py b/scripts/randomData.py index 36b02cb..58ecbdb 100644 --- a/scripts/randomData.py +++ b/scripts/randomData.py @@ -6,6 +6,7 @@ import re import time import jsonpath +from scripts.relevance import Cache def choice_data(data): @@ -57,9 +58,14 @@ def sql_json(jspath, res): return jsonpath.jsonpath(res, jspath)[0] -def url_param(paramname, param): +def nomal(paramname, param): return param[paramname] +def caches(data): + c = Cache() + return c.get(data) + + def random_int(scope): """ @@ -180,6 +186,10 @@ def get_time(time_type, layout, unit="0,0,0,0,0"): def replace_random(value, res=None, param=None): """ 调用定义方法替换字符串 + urls = "$url(home_id)$" + ca = "$caches(haha)$" + print(replace_random(urls,param={"home_id":"$caches(haha)$"})) + print(replace_random(ca)) :param param: 路径参数数据 :param res: jsonpath使用的返回结果 :param value: @@ -193,6 +203,7 @@ def replace_random(value, res=None, param=None): choice_list = re.findall(r"\$Choice\((.*?)\)\$", value) sqljson_list = re.findall(r"\$json\((.*?)\)\$", value) urlparam_list = re.findall(r"\$url\((.*?)\)\$", value) + caches_list = re.findall(r"\$caches\((.*?)\)\$", value) if len(int_list): # 获取整型数据替换 @@ -251,11 +262,22 @@ def replace_random(value, res=None, param=None): value = replace_random(value, res) elif len(urlparam_list): + + # urls = "$url(home_id)$" + # replace_random(urls,param={"home_id":"$caches(haha)$"}) for i in urlparam_list: pattern = re.compile(r'\$url\(' + i + r'\)\$') - key = str(url_param(i, param)) + key = str(nomal(i, param)) value = re.sub(pattern, key, value, count=1) value = replace_random(value, param) + elif len(caches_list): + # urls = "$url(home_id)$" + # replace_random(urls,param={"home_id":"$caches(haha)$"}) + for i in caches_list: + pattern = re.compile(r'\$caches\(' + i + r'\)\$') + key = str(caches(i)) + value = re.sub(pattern, key, value, count=1) + value = replace_random(value,param) else: pass return value @@ -325,7 +347,9 @@ if __name__ == '__main__': t = "$GetTime(time_type=past,layout=13timestampDAY,unit=0,0,0,1,4)$" # print(replace_random(choice_num)) urls = "$url(home_id)$" - print(replace_random(str_num)) + ca = "$caches(haha)$" + print(replace_random(urls,param={"home_id":"$caches(haha)$"})) + print(replace_random(ca)) # pattern = re.compile(r'\$json\(' + '$.data.alarm[1].number'.replace('$',"\$").replace('[','\[') + r'\)\$') # # key = str(sql_json(i)) # key = "123" diff --git a/scripts/relevance.py b/scripts/relevance.py index 8a57c65..e01edc7 100644 --- a/scripts/relevance.py +++ b/scripts/relevance.py @@ -16,23 +16,29 @@ from scripts.readYamlFile import ini_yaml """ response: 执行接口,根据jsonpath 读取返回结果的指定值 存入缓存 body: 执行前 把body转化成json格式 根据jsonpath 读取指定参数值 存入缓存 -url: 执行前 将url参数 正则匹配出来 存入缓存 取 body: get请求 根据name replace参数 post 根据path 更新json数据 -url: 根据缓存name 更新urlparam字段 """ +def valueHandle(data: str): + param_dict = {} + param_list = data.split("&") + for param in param_list: + param_dict[param.split("=")[0]] = param.split("=")[1] + return param_dict + + class Cache(object): - def __init__(self, path): + def __init__(self, path=r"D:\apitest\caches"): self.path = path mk_dir(path) self.del_list = os.listdir(self.path) def set(self, key, value): - with open(self.path + key + ".text", 'w', encoding="utf-8") as f: - f.write(value) + with open(self.path + "/" + key + ".text", 'w', encoding="utf-8") as f: + f.write(str(value)) def get(self, key): @@ -66,59 +72,56 @@ class Cache(object): shutil.rmtree(file_path) -def valueHandle(data: str): - param_dict = {} - param_list = data.split("&") - for param in param_list: - param_dict[param.split("=")[0]] = param.split("=")[1] - return param_dict - +class Relevance(object): -s = {'regionList': {'name': '区域列表', 'host': 'host', 'address': '/v1/apps/region/', 'method': 'get', 'relevance': [ - {'relevancetype': 'set', 'datasfrom': 'reponse', 'path': '$.data', 'name': "ceshi"}, -{'relevancetype': 'get', 'datasfrom': 'reponse', 'path': '$.data', 'name': "ceshi"}, - {'relevancetype': 'set', 'datasfrom': 'body', 'path': None, 'name': None}, - {'relevancetype': 'set', 'datasfrom': 'url', 'path': None, 'name': None}], 'relevanceCheck': None}} -res = { - "data": { - "haha": 123 - } -} + def __init__(self): + self.caches = Cache() + def respons_cache(self, data, respons): + values = jsonpath.jsonpath(respons, data['path']) + if not values: + raise ValueError("path错误") + self.caches.set(key=data["name"], value=values[0]) -class Relevance(object): + def body_cache(self, data, param): + if isinstance(param, dict): + values = jsonpath.jsonpath(param, data['path']) - def __init__(self): - self.caches = Cache(path=r"D:\apitest\caches") + self.caches.set(key=data["name"], value=values[0]) + else: + values = jsonpath.jsonpath(valueHandle(param), data['path']) + if not values: + raise ValueError("path错误") + self.caches.set(key=data["name"], value=values[0]) - def relevance(self, data: dict): + def relevance(self, data: dict, bodys=None, res=None): rel = data['regionList']['relevance'] + if rel is None: + pass for i in rel: - if i["datasfrom"] == 'body': - print(1) - elif i["datasfrom"] == 'reponse': - if i["relevancetype"] == "set": - values = jsonpath.jsonpath(res, i['path']) - if not values: - raise SyntaxError("path错误") - - elif i["relevancetype"] == "get": - # self.caches.clear_cache("haha") - self.caches.get(i["name"]) - # self.caches.get("haha1") - - - elif i["datasfrom"] == 'url': - print(3) + if i["cachefrom"] == 'body': + self.body_cache(i, bodys) + elif i["cachefrom"] == 'response': + self.respons_cache(i, res) else: raise TypeError("datasfrom错误") - return rel if __name__ == '__main__': + s = {'regionList': {'name': '区域列表', 'host': 'host', 'address': '/v1/apps/region/', 'method': 'get', 'relevance': [ + # {'relevancetype': 'set', 'datasfrom': 'reponse', 'path': '$.data', 'name': "ceshi"}, + # {'relevancetype': 'get', 'datasfrom': 'reponse', 'path': '$.data', 'name': "ceshi"}, + {'relevancetype': 'set', 'datasfrom': 'body', 'path': "$.code", 'name': "code"}, + {'relevancetype': 'set', 'datasfrom': 'url', 'path': None, 'name': None}], 'relevanceCheck': None}} + ress = { + "data": { + "haha": 123 + } + } + bo = "code=200&msg=success" + s1 = ini_yaml("urltest.yml", r"D:\apitest\test_suite\datas\saasApp") rels = Relevance() - rels.relevance(s) - + rels.relevance(s1, bodys=bo, res=ress) -- Gitee From facf3910202aa83d3ef8caad18d2da05d1336e2e Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Wed, 17 Nov 2021 14:08:55 +0800 Subject: [PATCH 14/30] =?UTF-8?q?=E5=AE=8C=E5=96=84=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=20=E8=AF=BB=E5=8F=96=E7=BC=93=E5=AD=98=20?= =?UTF-8?q?=E9=87=87=E7=94=A8=E6=AD=A3=E5=88=99=E5=8C=B9=E9=85=8D=20?= =?UTF-8?q?=E8=AF=A6=E6=83=85randomData.py=E4=B8=AD=E6=9F=A5=E7=9C=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 173 ++++++++++++++++++++--------- common/basePage.py | 46 ++++---- scripts/relevance.py | 15 +-- scripts/writePage.py | 4 +- setupMain.py | 12 +- test_suite/page/saasApp_pages_1.py | 34 +++--- 6 files changed, 178 insertions(+), 106 deletions(-) diff --git a/README.md b/README.md index 0007143..ce5bd35 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,26 @@ # **接口自动化测试框架** ## 目录结构 - |-- 接口自动化测试框架 # 主目录 - ├─common - ├─config - ├─scripts - ├─testsuite - ├─log - ├─report - ├─pytest.ini - ├─requirements.txt - ├─README.md - └─setupMain.py + |--接口自动化测试框架 # 主目录 + ├─common + ├─config + └─ config.ini + ├─scripts + ├─testsuite + ├─datas + └─ 项目文件夹 名称同config中 testname一致 + ├─ urlData.yml + └─ login.yml + ├─page # 所有请求封装 通过writePage.py根据url的yaml文件生成 + ├─testcase + └─ 项目文件夹 名称同config中 testname一致 # 测试用例 + └─ test_login.py + ├─caches # 关联用的临时缓存文件夹 + ├─log + ├─report + ├─pytest.ini + ├─requirements.txt + ├─README.md + └─setupMain.py # 整体执行程序 ###yaml param格式 @@ -20,53 +30,68 @@ headers: { "Content-Type": "application/json" } - data: { - "username": "name", - "password": "*****" - } - assert: - jsonpath: - - { - "path": "$.data.expense_trend[0].peak_hour.peak_hour", - "value": "$json($.data.trend[0].data.peak_hour)$", - "asserttype": "==", - "relevance": "web电费统计" # 根据特定返回结果获取 为被关联接口的info - } - - { - "path": "$.code", - "value": 0, - "asserttype": "==", - "relevance": - } - - { - "path": "$.data.id", - "value": 196, - "asserttype": "==", - "relevance": - } - sqlassert: - - { - "datas": [ - { - "path": "$.data.id", - "name": "id" - }, - { - "path": "$.data.username", - "name": "username" - }, - ], - "sql": "select * from saas.user where username = '****'", + data: + param: { + "username": "finsiot","password": $caches(pwd)$ # 读取缓存值 } - time: 2 + urlparam: { + id: 123 + }# 路径参数 v1/api/$url(id)$/ + assert: + jsonpath: + # 关联验证 + - { + "path": "$.data.expense_trend[0].peak_hour.peak_hour", + "value": "$json($.data.trend[0].data.peak_hour)$", # 期望值为其他接口请求数据 + "asserttype": "==", + "relevance": "web电费统计" # 根据特定返回结果获取 为被关联验证接口的info + } + - { + "path": "$.code", + "value": 0, + "asserttype": "==", + "relevance": + } + - { + "path": "$.data.id", + "value": 196, + "asserttype": "==", + "relevance": + } + sqlassert: + - { + "datas": [ + { + "path": "$.data.id", + "name": "id" + }, + { + "path": "$.data.username", + "name": "username" + }, + ], + "sql": "select * from saas.user where username = '****'", + # 取数据库查询出的第一条数据进行验证 如果存在 列名 username 值为$.data.username则通过 + } + time: 2 ###yaml url格式 - login: - name: '登录' + regionList: + name: '区域列表' host: 'host' - address: '/v1/apps/login/' - method: 'post - relevance: true # 关联验证 true: 为被关联接口 生成page时会返回特定格式结果{"web电费统计":{"code":0,"msg":"success"}} 根据这个key 去yaml assert中验证 + address: '/v1/apps/region/' + method: 'get' + relevance: + - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 + path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 + name: 'code' + - cachefrom: 'response' # response : 从结果中获取 body : 从参数中获取 + path: '$.data' + name: 'data' + relevanceCheck: true + # true为断言验证存在关联验证 此接口为被关联验证接口 接口结果为 期望值 + # 不存关联验证时 为空或者false + # 断言验证关联 ###关联验证 多接口 @@ -74,6 +99,46 @@ 多接口结果合并成一个字典传给 third_datas 单接口 两种方法 同多接口类似 或者直接传结果给third_data +### config.ini + + [directory] + pro_dir = 项目根目录 + log_dir = /logs/ + data_dir = /datas/ + page_dir = /page/ + report_xml_dir = /report/xml/ + report_html_dir = /report/html/ + test_suite = /test_suite/ + case_dir = /testcase/ + cache_dir = /caches/ + test_name = 测试项目名称 + [host] + http_type = http + host = + host_117 = + [email] + ;服务器 + mail_host = smtp.sina.com + ;发送邮箱 + mail_user = + ;口令 + mail_pass = + ;发送者 + sender = + ;接收邮箱 + receivers = + [database] + host = 192. + port = 3306 + user = root + password = + ;database = + database = + charset = utf8 + [dingding] + webhook = + secret = + ### 其他 jsonpath断言中value支持根据正则关联其他接口数据 ![img.png](img.png) diff --git a/common/basePage.py b/common/basePage.py index 670927f..3d34fb6 100644 --- a/common/basePage.py +++ b/common/basePage.py @@ -13,6 +13,7 @@ from requests_toolbelt import MultipartEncoder from scripts.log import Log from scripts.randomData import replace_random from config.confManage import host_manage +from scripts.relevance import Relevance Log() @@ -21,6 +22,7 @@ class apiSend(object): def __init__(self): self.http_type = host_manage(hos="${http_type}$") + self.rel = Relevance() @staticmethod def iniDatas(data): @@ -31,10 +33,14 @@ class apiSend(object): dataran = replace_random(data) return dataran - def post(self, address, header, request_parameter_type="json", timeout=8, data=None, files=None, host="host"): + def post(self, address, header,rel,request_parameter_type="json", timeout=8, data=None, files=None, host="host"): """ post请求 :param host: + :param rel: 关联情况 + - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 + path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 + name: 'code' :param address: 请求地址 :param header: 请求头 :param request_parameter_type: 请求参数格式(form_data,raw) @@ -92,20 +98,21 @@ class apiSend(object): return response.text else: logging.info("请求接口结果: %s" % str(response.json())) + self.rel.relevance(rel,bodys=data_random,res=response.json()) return response.json(), response.elapsed.total_seconds() - except json.decoder.JSONDecodeError: - return '' - except simplejson.errors.JSONDecodeError: - return '' except Exception as e: logging.exception('ERROR') logging.error(e) raise - def get(self, address, header, data, timeout=8, host="host"): + def get(self, address,rel, header, data, timeout=8, host="host"): """ get请求 :param host: + :param rel: 关联情况 + - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 + path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 + name: 'code' :param address: :param header: 请求头 :param data: 请求参数 @@ -131,21 +138,22 @@ class apiSend(object): response = requests.get(url=response.headers["location"]) try: logging.info("请求接口结果: %s" % str(response.json())) + self.rel.relevance(rel, bodys=data_random, res=response.json()) return response.json(), response.elapsed.total_seconds() - # except json.decoder.JSONDecodeError: - # return '' - # except simplejson.errors.JSONDecodeError: - # return '' except Exception as e: logging.exception('ERROR') logging.error(e) raise - def put(self, address, header, request_parameter_type="json", timeout=8, data=None, files=None, host="host"): + def put(self, address,rel, header, request_parameter_type="json", timeout=8, data=None, files=None, host="host"): """ put请求 :param host: :param address: + :param rel: 关联情况 + - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 + path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 + name: 'code' :param header: 请求头 :param request_parameter_type: 请求参数格式(form_data,raw) :param timeout: 超时时间 @@ -171,20 +179,21 @@ class apiSend(object): response = requests.put(url=url, data=data_random, headers=header, timeout=timeout, files=files) try: logging.info("请求接口结果: %s" % str(response.json())) + self.rel.relevance(rel, bodys=data_random, res=response.json()) return response.json(), response.elapsed.total_seconds() - except json.decoder.JSONDecodeError: - return '' - except simplejson.errors.JSONDecodeError: - return '' except Exception as e: logging.exception('ERROR') logging.error(e) raise - def delete(self, address, header, data, timeout=8, host="host"): + def delete(self, address, rel,header, data, timeout=8, host="host"): """ get请求 :param host: + :param rel: 关联情况 + - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 + path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 + name: 'code' :param address: :param header: 请求头 :param data: 请求参数 @@ -205,11 +214,8 @@ class apiSend(object): try: logging.info("请求接口结果: %s" % str(response.json())) + self.rel.relevance(rel, bodys=data_random, res=response.json()) return response.json(), response.elapsed.total_seconds() - except json.decoder.JSONDecodeError: - return '' - except simplejson.errors.JSONDecodeError: - return '' except Exception as e: logging.exception('ERROR') logging.error(e) diff --git a/scripts/relevance.py b/scripts/relevance.py index e01edc7..5ffe44b 100644 --- a/scripts/relevance.py +++ b/scripts/relevance.py @@ -12,6 +12,7 @@ import shutil import jsonpath from scripts.mkDir import mk_dir from scripts.readYamlFile import ini_yaml +from config.confManage import dir_manage """ response: 执行接口,根据jsonpath 读取返回结果的指定值 存入缓存 @@ -20,6 +21,7 @@ body: 执行前 把body转化成json格式 根据jsonpath 读取指定参数值 取 body: get请求 根据name replace参数 post 根据path 更新json数据 """ +cachepath = dir_manage("${pro_dir}$") + dir_manage("${cache_dir}$") def valueHandle(data: str): @@ -31,19 +33,19 @@ def valueHandle(data: str): class Cache(object): - def __init__(self, path=r"D:\apitest\caches"): + def __init__(self, path=cachepath): self.path = path mk_dir(path) self.del_list = os.listdir(self.path) def set(self, key, value): - with open(self.path + "/" + key + ".text", 'w', encoding="utf-8") as f: + with open(self.path + key + ".text", 'w', encoding="utf-8") as f: f.write(str(value)) def get(self, key): if key + ".text" in self.del_list: - with open(self.path + "/" + key + ".text", 'r', encoding="utf-8") as f: + with open(self.path + key + ".text", 'r', encoding="utf-8") as f: value = f.read() return value else: @@ -51,7 +53,7 @@ class Cache(object): def set_many(self, data: dict): for i in data: - with open(self.path + "/" + i + ".text", 'w', encoding="utf-8") as f: + with open(self.path + i + ".text", 'w', encoding="utf-8") as f: f.write(str(data[i])) def clear_all_cache(self): @@ -95,10 +97,9 @@ class Relevance(object): self.caches.set(key=data["name"], value=values[0]) def relevance(self, data: dict, bodys=None, res=None): - rel = data['regionList']['relevance'] - if rel is None: + if data is None: pass - for i in rel: + for i in data: if i["cachefrom"] == 'body': self.body_cache(i, bodys) elif i["cachefrom"] == 'response': diff --git a/scripts/writePage.py b/scripts/writePage.py index 47cd6ff..17b9f13 100644 --- a/scripts/writePage.py +++ b/scripts/writePage.py @@ -37,10 +37,10 @@ def {testtitle}(casedata):""".format(testtitle=item[0])) """ logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"])""" + data=casedata["data"],rel=data["relevance"])""" ) - if item[1]["relevance"]: + if item[1]["relevanceCheck"]: f.write( """ return {casedata["info"]:res}, restime\n\n""" diff --git a/setupMain.py b/setupMain.py index 8e703da..dc6419e 100644 --- a/setupMain.py +++ b/setupMain.py @@ -28,21 +28,21 @@ def run(): mk_dir(temp_path) mk_dir(html_path) # 执行命令行 - # args = ['-s', '-q', test_case_path, '--alluredir', temp_path] - args = ['-s', '-q', test_case_path,] + args = ['-s', '-q', test_case_path, '--alluredir', temp_path] + # args = ['-s', '-q', test_case_path,] pytest.main(args) - # cmd = 'allure generate %s -o %s -c' % (temp_path, html_path) - # os.system(cmd) + cmd = 'allure generate %s -o %s -c' % (temp_path, html_path) + os.system(cmd) # 发送报告 # send_email(localtime + "测试报告", "http://192.168.1.2:9999") # 钉钉发送 # ding = DingTalkSendMsg() # ding.send_text("点击链接打开测试报告 http://192.168.1.2:9999",[13688400244]) # 生成html报告 - # os.system(r'allure generate {0} -o {1} '.format(temp_path, html_path)) + os.system(r'allure generate {0} -o {1} '.format(temp_path, html_path)) # # 打开报告服务 并指定端口 - # os.system(r'allure serve {0} -p 9999'.format(temp_path)) + os.system(r'allure serve {0} -p 9999'.format(temp_path)) if __name__ == '__main__': diff --git a/test_suite/page/saasApp_pages_1.py b/test_suite/page/saasApp_pages_1.py index da9bbb0..373dade 100644 --- a/test_suite/page/saasApp_pages_1.py +++ b/test_suite/page/saasApp_pages_1.py @@ -8,7 +8,7 @@ def login(casedata): data = urlData["login"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -16,7 +16,7 @@ def forgetPassword(casedata): data = urlData["forgetPassword"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -24,7 +24,7 @@ def mobileCode(casedata): data = urlData["mobileCode"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -32,7 +32,7 @@ def todayTask(casedata): data = urlData["todayTask"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -40,7 +40,7 @@ def companyName(casedata): data = urlData["companyName"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -48,7 +48,7 @@ def companyPower(casedata): data = urlData["companyPower"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -56,7 +56,7 @@ def deviceState(casedata): data = urlData["deviceState"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -64,7 +64,7 @@ def companyAlarm(casedata): data = urlData["companyAlarm"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -72,7 +72,7 @@ def todayTrend(casedata): data = urlData["todayTrend"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -80,7 +80,7 @@ def alarmStatistic(casedata): data = urlData["alarmStatistic"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -88,7 +88,7 @@ def alarmTrend(casedata): data = urlData["alarmTrend"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -96,7 +96,7 @@ def alarmRank(casedata): data = urlData["alarmRank"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -104,7 +104,7 @@ def alarmDistribute(casedata): data = urlData["alarmDistribute"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -112,7 +112,7 @@ def powerToday(casedata): data = urlData["powerToday"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -120,7 +120,7 @@ def powerFees(casedata): data = urlData["powerFees"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -128,7 +128,7 @@ def functionList(casedata): data = urlData["functionList"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -136,6 +136,6 @@ def regionList(casedata): data = urlData["regionList"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime -- Gitee From b798a57d931f35c0804d5c7a92264c97076483a7 Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Wed, 17 Nov 2021 14:32:06 +0800 Subject: [PATCH 15/30] =?UTF-8?q?=E5=AE=8C=E5=96=84=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=20=E8=AF=BB=E5=8F=96=E7=BC=93=E5=AD=98=20?= =?UTF-8?q?=E9=87=87=E7=94=A8=E6=AD=A3=E5=88=99=E5=8C=B9=E9=85=8D=20?= =?UTF-8?q?=E8=AF=A6=E6=83=85randomData.py=E4=B8=AD=E6=9F=A5=E7=9C=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +++++----- test_suite/testcase/saasApp/test_ca.py | 23 ----------------------- test_suite/testcase/saasApp/test_ca2.py | 21 --------------------- 3 files changed, 5 insertions(+), 49 deletions(-) delete mode 100644 test_suite/testcase/saasApp/test_ca.py delete mode 100644 test_suite/testcase/saasApp/test_ca2.py diff --git a/README.md b/README.md index ce5bd35..bfa22fb 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ └─setupMain.py # 整体执行程序 -###yaml param格式 +##yaml param格式 login: - info: "用户名登录-成功" @@ -74,7 +74,7 @@ # 取数据库查询出的第一条数据进行验证 如果存在 列名 username 值为$.data.username则通过 } time: 2 -###yaml url格式 +##yaml url格式 regionList: name: '区域列表' @@ -93,13 +93,13 @@ # 不存关联验证时 为空或者false # 断言验证关联 -###关联验证 +##关联验证 多接口 ![img_2.png](img_2.png) 多接口结果合并成一个字典传给 third_datas 单接口 两种方法 同多接口类似 或者直接传结果给third_data -### config.ini +## config.ini [directory] pro_dir = 项目根目录 @@ -139,7 +139,7 @@ webhook = secret = -### 其他 +## 其他 jsonpath断言中value支持根据正则关联其他接口数据 ![img.png](img.png) ![img_1.png](img_1.png) diff --git a/test_suite/testcase/saasApp/test_ca.py b/test_suite/testcase/saasApp/test_ca.py deleted file mode 100644 index c73ca81..0000000 --- a/test_suite/testcase/saasApp/test_ca.py +++ /dev/null @@ -1,23 +0,0 @@ -# coding:utf-8 -""" -@author: 井松 -@contact: 529548204@qq.com -@file: test_ca.py -@time: 2021/11/16 14:15 -""" - -import cacheout - -class Test_login(object): - # def setup(self): - # self.re = saasPages() - # ids=[i["info"] for i in paramData["login"]] - - def test_login(self,cache): - cache.set() - # relv.caches.set_many({ - # "haha123": 1234 - # }) - # print(relv.caches.get("haha123")) - def test_login2(self,relv): - print(relv.caches.get("haha123")) \ No newline at end of file diff --git a/test_suite/testcase/saasApp/test_ca2.py b/test_suite/testcase/saasApp/test_ca2.py deleted file mode 100644 index ab00d25..0000000 --- a/test_suite/testcase/saasApp/test_ca2.py +++ /dev/null @@ -1,21 +0,0 @@ -# coding:utf-8 -""" -@author: 井松 -@contact: 529548204@qq.com -@file: test_ca.py -@time: 2021/11/16 14:15 -""" - -import cacheout - -class Test_login(object): - # def setup(self): - # self.re = saasPages() - # ids=[i["info"] for i in paramData["login"]] - - def test_login(self,relv,cache): - cache.set() - - print(relv.caches.get("haha123")) - def test_login2(self,relv): - print(relv.caches.get("haha123")) \ No newline at end of file -- Gitee From b8f94107dd8866e22314b6c8f1e4e01d67f81f5d Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Wed, 17 Nov 2021 14:37:32 +0800 Subject: [PATCH 16/30] =?UTF-8?q?=E5=AE=8C=E5=96=84=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=20=E8=AF=BB=E5=8F=96=E7=BC=93=E5=AD=98=20?= =?UTF-8?q?=E9=87=87=E7=94=A8=E6=AD=A3=E5=88=99=E5=8C=B9=E9=85=8D=20?= =?UTF-8?q?=E8=AF=A6=E6=83=85randomData.py=E4=B8=AD=E6=9F=A5=E7=9C=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bfa22fb..47c9b47 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ └─setupMain.py # 整体执行程序 -##yaml param格式 +## yaml param格式 login: - info: "用户名登录-成功" @@ -74,7 +74,7 @@ # 取数据库查询出的第一条数据进行验证 如果存在 列名 username 值为$.data.username则通过 } time: 2 -##yaml url格式 +## yaml url格式 regionList: name: '区域列表' -- Gitee From 859dfaa4ef259f4fd449c946c12dced748cd9073 Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Wed, 17 Nov 2021 15:16:29 +0800 Subject: [PATCH 17/30] =?UTF-8?q?=E5=AE=8C=E5=96=84=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=20=E8=AF=BB=E5=8F=96=E7=BC=93=E5=AD=98=20?= =?UTF-8?q?=E9=87=87=E7=94=A8=E6=AD=A3=E5=88=99=E5=8C=B9=E9=85=8D=20?= =?UTF-8?q?=E8=AF=A6=E6=83=85randomData.py=E4=B8=AD=E6=9F=A5=E7=9C=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/basePage.py | 10 +++---- scripts/relevance.py | 19 ++++++------- test_suite/page/saasApp_pages.py | 34 +++++++++++------------ test_suite/testcase/saasApp/test_login.py | 4 +++ 4 files changed, 35 insertions(+), 32 deletions(-) diff --git a/common/basePage.py b/common/basePage.py index 3d34fb6..c3139c7 100644 --- a/common/basePage.py +++ b/common/basePage.py @@ -221,16 +221,16 @@ class apiSend(object): logging.error(e) raise - def __call__(self, address, method, headers, data, **kwargs): + def __call__(self,rel, address, method, headers, data,**kwargs): try: if method == "post" or method == 'POST': - return self.post(address=address, data=data, header=headers, **kwargs) + return self.post(address=address, data=data, header=headers,rel=rel, **kwargs) elif method == "get" or method == 'GET': - return self.get(address=address, data=data, header=headers, **kwargs) + return self.get(address=address, data=data, header=headers,rel=rel, **kwargs) elif method == "delete" or method == 'DELETE': - return self.delete(address=address, data=data, header=headers ** kwargs) + return self.delete(address=address, data=data, header=headers,rel=rel, ** kwargs) elif method == "put" or method == 'PUT': - return self.put(address=address, data=data, header=headers, **kwargs) + return self.put(address=address, data=data, header=headers,rel=rel, **kwargs) else: raise TypeError(f"请求异常,检查yml文件method") except Exception: diff --git a/scripts/relevance.py b/scripts/relevance.py index 5ffe44b..3bab009 100644 --- a/scripts/relevance.py +++ b/scripts/relevance.py @@ -97,16 +97,15 @@ class Relevance(object): self.caches.set(key=data["name"], value=values[0]) def relevance(self, data: dict, bodys=None, res=None): - if data is None: - pass - for i in data: - if i["cachefrom"] == 'body': - self.body_cache(i, bodys) - elif i["cachefrom"] == 'response': - self.respons_cache(i, res) - else: - raise TypeError("datasfrom错误") - return rel + if data is not None: + for i in data: + if i["cachefrom"] == 'body': + self.body_cache(i, bodys) + elif i["cachefrom"] == 'response': + self.respons_cache(i, res) + else: + raise TypeError("datasfrom错误") + if __name__ == '__main__': diff --git a/test_suite/page/saasApp_pages.py b/test_suite/page/saasApp_pages.py index da9bbb0..373dade 100644 --- a/test_suite/page/saasApp_pages.py +++ b/test_suite/page/saasApp_pages.py @@ -8,7 +8,7 @@ def login(casedata): data = urlData["login"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -16,7 +16,7 @@ def forgetPassword(casedata): data = urlData["forgetPassword"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -24,7 +24,7 @@ def mobileCode(casedata): data = urlData["mobileCode"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -32,7 +32,7 @@ def todayTask(casedata): data = urlData["todayTask"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -40,7 +40,7 @@ def companyName(casedata): data = urlData["companyName"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -48,7 +48,7 @@ def companyPower(casedata): data = urlData["companyPower"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -56,7 +56,7 @@ def deviceState(casedata): data = urlData["deviceState"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -64,7 +64,7 @@ def companyAlarm(casedata): data = urlData["companyAlarm"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -72,7 +72,7 @@ def todayTrend(casedata): data = urlData["todayTrend"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -80,7 +80,7 @@ def alarmStatistic(casedata): data = urlData["alarmStatistic"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -88,7 +88,7 @@ def alarmTrend(casedata): data = urlData["alarmTrend"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -96,7 +96,7 @@ def alarmRank(casedata): data = urlData["alarmRank"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -104,7 +104,7 @@ def alarmDistribute(casedata): data = urlData["alarmDistribute"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -112,7 +112,7 @@ def powerToday(casedata): data = urlData["powerToday"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -120,7 +120,7 @@ def powerFees(casedata): data = urlData["powerFees"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -128,7 +128,7 @@ def functionList(casedata): data = urlData["functionList"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime @@ -136,6 +136,6 @@ def regionList(casedata): data = urlData["regionList"] logging.info("{}".format(casedata["info"])) res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) + data=casedata["data"],rel=data["relevance"]) return res, restime diff --git a/test_suite/testcase/saasApp/test_login.py b/test_suite/testcase/saasApp/test_login.py index fcfddcb..c8980c1 100644 --- a/test_suite/testcase/saasApp/test_login.py +++ b/test_suite/testcase/saasApp/test_login.py @@ -1,4 +1,6 @@ # coding:utf-8 +import pytest + from test_suite.page.saasApp_pages import * paramData = ini_yaml("loginData.yml") @@ -25,10 +27,12 @@ class Test_login(object): asserting(hope_res=casedata["assert"], real_res=res,re_time=restime) # cache.set("session_id", res["session_id"]) + @allure.story("Test_forgetPassword") @pytest.mark.parametrize('casedata', paramData["forgetPassword"], ids=[i["info"] for i in paramData["forgetPassword"]]) @pytest.mark.flaky(reruns=1, reruns_delay=1) @pytest.mark.run(order=1) + @pytest.mark.skip("验证码异常") def test_forgetPassword(self, casedata,cache): res, restime = mobileCode(paramData["mobileCode"][1]) casedata['data']["param"]['session_key'] = cache.get('k',None) -- Gitee From 1dfd4ba9dfd6ee63805b1e681e9e1d5060596f0c Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Thu, 18 Nov 2021 15:52:33 +0800 Subject: [PATCH 18/30] =?UTF-8?q?=E5=AE=8C=E5=96=84=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=20=E8=AF=BB=E5=8F=96=E7=BC=93=E5=AD=98=20?= =?UTF-8?q?=E9=87=87=E7=94=A8=E6=AD=A3=E5=88=99=E5=8C=B9=E9=85=8D=20?= =?UTF-8?q?=E8=AF=A6=E6=83=85randomData.py=E4=B8=AD=E6=9F=A5=E7=9C=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++---- common/basePage.py | 1 + scripts/readYamlFile.py | 2 +- test_suite/testcase/saasApp/conftest.py | 6 ------ 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 47c9b47..c0c52d8 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ ├─testsuite ├─datas └─ 项目文件夹 名称同config中 testname一致 - ├─ urlData.yml - └─ login.yml + ├─ urlData.yml # 所有 url 具体格式参考下面YAML URL格式说明 + └─ login.yml # 用例数据 格式参考下面YAML PARAM格式说明 ├─page # 所有请求封装 通过writePage.py根据url的yaml文件生成 ├─testcase └─ 项目文件夹 名称同config中 testname一致 # 测试用例 @@ -32,7 +32,7 @@ } data: param: { - "username": "finsiot","password": $caches(pwd)$ # 读取缓存值 + "username": "finsiot","password": _$caches(pwd)$ # 读取缓存值_ } urlparam: { id: 123 @@ -79,7 +79,7 @@ regionList: name: '区域列表' host: 'host' - address: '/v1/apps/region/' + address: '/v1/apps/$url(region_id)$/' # $url(region_id)$ 正则匹配参数中的路径参数 method: 'get' relevance: - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 diff --git a/common/basePage.py b/common/basePage.py index c3139c7..7c9e555 100644 --- a/common/basePage.py +++ b/common/basePage.py @@ -243,3 +243,4 @@ if __name__ == '__main__': "Content-Type": "application/json" }, host="host") print(ress) +que \ No newline at end of file diff --git a/scripts/readYamlFile.py b/scripts/readYamlFile.py index 131ed28..a37c71d 100644 --- a/scripts/readYamlFile.py +++ b/scripts/readYamlFile.py @@ -17,7 +17,7 @@ def ini_yaml(filename, path=datapath): if __name__ == '__main__': # get_yaml_data(r"F:\api2.0\config\runConfig.yml") - runConfig_dict = ini_yaml("loginData.yml") + runConfig_dict = ini_yaml("loginData.yaml",path=r"D:\apitest\test_suite") # case_level = runConfig_dict[0]["address"].format(**{"home_id": "123"}) print(runConfig_dict) # print(case_level) diff --git a/test_suite/testcase/saasApp/conftest.py b/test_suite/testcase/saasApp/conftest.py index 38f68d3..2e1538e 100644 --- a/test_suite/testcase/saasApp/conftest.py +++ b/test_suite/testcase/saasApp/conftest.py @@ -29,9 +29,3 @@ def third_login(): res, restime = apisend(address=data["address"], method=data["method"], headers=paramData["headers"], data=paramData["data"]) return res -@pytest.fixture(scope="module") -def relv(): - global haha - haha=1 - ca = Relevance() - return haha \ No newline at end of file -- Gitee From 17c05ae96323f55168dfcac30fa393b2f470b3f2 Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Thu, 18 Nov 2021 16:12:33 +0800 Subject: [PATCH 19/30] =?UTF-8?q?=E5=AE=8C=E5=96=84=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=20=E8=AF=BB=E5=8F=96=E7=BC=93=E5=AD=98=20?= =?UTF-8?q?=E9=87=87=E7=94=A8=E6=AD=A3=E5=88=99=E5=8C=B9=E9=85=8D=20?= =?UTF-8?q?=E8=AF=A6=E6=83=85randomData.py=E4=B8=AD=E6=9F=A5=E7=9C=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 210 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 109 insertions(+), 101 deletions(-) diff --git a/README.md b/README.md index c0c52d8..718e9c2 100644 --- a/README.md +++ b/README.md @@ -25,73 +25,79 @@ ## yaml param格式 - login: - - info: "用户名登录-成功" - headers: { - "Content-Type": "application/json" +``` yaml +login: +- info: "用户名登录-成功" \ + headers: { + "Content-Type": "application/json"\ + } + data: + param: { + "username": "finsiot","password": "$caches(pwd)$" # 读取缓存值 + } + urlparam: { + id: 123 + }# 路径参数 v1/api/$url(id)$/ + assert: + jsonpath: + # 关联验证 + - { + "path": "$.data.expense_trend[0].peak_hour.peak_hour", + "value": "$json($.data.trend[0].data.peak_hour)$", # 期望值为其他接口请求数据 + "asserttype": "==", + "relevance": "web电费统计" # 根据特定返回结果获取 为被关联验证接口的info + } + - { + "path": "$.code", + "value": 0, + "asserttype": "==", + "relevance": + } + - { + "path": "$.data.id", + "value": 196, + "asserttype": "==", + "relevance": + } + sqlassert: + - { + "datas": [ + { + "path": "$.data.id", + "name": "id" + }, + { + "path": "$.data.username", + "name": "username" + }, + ], + "sql": "select * from saas.user where username = '****'", + # 取数据库查询出的第一条数据进行验证 如果存在 列名 username 值为$.data.username则通过 } - data: - param: { - "username": "finsiot","password": _$caches(pwd)$ # 读取缓存值_ - } - urlparam: { - id: 123 - }# 路径参数 v1/api/$url(id)$/ - assert: - jsonpath: - # 关联验证 - - { - "path": "$.data.expense_trend[0].peak_hour.peak_hour", - "value": "$json($.data.trend[0].data.peak_hour)$", # 期望值为其他接口请求数据 - "asserttype": "==", - "relevance": "web电费统计" # 根据特定返回结果获取 为被关联验证接口的info - } - - { - "path": "$.code", - "value": 0, - "asserttype": "==", - "relevance": - } - - { - "path": "$.data.id", - "value": 196, - "asserttype": "==", - "relevance": - } - sqlassert: - - { - "datas": [ - { - "path": "$.data.id", - "name": "id" - }, - { - "path": "$.data.username", - "name": "username" - }, - ], - "sql": "select * from saas.user where username = '****'", - # 取数据库查询出的第一条数据进行验证 如果存在 列名 username 值为$.data.username则通过 - } - time: 2 + time: 2 +``` + + ## yaml url格式 - regionList: - name: '区域列表' - host: 'host' - address: '/v1/apps/$url(region_id)$/' # $url(region_id)$ 正则匹配参数中的路径参数 - method: 'get' - relevance: - - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 - path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 - name: 'code' - - cachefrom: 'response' # response : 从结果中获取 body : 从参数中获取 - path: '$.data' - name: 'data' - relevanceCheck: true - # true为断言验证存在关联验证 此接口为被关联验证接口 接口结果为 期望值 - # 不存关联验证时 为空或者false - # 断言验证关联 +``` yaml +regionList: + name: '区域列表' + host: 'host' + address: '/v1/apps/$url(region_id)$/' # $url(region_id)$ 正则匹配参数中的路径参数 + method: 'get' + relevance: + - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 + path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 + name: 'code' + - cachefrom: 'response' # response : 从结果中获取 body : 从参数中获取 + path: '$.data' + name: 'data' + relevanceCheck: true + # true为断言验证存在关联验证 此接口为被关联验证接口 接口结果为 期望值 + # 不存关联验证时 为空或者false + # 断言验证关联 +``` ##关联验证 多接口 @@ -101,43 +107,45 @@ 两种方法 同多接口类似 或者直接传结果给third_data ## config.ini - [directory] - pro_dir = 项目根目录 - log_dir = /logs/ - data_dir = /datas/ - page_dir = /page/ - report_xml_dir = /report/xml/ - report_html_dir = /report/html/ - test_suite = /test_suite/ - case_dir = /testcase/ - cache_dir = /caches/ - test_name = 测试项目名称 - [host] - http_type = http - host = - host_117 = - [email] - ;服务器 - mail_host = smtp.sina.com - ;发送邮箱 - mail_user = - ;口令 - mail_pass = - ;发送者 - sender = - ;接收邮箱 - receivers = - [database] - host = 192. - port = 3306 - user = root - password = - ;database = - database = - charset = utf8 - [dingding] - webhook = - secret = +```ini +[directory] +pro_dir = 项目根目录 +log_dir = /logs/ +data_dir = /datas/ +page_dir = /page/ +report_xml_dir = /report/xml/ +report_html_dir = /report/html/ +test_suite = /test_suite/ +case_dir = /testcase/ +cache_dir = /caches/ +test_name = 测试项目名称 +[host] +http_type = http +host = +host_117 = +[email] +;服务器 +mail_host = smtp.sina.com +;发送邮箱 +mail_user = +;口令 +mail_pass = +;发送者 +sender = +;接收邮箱 +receivers = +[database] +host = 192. +port = 3306 +user = root +password = +;database = +database = +charset = utf8 +[dingding] +webhook = +secret = +``` ## 其他 jsonpath断言中value支持根据正则关联其他接口数据 -- Gitee From 92a94461b4549fce64d42002e4026a56651a8988 Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Fri, 19 Nov 2021 09:58:33 +0800 Subject: [PATCH 20/30] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E5=A4=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- common/basePage.py | 66 ++-- common/basePageSession.py | 447 ++++++++++++++++++++++ test_suite/testcase/saasApp/test_alarm.py | 5 +- 4 files changed, 485 insertions(+), 37 deletions(-) create mode 100644 common/basePageSession.py diff --git a/README.md b/README.md index 718e9c2..363aadc 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,9 @@ ``` yaml login: -- info: "用户名登录-成功" \ +- info: "用户名登录-成功" headers: { - "Content-Type": "application/json"\ + "Content-Type": "application/json" } data: param: { diff --git a/common/basePage.py b/common/basePage.py index 7c9e555..4c9b506 100644 --- a/common/basePage.py +++ b/common/basePage.py @@ -33,7 +33,7 @@ class apiSend(object): dataran = replace_random(data) return dataran - def post(self, address, header,rel,request_parameter_type="json", timeout=8, data=None, files=None, host="host"): + def post(self, address, header, rel, timeout=8, data=None, files=None, host="host"): """ post请求 :param host: @@ -43,7 +43,6 @@ class apiSend(object): name: 'code' :param address: 请求地址 :param header: 请求头 - :param request_parameter_type: 请求参数格式(form_data,raw) :param timeout: 超时时间 :param data: 请求参数 :param files: 文件路径 @@ -56,7 +55,7 @@ class apiSend(object): logging.info("请求地址:%s" % "" + url) logging.info("请求头: %s" % str(header)) logging.info("请求参数: %s" % str(data_random)) - if 'form-data' in request_parameter_type: + if "multipart/form-data" in header.values: with allure.step("POST上传文件"): allure.attach(name="请求地址", body=url) allure.attach(name="请求头", body=str(header)) @@ -77,16 +76,18 @@ class apiSend(object): boundary='-----------------------------' + str(random.randint(int(1e28), int(1e29 - 1))) ) header['Content-Type'] = multipart.content_type - response = requests.post(url=url, data=data, headers=header, timeout=timeout) else: - # print(type(data)) multipart = MultipartEncoder(data) - response = requests.post(url=url, data=multipart, headers={'Content-Type': multipart.content_type}, timeout=timeout) + elif 'application/json' in header.values: + with allure.step("POST请求接口"): + allure.attach(name="请求地址", body=url) + allure.attach(name="请求头", body=str(header)) + allure.attach(name="请求参数", body=str(data_random)) + response = requests.post(url=url, json=data_random, headers=header, timeout=timeout) else: - with allure.step("POST请求接口"): allure.attach(name="请求地址", body=url) allure.attach(name="请求头", body=str(header)) @@ -98,14 +99,13 @@ class apiSend(object): return response.text else: logging.info("请求接口结果: %s" % str(response.json())) - self.rel.relevance(rel,bodys=data_random,res=response.json()) + self.rel.relevance(rel, bodys=data_random, res=response.json()) return response.json(), response.elapsed.total_seconds() except Exception as e: - logging.exception('ERROR') logging.error(e) raise - def get(self, address,rel, header, data, timeout=8, host="host"): + def get(self, address, rel, header, data, timeout=8, host="host"): """ get请求 :param host: @@ -128,7 +128,6 @@ class apiSend(object): logging.info("请求地址:%s" % "" + url) logging.info("请求头: %s" % str(header)) logging.info("请求参数: %s" % str(data_random)) - with allure.step("GET请求接口"): allure.attach(name="请求地址", body=url) allure.attach(name="请求头", body=str(header)) @@ -141,11 +140,10 @@ class apiSend(object): self.rel.relevance(rel, bodys=data_random, res=response.json()) return response.json(), response.elapsed.total_seconds() except Exception as e: - logging.exception('ERROR') logging.error(e) raise - def put(self, address,rel, header, request_parameter_type="json", timeout=8, data=None, files=None, host="host"): + def put(self, address, rel, header, timeout=8, data=None, files=None, host="host"): """ put请求 :param host: @@ -155,7 +153,7 @@ class apiSend(object): path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 name: 'code' :param header: 请求头 - :param request_parameter_type: 请求参数格式(form_data,raw) + :param timeout: 超时时间 :param data: 请求参数 :param files: 文件路径 @@ -165,28 +163,30 @@ class apiSend(object): url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress logging.info("请求地址:%s" % "" + url) logging.info("请求头: %s" % str(header)) - - if request_parameter_type == 'raw': - data_random = self.iniDatas(data["param"]) - else: - data_random = data + data_random = self.iniDatas(data["param"]) logging.info("请求参数: %s" % str(data_random)) - with allure.step("PUT请求接口"): - allure.attach(name="请求地址", body=url) - allure.attach(name="请求头", body=str(header)) - allure.attach(name="请求参数", body=str(data_random)) + if 'application/json' in header.values: + with allure.step("PUT请求接口"): + allure.attach(name="请求地址", body=url) + allure.attach(name="请求头", body=str(header)) + allure.attach(name="请求参数", body=str(data_random)) + response = requests.put(url=url, json=data_random, headers=header, timeout=timeout, files=files) + else: + with allure.step("PUT请求接口"): + allure.attach(name="请求地址", body=url) + allure.attach(name="请求头", body=str(header)) + allure.attach(name="请求参数", body=str(data_random)) - response = requests.put(url=url, data=data_random, headers=header, timeout=timeout, files=files) + response = requests.put(url=url, data=data_random, headers=header, timeout=timeout, files=files) try: logging.info("请求接口结果: %s" % str(response.json())) self.rel.relevance(rel, bodys=data_random, res=response.json()) return response.json(), response.elapsed.total_seconds() except Exception as e: - logging.exception('ERROR') logging.error(e) raise - def delete(self, address, rel,header, data, timeout=8, host="host"): + def delete(self, address, rel, header, data, timeout=8, host="host"): """ get请求 :param host: @@ -217,20 +217,19 @@ class apiSend(object): self.rel.relevance(rel, bodys=data_random, res=response.json()) return response.json(), response.elapsed.total_seconds() except Exception as e: - logging.exception('ERROR') logging.error(e) raise - def __call__(self,rel, address, method, headers, data,**kwargs): + def __call__(self, rel, address, method, headers, data, **kwargs): try: if method == "post" or method == 'POST': - return self.post(address=address, data=data, header=headers,rel=rel, **kwargs) + return self.post(address=address, data=data, header=headers, rel=rel, **kwargs) elif method == "get" or method == 'GET': - return self.get(address=address, data=data, header=headers,rel=rel, **kwargs) + return self.get(address=address, data=data, header=headers, rel=rel, **kwargs) elif method == "delete" or method == 'DELETE': - return self.delete(address=address, data=data, header=headers,rel=rel, ** kwargs) + return self.delete(address=address, data=data, header=headers, rel=rel, **kwargs) elif method == "put" or method == 'PUT': - return self.put(address=address, data=data, header=headers,rel=rel, **kwargs) + return self.put(address=address, data=data, header=headers, rel=rel, **kwargs) else: raise TypeError(f"请求异常,检查yml文件method") except Exception: @@ -239,8 +238,7 @@ class apiSend(object): apisend = apiSend() if __name__ == '__main__': - ress = apisend("/$url(home_id)$", "get", data={"param":None,"urlparam": {"home_id": 123}}, headers={ + ress = apisend("/$url(home_id)$", "get", data={"param": None, "urlparam": {"home_id": 123}}, headers={ "Content-Type": "application/json" }, host="host") print(ress) -que \ No newline at end of file diff --git a/common/basePageSession.py b/common/basePageSession.py new file mode 100644 index 0000000..e1aade2 --- /dev/null +++ b/common/basePageSession.py @@ -0,0 +1,447 @@ +# coding:utf-8 +""" +@author: 井松 +@contact: 529548204@qq.com +@file: basePageSession.py +@time: 2021/11/19 9:57 +""" +# coding:utf-8 +import json +import logging +import os +import random +import time + +import allure +import requests +import simplejson +from requests_toolbelt import MultipartEncoder + +from scripts.log import Log +from scripts.randomData import replace_random +from config.confManage import host_manage +from scripts.relevance import Relevance + +Log() + + +class apiSend(object): + + def __init__(self): + self.http_type = host_manage(hos="${http_type}$") + self.rel = Relevance() + self.session = requests.Session() + + @staticmethod + def iniDatas(data): + if isinstance(data, dict): + data = json.dumps(data) + if data is None: + return data + dataran = replace_random(data) + return dataran + + def post(self, address, header, rel, timeout=8, data=None, files=None, host="host"): + """ + post请求 + :param host: + :param rel: 关联情况 + - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 + path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 + name: 'code' + :param address: 请求地址 + :param header: 请求头 + :param timeout: 超时时间 + :param data: 请求参数 + :param files: 文件路径 + :return: + """ + + iniaddress = replace_random(address, param=data["urlparam"]) + url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress + data_random = self.iniDatas(data["param"]) + logging.info("请求地址:%s" % "" + url) + logging.info("请求头: %s" % str(header)) + logging.info("请求参数: %s" % str(data_random)) + if "multipart/form-data" in header.values: + with allure.step("POST上传文件"): + allure.attach(name="请求地址", body=url) + allure.attach(name="请求头", body=str(header)) + allure.attach(name="请求参数", body=str(data_random)) + if files is not None: + for i in files: + value = files[i] + if isinstance(value, int): + files[i] = str(value) + pass + elif '/' in value: + file_parm = i + files[file_parm] = (os.path.basename(value), open(value, 'rb'), 'application/octet-stream') + else: + pass + multipart = MultipartEncoder( + fields=files, + boundary='-----------------------------' + str(random.randint(int(1e28), int(1e29 - 1))) + ) + header['Content-Type'] = multipart.content_type + response = requests.post(url=url, data=data, headers=header, timeout=timeout) + else: + multipart = MultipartEncoder(data) + response = requests.post(url=url, data=multipart, headers={'Content-Type': multipart.content_type}, + timeout=timeout) + elif 'application/json' in header.values: + with allure.step("POST请求接口"): + allure.attach(name="请求地址", body=url) + allure.attach(name="请求头", body=str(header)) + allure.attach(name="请求参数", body=str(data_random)) + response = requests.post(url=url, json=data_random, headers=header, timeout=timeout) + else: + with allure.step("POST请求接口"): + allure.attach(name="请求地址", body=url) + allure.attach(name="请求头", body=str(header)) + allure.attach(name="请求参数", body=str(data_random)) + response = requests.post(url=url, data=data_random, headers=header, timeout=timeout) + try: + if response.status_code != 200: + logging.info("请求接口结果: %s" % str(response.text)) + return response.text + else: + logging.info("请求接口结果: %s" % str(response.json())) + self.rel.relevance(rel, bodys=data_random, res=response.json()) + return response.json(), response.elapsed.total_seconds() + except Exception as e: + logging.error(e) + raise + + def get(self, address, rel, header, data, timeout=8, host="host"): + """ + get请求 + :param host: + :param rel: 关联情况 + - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 + path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 + name: 'code' + :param address: + :param header: 请求头 + :param data: 请求参数 + :param timeout: 超时时间 + :return: + """ + iniaddress = replace_random(address, param=data["urlparam"]) + # if isinstance(data, dict): + # if "urlparam" in data.keys(): + # address = replace_random(address, param=data["urlparam"]) + data_random = self.iniDatas(data["param"]) + url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress + logging.info("请求地址:%s" % "" + url) + logging.info("请求头: %s" % str(header)) + logging.info("请求参数: %s" % str(data_random)) + with allure.step("GET请求接口"): + allure.attach(name="请求地址", body=url) + allure.attach(name="请求头", body=str(header)) + allure.attach(name="请求参数", body=str(data_random)) + response = requests.get(url=url, params=data_random, headers=header, timeout=timeout) + if response.status_code == 301: + response = requests.get(url=response.headers["location"]) + try: + logging.info("请求接口结果: %s" % str(response.json())) + self.rel.relevance(rel, bodys=data_random, res=response.json()) + return response.json(), response.elapsed.total_seconds() + except Exception as e: + logging.error(e) + raise + + def put(self, address, rel, header, timeout=8, data=None, files=None, host="host"): + """ + put请求 + :param host: + :param address: + :param rel: 关联情况 + - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 + path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 + name: 'code' + :param header: 请求头 + + :param timeout: 超时时间 + :param data: 请求参数 + :param files: 文件路径 + :return: + """ + iniaddress = replace_random(address, param=data["urlparam"]) + url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress + logging.info("请求地址:%s" % "" + url) + logging.info("请求头: %s" % str(header)) + data_random = self.iniDatas(data["param"]) + logging.info("请求参数: %s" % str(data_random)) + if 'application/json' in header.values: + with allure.step("PUT请求接口"): + allure.attach(name="请求地址", body=url) + allure.attach(name="请求头", body=str(header)) + allure.attach(name="请求参数", body=str(data_random)) + response = requests.put(url=url, json=data_random, headers=header, timeout=timeout, files=files) + else: + with allure.step("PUT请求接口"): + allure.attach(name="请求地址", body=url) + allure.attach(name="请求头", body=str(header)) + allure.attach(name="请求参数", body=str(data_random)) + + response = requests.put(url=url, data=data_random, headers=header, timeout=timeout, files=files) + try: + logging.info("请求接口结果: %s" % str(response.json())) + self.rel.relevance(rel, bodys=data_random, res=response.json()) + return response.json(), response.elapsed.total_seconds() + except Exception as e: + logging.error(e) + raise + + def delete(self, address, rel, header, data, timeout=8, host="host"): + """ + get请求 + :param host: + :param rel: 关联情况 + - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 + path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 + name: 'code' + :param address: + :param header: 请求头 + :param data: 请求参数 + :param timeout: 超时时间 + :return: + """ + iniaddress = replace_random(address, param=data["urlparam"]) + data_random = self.iniDatas(data["param"]) + url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress + logging.info("请求地址:%s" % "" + url) + logging.info("请求头: %s" % str(header)) + logging.info("请求参数: %s" % str(data_random)) + with allure.step("DELETE请求接口"): + allure.attach(name="请求地址", body=url) + allure.attach(name="请求头", body=str(header)) + allure.attach(name="请求参数", body=str(data_random)) + response = requests.delete(url=url, params=data_random, headers=header, timeout=timeout) + + try: + logging.info("请求接口结果: %s" % str(response.json())) + self.rel.relevance(rel, bodys=data_random, res=response.json()) + return response.json(), response.elapsed.total_seconds() + except Exception as e: + logging.error(e) + raise + + def post_session(self, address, header, rel, timeout=8, data=None, files=None, host="host"): + """ + post请求 + :param host: + :param rel: 关联情况 + - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 + path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 + name: 'code' + :param address: 请求地址 + :param header: 请求头 + :param timeout: 超时时间 + :param data: 请求参数 + :param files: 文件路径 + :return: + """ + + iniaddress = replace_random(address, param=data["urlparam"]) + url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress + data_random = self.iniDatas(data["param"]) + logging.info("请求地址:%s" % "" + url) + logging.info("请求头: %s" % str(header)) + logging.info("请求参数: %s" % str(data_random)) + if "multipart/form-data" in header.values: + with allure.step("POST上传文件"): + allure.attach(name="请求地址", body=url) + allure.attach(name="请求头", body=str(header)) + allure.attach(name="请求参数", body=str(data_random)) + if files is not None: + for i in files: + value = files[i] + if isinstance(value, int): + files[i] = str(value) + pass + elif '/' in value: + file_parm = i + files[file_parm] = (os.path.basename(value), open(value, 'rb'), 'application/octet-stream') + else: + pass + multipart = MultipartEncoder( + fields=files, + boundary='-----------------------------' + str(random.randint(int(1e28), int(1e29 - 1))) + ) + header['Content-Type'] = multipart.content_type + response = self.session.post(url=url, data=data, headers=header, timeout=timeout) + else: + multipart = MultipartEncoder(data) + response = self.session.post(url=url, data=multipart, headers={'Content-Type': multipart.content_type}, + timeout=timeout) + elif 'application/json' in header.values: + with allure.step("POST请求接口"): + allure.attach(name="请求地址", body=url) + allure.attach(name="请求头", body=str(header)) + allure.attach(name="请求参数", body=str(data_random)) + response = self.session.post(url=url, json=data_random, headers=header, timeout=timeout) + else: + with allure.step("POST请求接口"): + allure.attach(name="请求地址", body=url) + allure.attach(name="请求头", body=str(header)) + allure.attach(name="请求参数", body=str(data_random)) + response = self.session.post(url=url, data=data_random, headers=header, timeout=timeout) + try: + if response.status_code != 200: + logging.info("请求接口结果: %s" % str(response.text)) + return response.text + else: + logging.info("请求接口结果: %s" % str(response.json())) + self.rel.relevance(rel, bodys=data_random, res=response.json()) + return response.json(), response.elapsed.total_seconds() + except Exception as e: + logging.error(e) + raise + + def get_session(self, address, rel, header, data, timeout=8, host="host"): + """ + get请求 + :param host: + :param rel: 关联情况 + - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 + path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 + name: 'code' + :param address: + :param header: 请求头 + :param data: 请求参数 + :param timeout: 超时时间 + :return: + """ + iniaddress = replace_random(address, param=data["urlparam"]) + # if isinstance(data, dict): + # if "urlparam" in data.keys(): + # address = replace_random(address, param=data["urlparam"]) + data_random = self.iniDatas(data["param"]) + url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress + logging.info("请求地址:%s" % "" + url) + logging.info("请求头: %s" % str(header)) + logging.info("请求参数: %s" % str(data_random)) + with allure.step("GET请求接口"): + allure.attach(name="请求地址", body=url) + allure.attach(name="请求头", body=str(header)) + allure.attach(name="请求参数", body=str(data_random)) + response = requests.get(url=url, params=data_random, headers=header, timeout=timeout) + if response.status_code == 301: + response = self.session.get(url=response.headers["location"]) + try: + logging.info("请求接口结果: %s" % str(response.json())) + self.rel.relevance(rel, bodys=data_random, res=response.json()) + return response.json(), response.elapsed.total_seconds() + except Exception as e: + logging.error(e) + raise + + def put_session(self, address, rel, header, timeout=8, data=None, files=None, host="host"): + """ + put请求 + :param host: + :param address: + :param rel: 关联情况 + - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 + path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 + name: 'code' + :param header: 请求头 + + :param timeout: 超时时间 + :param data: 请求参数 + :param files: 文件路径 + :return: + """ + iniaddress = replace_random(address, param=data["urlparam"]) + url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress + logging.info("请求地址:%s" % "" + url) + logging.info("请求头: %s" % str(header)) + data_random = self.iniDatas(data["param"]) + logging.info("请求参数: %s" % str(data_random)) + if 'application/json' in header.values: + with allure.step("PUT请求接口"): + allure.attach(name="请求地址", body=url) + allure.attach(name="请求头", body=str(header)) + allure.attach(name="请求参数", body=str(data_random)) + response = self.session.put(url=url, json=data_random, headers=header, timeout=timeout, files=files) + else: + with allure.step("PUT请求接口"): + allure.attach(name="请求地址", body=url) + allure.attach(name="请求头", body=str(header)) + allure.attach(name="请求参数", body=str(data_random)) + + response = self.session.put(url=url, data=data_random, headers=header, timeout=timeout, files=files) + try: + logging.info("请求接口结果: %s" % str(response.json())) + self.rel.relevance(rel, bodys=data_random, res=response.json()) + return response.json(), response.elapsed.total_seconds() + except Exception as e: + logging.error(e) + raise + + def delete_session(self, address, rel, header, data, timeout=8, host="host"): + """ + get请求 + :param host: + :param rel: 关联情况 + - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 + path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 + name: 'code' + :param address: + :param header: 请求头 + :param data: 请求参数 + :param timeout: 超时时间 + :return: + """ + iniaddress = replace_random(address, param=data["urlparam"]) + data_random = self.iniDatas(data["param"]) + url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress + logging.info("请求地址:%s" % "" + url) + logging.info("请求头: %s" % str(header)) + logging.info("请求参数: %s" % str(data_random)) + with allure.step("DELETE请求接口"): + allure.attach(name="请求地址", body=url) + allure.attach(name="请求头", body=str(header)) + allure.attach(name="请求参数", body=str(data_random)) + response = self.session.delete(url=url, params=data_random, headers=header, timeout=timeout) + + try: + logging.info("请求接口结果: %s" % str(response.json())) + self.rel.relevance(rel, bodys=data_random, res=response.json()) + return response.json(), response.elapsed.total_seconds() + except Exception as e: + logging.error(e) + raise + + def __call__(self, rel, address, method:str, headers, data, **kwargs): + try: + if method.lower() == "post": + return self.post(address=address, data=data, header=headers, rel=rel, **kwargs) + elif method.lower() == "get": + return self.get(address=address, data=data, header=headers, rel=rel, **kwargs) + elif method.lower() == "delete": + return self.delete(address=address, data=data, header=headers, rel=rel, **kwargs) + elif method.lower() == "put" or method == 'PUT': + return self.put(address=address, data=data, header=headers, rel=rel, **kwargs) + if method.lower() == "post_session": + return self.post(address=address, data=data, header=headers, rel=rel, **kwargs) + elif method.lower() == "get_session": + return self.get(address=address, data=data, header=headers, rel=rel, **kwargs) + elif method.lower() == "delete_session": + return self.delete(address=address, data=data, header=headers, rel=rel, **kwargs) + elif method.lower() == "put_session": + return self.put(address=address, data=data, header=headers, rel=rel, **kwargs) + else: + raise TypeError(f"请求异常,检查yml文件method") + except Exception: + raise + + +apisend = apiSend() +if __name__ == '__main__': + ress = apisend("/$url(home_id)$", "get", data={"param": None, "urlparam": {"home_id": 123}}, headers={ + "Content-Type": "application/json" + }, host="host") + print(ress) diff --git a/test_suite/testcase/saasApp/test_alarm.py b/test_suite/testcase/saasApp/test_alarm.py index 0cf0ef0..7c56e4f 100644 --- a/test_suite/testcase/saasApp/test_alarm.py +++ b/test_suite/testcase/saasApp/test_alarm.py @@ -48,4 +48,7 @@ class Test_alarm(object): def test_alarmDistribute(self, setup_login, casedata): casedata["headers"]["Authorization"] = "JWT " + setup_login["data"]["token"] res, restime = alarmDistribute(casedata) - asserting(hope_res=casedata["assert"], real_res=res, re_time=restime) \ No newline at end of file + asserting(hope_res=casedata["assert"], real_res=res, re_time=restime) + +if __name__ == '__main__': + print(111) \ No newline at end of file -- Gitee From 8f3da146bdb4c74e37049c43ecaa8dc22c25fd21 Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Fri, 19 Nov 2021 10:02:41 +0800 Subject: [PATCH 21/30] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E5=A4=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/basePageSession.py | 447 -------------------------------------- 1 file changed, 447 deletions(-) delete mode 100644 common/basePageSession.py diff --git a/common/basePageSession.py b/common/basePageSession.py deleted file mode 100644 index e1aade2..0000000 --- a/common/basePageSession.py +++ /dev/null @@ -1,447 +0,0 @@ -# coding:utf-8 -""" -@author: 井松 -@contact: 529548204@qq.com -@file: basePageSession.py -@time: 2021/11/19 9:57 -""" -# coding:utf-8 -import json -import logging -import os -import random -import time - -import allure -import requests -import simplejson -from requests_toolbelt import MultipartEncoder - -from scripts.log import Log -from scripts.randomData import replace_random -from config.confManage import host_manage -from scripts.relevance import Relevance - -Log() - - -class apiSend(object): - - def __init__(self): - self.http_type = host_manage(hos="${http_type}$") - self.rel = Relevance() - self.session = requests.Session() - - @staticmethod - def iniDatas(data): - if isinstance(data, dict): - data = json.dumps(data) - if data is None: - return data - dataran = replace_random(data) - return dataran - - def post(self, address, header, rel, timeout=8, data=None, files=None, host="host"): - """ - post请求 - :param host: - :param rel: 关联情况 - - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 - path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 - name: 'code' - :param address: 请求地址 - :param header: 请求头 - :param timeout: 超时时间 - :param data: 请求参数 - :param files: 文件路径 - :return: - """ - - iniaddress = replace_random(address, param=data["urlparam"]) - url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress - data_random = self.iniDatas(data["param"]) - logging.info("请求地址:%s" % "" + url) - logging.info("请求头: %s" % str(header)) - logging.info("请求参数: %s" % str(data_random)) - if "multipart/form-data" in header.values: - with allure.step("POST上传文件"): - allure.attach(name="请求地址", body=url) - allure.attach(name="请求头", body=str(header)) - allure.attach(name="请求参数", body=str(data_random)) - if files is not None: - for i in files: - value = files[i] - if isinstance(value, int): - files[i] = str(value) - pass - elif '/' in value: - file_parm = i - files[file_parm] = (os.path.basename(value), open(value, 'rb'), 'application/octet-stream') - else: - pass - multipart = MultipartEncoder( - fields=files, - boundary='-----------------------------' + str(random.randint(int(1e28), int(1e29 - 1))) - ) - header['Content-Type'] = multipart.content_type - response = requests.post(url=url, data=data, headers=header, timeout=timeout) - else: - multipart = MultipartEncoder(data) - response = requests.post(url=url, data=multipart, headers={'Content-Type': multipart.content_type}, - timeout=timeout) - elif 'application/json' in header.values: - with allure.step("POST请求接口"): - allure.attach(name="请求地址", body=url) - allure.attach(name="请求头", body=str(header)) - allure.attach(name="请求参数", body=str(data_random)) - response = requests.post(url=url, json=data_random, headers=header, timeout=timeout) - else: - with allure.step("POST请求接口"): - allure.attach(name="请求地址", body=url) - allure.attach(name="请求头", body=str(header)) - allure.attach(name="请求参数", body=str(data_random)) - response = requests.post(url=url, data=data_random, headers=header, timeout=timeout) - try: - if response.status_code != 200: - logging.info("请求接口结果: %s" % str(response.text)) - return response.text - else: - logging.info("请求接口结果: %s" % str(response.json())) - self.rel.relevance(rel, bodys=data_random, res=response.json()) - return response.json(), response.elapsed.total_seconds() - except Exception as e: - logging.error(e) - raise - - def get(self, address, rel, header, data, timeout=8, host="host"): - """ - get请求 - :param host: - :param rel: 关联情况 - - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 - path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 - name: 'code' - :param address: - :param header: 请求头 - :param data: 请求参数 - :param timeout: 超时时间 - :return: - """ - iniaddress = replace_random(address, param=data["urlparam"]) - # if isinstance(data, dict): - # if "urlparam" in data.keys(): - # address = replace_random(address, param=data["urlparam"]) - data_random = self.iniDatas(data["param"]) - url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress - logging.info("请求地址:%s" % "" + url) - logging.info("请求头: %s" % str(header)) - logging.info("请求参数: %s" % str(data_random)) - with allure.step("GET请求接口"): - allure.attach(name="请求地址", body=url) - allure.attach(name="请求头", body=str(header)) - allure.attach(name="请求参数", body=str(data_random)) - response = requests.get(url=url, params=data_random, headers=header, timeout=timeout) - if response.status_code == 301: - response = requests.get(url=response.headers["location"]) - try: - logging.info("请求接口结果: %s" % str(response.json())) - self.rel.relevance(rel, bodys=data_random, res=response.json()) - return response.json(), response.elapsed.total_seconds() - except Exception as e: - logging.error(e) - raise - - def put(self, address, rel, header, timeout=8, data=None, files=None, host="host"): - """ - put请求 - :param host: - :param address: - :param rel: 关联情况 - - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 - path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 - name: 'code' - :param header: 请求头 - - :param timeout: 超时时间 - :param data: 请求参数 - :param files: 文件路径 - :return: - """ - iniaddress = replace_random(address, param=data["urlparam"]) - url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress - logging.info("请求地址:%s" % "" + url) - logging.info("请求头: %s" % str(header)) - data_random = self.iniDatas(data["param"]) - logging.info("请求参数: %s" % str(data_random)) - if 'application/json' in header.values: - with allure.step("PUT请求接口"): - allure.attach(name="请求地址", body=url) - allure.attach(name="请求头", body=str(header)) - allure.attach(name="请求参数", body=str(data_random)) - response = requests.put(url=url, json=data_random, headers=header, timeout=timeout, files=files) - else: - with allure.step("PUT请求接口"): - allure.attach(name="请求地址", body=url) - allure.attach(name="请求头", body=str(header)) - allure.attach(name="请求参数", body=str(data_random)) - - response = requests.put(url=url, data=data_random, headers=header, timeout=timeout, files=files) - try: - logging.info("请求接口结果: %s" % str(response.json())) - self.rel.relevance(rel, bodys=data_random, res=response.json()) - return response.json(), response.elapsed.total_seconds() - except Exception as e: - logging.error(e) - raise - - def delete(self, address, rel, header, data, timeout=8, host="host"): - """ - get请求 - :param host: - :param rel: 关联情况 - - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 - path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 - name: 'code' - :param address: - :param header: 请求头 - :param data: 请求参数 - :param timeout: 超时时间 - :return: - """ - iniaddress = replace_random(address, param=data["urlparam"]) - data_random = self.iniDatas(data["param"]) - url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress - logging.info("请求地址:%s" % "" + url) - logging.info("请求头: %s" % str(header)) - logging.info("请求参数: %s" % str(data_random)) - with allure.step("DELETE请求接口"): - allure.attach(name="请求地址", body=url) - allure.attach(name="请求头", body=str(header)) - allure.attach(name="请求参数", body=str(data_random)) - response = requests.delete(url=url, params=data_random, headers=header, timeout=timeout) - - try: - logging.info("请求接口结果: %s" % str(response.json())) - self.rel.relevance(rel, bodys=data_random, res=response.json()) - return response.json(), response.elapsed.total_seconds() - except Exception as e: - logging.error(e) - raise - - def post_session(self, address, header, rel, timeout=8, data=None, files=None, host="host"): - """ - post请求 - :param host: - :param rel: 关联情况 - - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 - path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 - name: 'code' - :param address: 请求地址 - :param header: 请求头 - :param timeout: 超时时间 - :param data: 请求参数 - :param files: 文件路径 - :return: - """ - - iniaddress = replace_random(address, param=data["urlparam"]) - url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress - data_random = self.iniDatas(data["param"]) - logging.info("请求地址:%s" % "" + url) - logging.info("请求头: %s" % str(header)) - logging.info("请求参数: %s" % str(data_random)) - if "multipart/form-data" in header.values: - with allure.step("POST上传文件"): - allure.attach(name="请求地址", body=url) - allure.attach(name="请求头", body=str(header)) - allure.attach(name="请求参数", body=str(data_random)) - if files is not None: - for i in files: - value = files[i] - if isinstance(value, int): - files[i] = str(value) - pass - elif '/' in value: - file_parm = i - files[file_parm] = (os.path.basename(value), open(value, 'rb'), 'application/octet-stream') - else: - pass - multipart = MultipartEncoder( - fields=files, - boundary='-----------------------------' + str(random.randint(int(1e28), int(1e29 - 1))) - ) - header['Content-Type'] = multipart.content_type - response = self.session.post(url=url, data=data, headers=header, timeout=timeout) - else: - multipart = MultipartEncoder(data) - response = self.session.post(url=url, data=multipart, headers={'Content-Type': multipart.content_type}, - timeout=timeout) - elif 'application/json' in header.values: - with allure.step("POST请求接口"): - allure.attach(name="请求地址", body=url) - allure.attach(name="请求头", body=str(header)) - allure.attach(name="请求参数", body=str(data_random)) - response = self.session.post(url=url, json=data_random, headers=header, timeout=timeout) - else: - with allure.step("POST请求接口"): - allure.attach(name="请求地址", body=url) - allure.attach(name="请求头", body=str(header)) - allure.attach(name="请求参数", body=str(data_random)) - response = self.session.post(url=url, data=data_random, headers=header, timeout=timeout) - try: - if response.status_code != 200: - logging.info("请求接口结果: %s" % str(response.text)) - return response.text - else: - logging.info("请求接口结果: %s" % str(response.json())) - self.rel.relevance(rel, bodys=data_random, res=response.json()) - return response.json(), response.elapsed.total_seconds() - except Exception as e: - logging.error(e) - raise - - def get_session(self, address, rel, header, data, timeout=8, host="host"): - """ - get请求 - :param host: - :param rel: 关联情况 - - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 - path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 - name: 'code' - :param address: - :param header: 请求头 - :param data: 请求参数 - :param timeout: 超时时间 - :return: - """ - iniaddress = replace_random(address, param=data["urlparam"]) - # if isinstance(data, dict): - # if "urlparam" in data.keys(): - # address = replace_random(address, param=data["urlparam"]) - data_random = self.iniDatas(data["param"]) - url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress - logging.info("请求地址:%s" % "" + url) - logging.info("请求头: %s" % str(header)) - logging.info("请求参数: %s" % str(data_random)) - with allure.step("GET请求接口"): - allure.attach(name="请求地址", body=url) - allure.attach(name="请求头", body=str(header)) - allure.attach(name="请求参数", body=str(data_random)) - response = requests.get(url=url, params=data_random, headers=header, timeout=timeout) - if response.status_code == 301: - response = self.session.get(url=response.headers["location"]) - try: - logging.info("请求接口结果: %s" % str(response.json())) - self.rel.relevance(rel, bodys=data_random, res=response.json()) - return response.json(), response.elapsed.total_seconds() - except Exception as e: - logging.error(e) - raise - - def put_session(self, address, rel, header, timeout=8, data=None, files=None, host="host"): - """ - put请求 - :param host: - :param address: - :param rel: 关联情况 - - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 - path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 - name: 'code' - :param header: 请求头 - - :param timeout: 超时时间 - :param data: 请求参数 - :param files: 文件路径 - :return: - """ - iniaddress = replace_random(address, param=data["urlparam"]) - url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress - logging.info("请求地址:%s" % "" + url) - logging.info("请求头: %s" % str(header)) - data_random = self.iniDatas(data["param"]) - logging.info("请求参数: %s" % str(data_random)) - if 'application/json' in header.values: - with allure.step("PUT请求接口"): - allure.attach(name="请求地址", body=url) - allure.attach(name="请求头", body=str(header)) - allure.attach(name="请求参数", body=str(data_random)) - response = self.session.put(url=url, json=data_random, headers=header, timeout=timeout, files=files) - else: - with allure.step("PUT请求接口"): - allure.attach(name="请求地址", body=url) - allure.attach(name="请求头", body=str(header)) - allure.attach(name="请求参数", body=str(data_random)) - - response = self.session.put(url=url, data=data_random, headers=header, timeout=timeout, files=files) - try: - logging.info("请求接口结果: %s" % str(response.json())) - self.rel.relevance(rel, bodys=data_random, res=response.json()) - return response.json(), response.elapsed.total_seconds() - except Exception as e: - logging.error(e) - raise - - def delete_session(self, address, rel, header, data, timeout=8, host="host"): - """ - get请求 - :param host: - :param rel: 关联情况 - - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 - path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 - name: 'code' - :param address: - :param header: 请求头 - :param data: 请求参数 - :param timeout: 超时时间 - :return: - """ - iniaddress = replace_random(address, param=data["urlparam"]) - data_random = self.iniDatas(data["param"]) - url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress - logging.info("请求地址:%s" % "" + url) - logging.info("请求头: %s" % str(header)) - logging.info("请求参数: %s" % str(data_random)) - with allure.step("DELETE请求接口"): - allure.attach(name="请求地址", body=url) - allure.attach(name="请求头", body=str(header)) - allure.attach(name="请求参数", body=str(data_random)) - response = self.session.delete(url=url, params=data_random, headers=header, timeout=timeout) - - try: - logging.info("请求接口结果: %s" % str(response.json())) - self.rel.relevance(rel, bodys=data_random, res=response.json()) - return response.json(), response.elapsed.total_seconds() - except Exception as e: - logging.error(e) - raise - - def __call__(self, rel, address, method:str, headers, data, **kwargs): - try: - if method.lower() == "post": - return self.post(address=address, data=data, header=headers, rel=rel, **kwargs) - elif method.lower() == "get": - return self.get(address=address, data=data, header=headers, rel=rel, **kwargs) - elif method.lower() == "delete": - return self.delete(address=address, data=data, header=headers, rel=rel, **kwargs) - elif method.lower() == "put" or method == 'PUT': - return self.put(address=address, data=data, header=headers, rel=rel, **kwargs) - if method.lower() == "post_session": - return self.post(address=address, data=data, header=headers, rel=rel, **kwargs) - elif method.lower() == "get_session": - return self.get(address=address, data=data, header=headers, rel=rel, **kwargs) - elif method.lower() == "delete_session": - return self.delete(address=address, data=data, header=headers, rel=rel, **kwargs) - elif method.lower() == "put_session": - return self.put(address=address, data=data, header=headers, rel=rel, **kwargs) - else: - raise TypeError(f"请求异常,检查yml文件method") - except Exception: - raise - - -apisend = apiSend() -if __name__ == '__main__': - ress = apisend("/$url(home_id)$", "get", data={"param": None, "urlparam": {"home_id": 123}}, headers={ - "Content-Type": "application/json" - }, host="host") - print(ress) -- Gitee From b859088310b74bd920364c825413cca09a2fa43b Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Fri, 19 Nov 2021 17:22:17 +0800 Subject: [PATCH 22/30] =?UTF-8?q?=E6=94=B9BUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/basePage.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/common/basePage.py b/common/basePage.py index 4c9b506..3b71856 100644 --- a/common/basePage.py +++ b/common/basePage.py @@ -55,7 +55,9 @@ class apiSend(object): logging.info("请求地址:%s" % "" + url) logging.info("请求头: %s" % str(header)) logging.info("请求参数: %s" % str(data_random)) - if "multipart/form-data" in header.values: + print(header) + print(header.values()) + if "multipart/form-data" in header.values(): with allure.step("POST上传文件"): allure.attach(name="请求地址", body=url) allure.attach(name="请求头", body=str(header)) @@ -81,12 +83,13 @@ class apiSend(object): multipart = MultipartEncoder(data) response = requests.post(url=url, data=multipart, headers={'Content-Type': multipart.content_type}, timeout=timeout) - elif 'application/json' in header.values: + elif 'application/json' in header.values(): with allure.step("POST请求接口"): allure.attach(name="请求地址", body=url) allure.attach(name="请求头", body=str(header)) allure.attach(name="请求参数", body=str(data_random)) - response = requests.post(url=url, json=data_random, headers=header, timeout=timeout) + + response = requests.post(url=url, json=json.loads(data_random), headers=header, timeout=timeout) else: with allure.step("POST请求接口"): allure.attach(name="请求地址", body=url) @@ -165,12 +168,12 @@ class apiSend(object): logging.info("请求头: %s" % str(header)) data_random = self.iniDatas(data["param"]) logging.info("请求参数: %s" % str(data_random)) - if 'application/json' in header.values: + if 'application/json' in header.values(): with allure.step("PUT请求接口"): allure.attach(name="请求地址", body=url) allure.attach(name="请求头", body=str(header)) allure.attach(name="请求参数", body=str(data_random)) - response = requests.put(url=url, json=data_random, headers=header, timeout=timeout, files=files) + response = requests.put(url=url, json=json.loads(data_random), headers=header, timeout=timeout, files=files) else: with allure.step("PUT请求接口"): allure.attach(name="请求地址", body=url) -- Gitee From 215b289d88e155b911d34ac049d05d6a20dcd98d Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Fri, 19 Nov 2021 17:22:47 +0800 Subject: [PATCH 23/30] =?UTF-8?q?=E6=94=B9BUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/basePage.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/common/basePage.py b/common/basePage.py index 3b71856..1435ee8 100644 --- a/common/basePage.py +++ b/common/basePage.py @@ -55,8 +55,6 @@ class apiSend(object): logging.info("请求地址:%s" % "" + url) logging.info("请求头: %s" % str(header)) logging.info("请求参数: %s" % str(data_random)) - print(header) - print(header.values()) if "multipart/form-data" in header.values(): with allure.step("POST上传文件"): allure.attach(name="请求地址", body=url) -- Gitee From a4edffa0ae6935e8b3886c6e5e1ea2c36b1030c1 Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Mon, 22 Nov 2021 13:19:09 +0800 Subject: [PATCH 24/30] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=AF=BB=E5=8F=96yaml?= =?UTF-8?q?=E7=BC=96=E7=A0=81=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/readYamlFile.py | 20 ++++++++++++++------ scripts/writePage.py | 2 -- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/scripts/readYamlFile.py b/scripts/readYamlFile.py index a37c71d..224dadd 100644 --- a/scripts/readYamlFile.py +++ b/scripts/readYamlFile.py @@ -7,18 +7,26 @@ datapath = dir_manage("${pro_dir}$") + dir_manage("${test_suite}$")+dir_manage(" def ini_yaml(filename, path=datapath): - # encoding="utf-8" 视情况加 - with open(path + "/" + filename, 'r') as f: - file_data = f.read() - data = yaml.safe_load(file_data) + try: + with open(path + "/" + filename, 'r',encoding="utf-8") as f: + file_data = f.read() + data = yaml.safe_load(file_data) - return data + return data + except UnicodeDecodeError: + with open(path + "/" + filename, 'r') as f: + file_data = f.read() + data = yaml.safe_load(file_data) + + return data if __name__ == '__main__': # get_yaml_data(r"F:\api2.0\config\runConfig.yml") - runConfig_dict = ini_yaml("loginData.yaml",path=r"D:\apitest\test_suite") + runConfig_dict = ini_yaml(r'tmUrlData.yml',path= r"D:\apitest\test_suite\datas\troubleManage") # case_level = runConfig_dict[0]["address"].format(**{"home_id": "123"}) print(runConfig_dict) + + # print(case_level) # print(type(case_level)) diff --git a/scripts/writePage.py b/scripts/writePage.py index 17b9f13..1a06765 100644 --- a/scripts/writePage.py +++ b/scripts/writePage.py @@ -21,8 +21,6 @@ def getFilePathList(path, filetype): def write_case(_file,pagename): path = dir_manage(dir_manage('${pro_dir}$') + dir_manage('${test_suite}$') + dir_manage('${page_dir}$')) with open(path + r"{}".format(pagename), 'w+', encoding='utf-8') as f: - # with open(path + r"saasApp_pages_1.py", 'w+', encoding='utf-8') as f: - # f.write("""# coding:utf-8\nfrom test_suite.page import *\n\nurlData = ini_yaml("urlData.yml")\n\n""") f.write("""# coding:utf-8\nfrom test_suite.page import *\n\nurlData = ini_yaml("{}")\n\n""".format(_file)) yml_data = ini_yaml(_file) -- Gitee From 59c3e123625fb9362ae1d347303c4b339d2ea7fd Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Tue, 23 Nov 2021 17:24:43 +0800 Subject: [PATCH 25/30] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/basePage.py | 4 --- common/checkResult.py | 43 +++++++++++++++---------- scripts/dataBase.py | 27 ++++++++++------ test_suite/testcase/saasApp/conftest.py | 6 ++-- 4 files changed, 46 insertions(+), 34 deletions(-) diff --git a/common/basePage.py b/common/basePage.py index 1435ee8..e0d6ee4 100644 --- a/common/basePage.py +++ b/common/basePage.py @@ -3,13 +3,9 @@ import json import logging import os import random -import time - import allure import requests -import simplejson from requests_toolbelt import MultipartEncoder - from scripts.log import Log from scripts.randomData import replace_random from config.confManage import host_manage diff --git a/common/checkResult.py b/common/checkResult.py index c74e84b..36d989f 100644 --- a/common/checkResult.py +++ b/common/checkResult.py @@ -1,7 +1,6 @@ # coding:utf-8 import logging -import time import allure import jsonpath @@ -69,7 +68,7 @@ def assert_json(data, key=None, value=None, asserttype="KEY"): logging.error("断言类型错误, 请选择断言类型.") -def assert_text(hope_res, real_res,third_data=None,third_datas=None): +def assert_text(hope_res, real_res, third_data=None, third_datas=None): """ 文本判断 :param third_datas: @@ -85,7 +84,7 @@ def assert_text(hope_res, real_res,third_data=None,third_datas=None): if h_res["asserttype"] == "==": try: if not third_datas: - h_res["value"] = replace_random(str(h_res["value"]),res=third_data) + h_res["value"] = replace_random(str(h_res["value"]), res=third_data) with allure.step("json断言判断相等"): allure.attach(name="期望结果", body=str(h_res)) allure.attach(name='实际实际结果', body=str(r_res)) @@ -93,7 +92,8 @@ def assert_text(hope_res, real_res,third_data=None,third_datas=None): logging.info("json断言通过, 期望结果'{0}', 实际结果'{1}'".format(h_res, r_res)) elif third_datas: if h_res["relevanceCheck"]: - h_res["value"] = replace_random(str(h_res["value"]), res=third_datas[h_res["relevanceCheck"]]) + h_res["value"] = replace_random(str(h_res["value"]), + res=third_datas[h_res["relevanceCheck"]]) with allure.step("json断言判断相等"): allure.attach(name="期望结果", body=str(h_res)) allure.attach(name='实际实际结果', body=str(r_res)) @@ -145,38 +145,47 @@ def assert_time(hope_res, real_res): def assert_sql(hope_res, real_res): + """ + 一定要把断言的sql写精确写 只能通过sql查询出来的数据第一条进行断言 + :param hope_res: + :param real_res: + :return: + """ if isinstance(hope_res["sqlassert"], list): db = MYSQL() for h_res in hope_res["sqlassert"]: h_sql = h_res["sql"] + r_sql = replace_random(h_sql, real_res) + datas = db.run_sql(r_sql) for i in h_res["datas"]: if jsonpath.jsonpath(real_res, i["path"]): r_res = jsonpath.jsonpath(real_res, i["path"])[0] - i["name"] = replace_random(i["name"],real_res) - t1 = time.time() - r_sql = replace_random(h_sql,real_res) - datas = db.run_sql(r_sql) - + i["name"] = replace_random(i["name"], real_res) try: with allure.step("数据库校验"): allure.attach(name="期望结果", body=str(datas)) allure.attach(name='实际实际结果', body=str(r_res)) allure.attach(name='sql命令', body=str(r_sql)) - d = datas[0][i["name"]] - if isinstance(d,bytes): + if datas: + d = datas[0][i["name"]] + else: + d = None + if isinstance(d, bytes): d = d.decode("utf-8") assert str(r_res) == str(d) logging.info( - "sql断言通过, 期望结果'{0}', 实际结果'{1}',sql耗时{2:.5f}秒".format(datas, r_res, time.time() - t1)) + "sql断言通过, 期望结果'{0}', 实际结果'{1}'".format(datas, r_res)) except AssertionError: - logging.error("sql断言未通过, 期望结果'{0}', 实际结果'{1}',sql耗时{2:.5f}秒".format(datas, r_res, time.time() - t1)) + db.close() + logging.error("sql断言未通过, 期望结果'{0}', 实际结果'{1}'".format(datas, r_res)) raise else: + db.close() raise ValueError("获取json值失败,请检查jsonpath") db.close() -def asserting(hope_res, real_res, re_time=None,third_data=None,third_datas=None): +def asserting(hope_res, real_res, re_time=None, third_data=None, third_datas=None): """ :param hope_res: 期望结果 @@ -187,7 +196,7 @@ def asserting(hope_res, real_res, re_time=None,third_data=None,third_datas=None) :return: """ if hope_res["jsonpath"]: - assert_text(hope_res, real_res,third_data,third_datas) + assert_text(hope_res, real_res, third_data, third_datas) if hope_res["sqlassert"]: assert_sql(hope_res, real_res) if hope_res["time"]: @@ -233,5 +242,5 @@ if __name__ == '__main__': # "time": None # } hp = ini_yaml("loginData.yml") - print(hp) - asserting(hp, j,23) + # print(hp) + asserting(hp["login"][0]["assert"], j, 23) diff --git a/scripts/dataBase.py b/scripts/dataBase.py index 3f6dbab..8787fe1 100644 --- a/scripts/dataBase.py +++ b/scripts/dataBase.py @@ -7,6 +7,7 @@ import pymysql from scripts.log import Log import logging from config.confManage import db_manage +import time Log() @@ -21,13 +22,13 @@ class MYSQL(object): self.charset = db_manage("${charset}$") self.port = int(db_manage("${port}$")) try: - logging.debug("正在连接数据库..") + logging.debug("正在连接数据库.") self.conn = pymysql.connect(host=self.host, user=self.user, password=self.password, database=self.database, port=self.port, charset=self.charset) self.cursor = self.conn.cursor(cursor=pymysql.cursors.DictCursor) - logging.debug("数据库连接成功..") + logging.debug("数据库连接成功.") except Exception as e: - logging.error("连接数据库失败..{}".format(e)) + logging.error("连接数据库失败,{}".format(e)) raise e def run_sql(self, sql): @@ -39,20 +40,28 @@ class MYSQL(object): try: logging.debug("准备执行SQL语句..") logging.debug("sql语句:{}".format(sql)) + t1 = time.time() self.cursor.execute(sql) rs = self.cursor.fetchall() + logging.info("执行成功,,sql耗时{0:.5f}秒".format(time.time() - t1)) return rs except Exception as e: - logging.error("执行SQL失败..{}".format(e)) - raise + self.close() + logging.error("执行SQL失败.{}".format(e)) + raise SystemError("执行SQL失败.{}".format(e)) def commit(self): self.conn.commit() def close(self): - self.conn.close() - self.cursor.close() - logging.debug("关闭数据库") + try: + self.conn.close() + self.cursor.close() + logging.debug("断开数据库连接.") + except Exception: + logging.debug("断开数据库连接错误,请手动断开.") + raise + if __name__ == "__main__": DB = MYSQL() @@ -86,4 +95,4 @@ select replace((select count(*) ) ad), 0, null) as "haha" """ ) - print(datas[0]["haha"].decode("utf-8")) \ No newline at end of file + print(datas[0]["haha"].decode("utf-8")) diff --git a/test_suite/testcase/saasApp/conftest.py b/test_suite/testcase/saasApp/conftest.py index 2e1538e..de2f889 100644 --- a/test_suite/testcase/saasApp/conftest.py +++ b/test_suite/testcase/saasApp/conftest.py @@ -1,9 +1,6 @@ # coding:utf-8 -import pytest - from test_suite.page.saasApp_pages import * -from scripts.relevance import Relevance -import cacheout + paramData = ini_yaml("loginData.yml")["login"][0] thirdParamData = ini_yaml("loginData.yml")["login"][0] @@ -22,6 +19,7 @@ def setup_login(): thirdURL = ini_yaml("thirdUrl.yml") + @pytest.fixture(scope="module") def third_login(): data = thirdURL["weblogin"] -- Gitee From 24f23b7804ccc552cc819e0357ee964dc4fee1a1 Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Wed, 22 Dec 2021 16:53:11 +0800 Subject: [PATCH 26/30] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/basePage.py | 9 +- common/checkResult.py | 55 ----------- scripts/randomData.py | 96 +++++++++---------- scripts/readYamlFile.py | 7 +- scripts/writePage.py | 25 ++++- setupMain.py | 2 +- test_suite/page/__init__.py | 23 ----- test_suite/page/saasApp/__init__.py | 30 ++++++ .../page/{ => saasApp}/saasApp_pages.py | 2 +- .../page/{ => saasApp}/saasApp_pages_1.py | 0 test_suite/page/{ => saasApp}/temple.py | 0 test_suite/page/{ => saasApp}/third_pages.py | 0 .../page/{ => saasApp}/third_pages_1.py | 2 +- test_suite/page/saasWeb/__init__.py | 31 ++++++ test_suite/page/saasWeb/saasApp_pages_1.py | 45 +++++++++ test_suite/page/saasWeb/saasWeb_pages.py | 45 +++++++++ test_suite/page/saasWeb/saasWeb_pages_1.py | 45 +++++++++ test_suite/testcase/saasApp/conftest.py | 6 +- test_suite/testcase/saasApp/test_alarm.py | 3 +- test_suite/testcase/saasApp/test_function.py | 2 +- test_suite/testcase/saasApp/test_homePage.py | 2 +- test_suite/testcase/saasApp/test_login.py | 2 +- test_suite/testcase/saasApp/test_power.py | 4 +- test_suite/testcase/saasApp/test_region.py | 2 +- test_suite/testcase/saasWeb/conftest.py | 22 +++++ test_suite/testcase/saasWeb/test_firmware.py | 53 ++++++++++ test_suite/testcase/saasWeb/test_login.py | 27 ++++++ 27 files changed, 385 insertions(+), 155 deletions(-) create mode 100644 test_suite/page/saasApp/__init__.py rename test_suite/page/{ => saasApp}/saasApp_pages.py (99%) rename test_suite/page/{ => saasApp}/saasApp_pages_1.py (100%) rename test_suite/page/{ => saasApp}/temple.py (100%) rename test_suite/page/{ => saasApp}/third_pages.py (100%) rename test_suite/page/{ => saasApp}/third_pages_1.py (97%) create mode 100644 test_suite/page/saasWeb/__init__.py create mode 100644 test_suite/page/saasWeb/saasApp_pages_1.py create mode 100644 test_suite/page/saasWeb/saasWeb_pages.py create mode 100644 test_suite/page/saasWeb/saasWeb_pages_1.py create mode 100644 test_suite/testcase/saasWeb/conftest.py create mode 100644 test_suite/testcase/saasWeb/test_firmware.py create mode 100644 test_suite/testcase/saasWeb/test_login.py diff --git a/common/basePage.py b/common/basePage.py index e0d6ee4..df859a0 100644 --- a/common/basePage.py +++ b/common/basePage.py @@ -82,8 +82,9 @@ class apiSend(object): allure.attach(name="请求地址", body=url) allure.attach(name="请求头", body=str(header)) allure.attach(name="请求参数", body=str(data_random)) - - response = requests.post(url=url, json=json.loads(data_random), headers=header, timeout=timeout) + if data_random: + data_random = json.loads(data_random) + response = requests.post(url=url, json=data_random, headers=header, timeout=timeout) else: with allure.step("POST请求接口"): allure.attach(name="请求地址", body=url) @@ -167,7 +168,9 @@ class apiSend(object): allure.attach(name="请求地址", body=url) allure.attach(name="请求头", body=str(header)) allure.attach(name="请求参数", body=str(data_random)) - response = requests.put(url=url, json=json.loads(data_random), headers=header, timeout=timeout, files=files) + if data_random: + data_random = json.loads(data_random) + response = requests.put(url=url, json=data_random, headers=header, timeout=timeout, files=files) else: with allure.step("PUT请求接口"): allure.attach(name="请求地址", body=url) diff --git a/common/checkResult.py b/common/checkResult.py index 36d989f..69ec888 100644 --- a/common/checkResult.py +++ b/common/checkResult.py @@ -13,61 +13,6 @@ from scripts.readYamlFile import ini_yaml Log() -def assert_json(data, key=None, value=None, asserttype="KEY"): - """ - json返回值断言 - :param data: 返回数据 - :param key: - :param value: - :param asserttype: "KEY" OR "VALUE" OR "KEY-VALUE" - :return: - """ - if asserttype == "KEY": - try: - assert data.get(key) is not None - with allure.step("判断JSON返回值KEY是否存在"): - allure.attach(name="期望存在KEY", body=str(key)) - allure.attach(name='实际data', body=str(data)) - logging.info( - "断言通过, 存在key'{}', value为'{}'.".format(key, data.get(key))) - except Exception: - logging.error("断言未通过, 不存在key:'{}'.".format(key)) - raise - elif asserttype == "VALUE": - try: - rs_values = list(data.values()) - with allure.step("判断JSON返回值VALUE是否存在"): - allure.attach(name="期望VALUE", body=str(value)) - allure.attach(name='实际VALUE', body=str(rs_values)) - assert value in rs_values - - logging.info("断言通过, 存在value为'{}', key为'{}'.".format(value, list(data.keys())[ - list(data.values()).index(value)])) - except AssertionError: - logging.error( - "断言未通过, 不存在类型为'{}'的value:''{}''.".format(type(value), value)) - raise - elif asserttype == "KEY-VALUE": - try: - - with allure.step("判断JSON返回值是否存在KEY-VALUE"): - allure.attach(name="期望KEY-VALUE", body=str({key: value})) - allure.attach(name='实际KEY-VALUE', body=str(data)) - if key in data: - assert value == data[key] - else: - logging.error("断言未通过, 不存在key:'{}'.".format(key)) - raise AssertionError - logging.info("断言通过, 存在键值对key为'{}',value为'{}'.".format(list(data.keys())[ - list(data.values()).index(value)], value)) - except AssertionError: - logging.error( - "断言未通过, 不存在key为'{}',类型为'{}'的value:''{}''.".format(key, type(value), value)) - raise - else: - logging.error("断言类型错误, 请选择断言类型.") - - def assert_text(hope_res, real_res, third_data=None, third_datas=None): """ 文本判断 diff --git a/scripts/randomData.py b/scripts/randomData.py index 58ecbdb..402c119 100644 --- a/scripts/randomData.py +++ b/scripts/randomData.py @@ -186,6 +186,12 @@ def get_time(time_type, layout, unit="0,0,0,0,0"): def replace_random(value, res=None, param=None): """ 调用定义方法替换字符串 + int_num = "$RandomPosInt(1,333)$" + str_num = '$RandomString($RandomPosInt(2,23)$)$$RandomPosInt(1,333)$' + float_num = '$RandomFloat($RandomPosInt(2,13)$,$RandomPosInt(2,13)$,$RandomPosInt(2,13)$)$' + time_num = '$GetTime(time_type=else,layout=%Y-%m-%d %H:%M:%S,unit=0,0,0,0,0)$' + choice_num = '$Choice($RandomPosInt(2,13)$)$' + json_value = '$json($.data[-$RandomPosInt(1,5)$:])$' urls = "$url(home_id)$" ca = "$caches(haha)$" print(replace_random(urls,param={"home_id":"$caches(haha)$"})) @@ -290,66 +296,52 @@ if __name__ == '__main__': time_num = '$GetTime(time_type=else,layout=%Y-%m-%d %H:%M:%S,unit=0,0,0,0,0)$' choice_num = '$Choice($RandomPosInt(2,13)$)$' '$json($.data[-$RandomPosInt(1,5)$:])$' -# jsons = """ -# select count(*) '$json($.data[-$RandomPosInt(1,5)$:])$' -# $RandomString($RandomPosInt(2,23)$)$ -# ) as ad -# """ -# js = """ -# select count(*) '$GetTime(time_type=past,layout=%Y-%m-%d,unit=0,0,0,1,0)$' from ( -# select -# dr.qr_code, -# alarm.device_id, -# alarm.grade, -# alarm.create_time, -# alarm.handler_status, -# alarm.work_order -# -# from alarm -# inner join ( -# select device.id, -# device.qr_code, -# device.region_id, -# region.enterprise_id, -# device.status, -# device.category_id -# from device -# inner join region -# on device.region_id = region.id -# and region.enterprise_id = 88 -# ) as dr -# on dr.qr_code = alarm.device_id and alarm.create_time >= '$GetTime(time_type=past,layout=%Y-%m-%d 00:00:00,unit=0,0,0,1,0)$' and alarm.create_time < '$GetTime(time_type=past,layout=%Y-%m-%d 00:00:00,unit=0,0,0,0,0)$' -# ) as ad -# """ + jsons = """ +select count(*) '$json($.data[-$RandomPosInt(1,5)$:])$' +$RandomString($RandomPosInt(2,23)$)$ + ) as ad + """ + js = """ + select count(*) '$json($.data[-$RandomPosInt(1,5)$:])$' from ( +select + dr.qr_code, + alarm.device_id, + alarm.grade, + alarm.create_time, + alarm.handler_status, + alarm.work_order + +from alarm + inner join ( + select device.id, + device.qr_code, + device.region_id, + region.enterprise_id, + device.status, + device.category_id + from device + inner join region + on device.region_id = region.id + and region.enterprise_id = 88 +) as dr + on dr.qr_code = alarm.device_id and alarm.create_time >= '$GetTime(time_type=past,layout=%Y-%m-%d 00:00:00,unit=0,0,0,1,0)$' and alarm.create_time < '$GetTime(time_type=past,layout=%Y-%m-%d 00:00:00,unit=0,0,0,0,0)$' + ) as ad + """ # a = json.dumps(ini_yaml("homePageData.yml")["companyAlarm"]) # print(type(ini_yaml("家庭详情.yml")[0]["data"])) # print(replace_random(int_num)) # print(replace_random(str_num)) # print(replace_random(float_num)) # print(replace_random(time_num)) - # res1 = {'code': 0, 'msg': 'ok', - # 'data': [{'time': '2021-08-18', 'number': None}, {'time': '2021-08-19', 'number': None}, - # {'time': '2021-08-20', 'number': 1}, {'time': '2021-08-21', 'number': None}, - # {'time': '2021-08-22', 'number': None}, {'time': '2021-08-23', 'number': 9}, - # {'time': '2021-08-24', 'number': 7}, {'time': '2021-08-25', 'number': 11}, - # {'time': '2021-08-26', 'number': 2}, {'time': '2021-08-27', 'number': 1}, - # {'time': '2021-08-28', 'number': None}, {'time': '2021-08-29', 'number': None}, - # {'time': '2021-08-30', 'number': None}, {'time': '2021-08-31', 'number': 1}, - # {'time': '2021-09-01', 'number': 2}, {'time': '2021-09-02', 'number': 4}, - # {'time': '2021-09-03', 'number': 3}, {'time': '2021-09-04', 'number': 3}, - # {'time': '2021-09-05', 'number': 1}, {'time': '2021-09-06', 'number': 7}, - # {'time': '2021-09-07', 'number': 13}, {'time': '2021-09-08', 'number': 23}, - # {'time': '2021-09-09', 'number': 21}, {'time': '2021-09-10', 'number': 11}, - # {'time': '2021-09-11', 'number': 7}, {'time': '2021-09-12', 'number': 4}, - # {'time': '2021-09-13', 'number': 22}, {'time': '2021-09-14', 'number': 19}, - # {'time': '2021-09-15', 'number': 38}, {'time': '2021-09-16', 'number': 39}]} - # print(replace_random(js, res=res1)) + res1 = {'code': 0, 'msg': 'ok', + 'data': [{'time': '2021-08-18', 'number': None}, {'time': '2021-08-19', 'number': None}, + {'time': '2021-08-20', 'number': 1}, {'time': '2021-08-21', 'number': None}, + {'time': '2021-08-22', 'number': None}, {'time': '2021-08-23', 'number': 9}]} + print(replace_random(js, res=res1)) t = "$GetTime(time_type=past,layout=13timestampDAY,unit=0,0,0,1,4)$" # print(replace_random(choice_num)) - urls = "$url(home_id)$" - ca = "$caches(haha)$" - print(replace_random(urls,param={"home_id":"$caches(haha)$"})) - print(replace_random(ca)) + # print(replace_random(urls,param={"home_id":"$caches(haha)$"})) + # print(replace_random(ca)) # pattern = re.compile(r'\$json\(' + '$.data.alarm[1].number'.replace('$',"\$").replace('[','\[') + r'\)\$') # # key = str(sql_json(i)) # key = "123" diff --git a/scripts/readYamlFile.py b/scripts/readYamlFile.py index 224dadd..fa5cfd6 100644 --- a/scripts/readYamlFile.py +++ b/scripts/readYamlFile.py @@ -22,10 +22,11 @@ def ini_yaml(filename, path=datapath): if __name__ == '__main__': + print(datapath) # get_yaml_data(r"F:\api2.0\config\runConfig.yml") - runConfig_dict = ini_yaml(r'tmUrlData.yml',path= r"D:\apitest\test_suite\datas\troubleManage") - # case_level = runConfig_dict[0]["address"].format(**{"home_id": "123"}) - print(runConfig_dict) + # runConfig_dict = ini_yaml(r'tmUrlData.yml',path= r"D:\apitest\test_suite\datas\troubleManage") + # # case_level = runConfig_dict[0]["address"].format(**{"home_id": "123"}) + # print(runConfig_dict) # print(case_level) diff --git a/scripts/writePage.py b/scripts/writePage.py index 1a06765..d063974 100644 --- a/scripts/writePage.py +++ b/scripts/writePage.py @@ -1,5 +1,6 @@ # coding:utf-8 import os +import time from config.confManage import dir_manage from scripts.log import Log @@ -18,10 +19,12 @@ def getFilePathList(path, filetype): return pathList -def write_case(_file,pagename): - path = dir_manage(dir_manage('${pro_dir}$') + dir_manage('${test_suite}$') + dir_manage('${page_dir}$')) - with open(path + r"{}".format(pagename), 'w+', encoding='utf-8') as f: - f.write("""# coding:utf-8\nfrom test_suite.page import *\n\nurlData = ini_yaml("{}")\n\n""".format(_file)) +def write_pages(_file,pagename): + t1 = time.time() + path = dir_manage('${pro_dir}$') + dir_manage('${test_suite}$') + dir_manage('${page_dir}$') + dir_manage('${test_name}$') + print(path) + with open(path +"/" +r"{}".format(pagename), 'w+', encoding='utf-8') as f: + f.write("""# coding:utf-8\nfrom test_suite.page.{} import *\n\nurlData = ini_yaml("{}")\n\n""".format(dir_manage('${test_name}$'),_file)) yml_data = ini_yaml(_file) for item in yml_data.items(): @@ -48,11 +51,23 @@ def {testtitle}(casedata):""".format(testtitle=item[0])) """ return res, restime\n\n""" ) + t2 = time.time() - t1 + print(t2) + + +# def write_case(f): +# + if __name__ == '__main__': # ym_path = r'thirdUrl.yml' # pagenames = "third_pages_1.py" + ym_path = r'urlData.yml' pagenames = "saasApp_pages_1.py" - write_case(ym_path,pagenames) + # write_case(ym_path,pagenames) + + # ym_path = r'urlData.yml' + # pagenames = "saasWeb_pages_1.py" + write_pages(ym_path,pagenames) diff --git a/setupMain.py b/setupMain.py index dc6419e..5ba800a 100644 --- a/setupMain.py +++ b/setupMain.py @@ -30,7 +30,7 @@ def run(): # 执行命令行 args = ['-s', '-q', test_case_path, '--alluredir', temp_path] # args = ['-s', '-q', test_case_path,] - pytest.main(args) + # pytest.main(args) cmd = 'allure generate %s -o %s -c' % (temp_path, html_path) os.system(cmd) diff --git a/test_suite/page/__init__.py b/test_suite/page/__init__.py index 72afa49..f512dea 100644 --- a/test_suite/page/__init__.py +++ b/test_suite/page/__init__.py @@ -1,24 +1 @@ # coding:utf-8 -import logging - -import allure -import pytest - -from common.checkResult import asserting,assert_json -from scripts.log import Log -from scripts.readYamlFile import ini_yaml -from common.basePage import apisend -from test_suite.page import saasApp_pages - -Log() -__all__ = [ - 'pytest', - 'asserting', - 'Log', - 'ini_yaml', - 'logging', - 'allure', - 'apisend', - 'saasApp_pages', - 'assert_json' -] \ No newline at end of file diff --git a/test_suite/page/saasApp/__init__.py b/test_suite/page/saasApp/__init__.py new file mode 100644 index 0000000..970b2ad --- /dev/null +++ b/test_suite/page/saasApp/__init__.py @@ -0,0 +1,30 @@ +# coding:utf-8 +""" +@author: 井松 +@contact: 529548204@qq.com +@file: __init__.py.py +@time: 2021/12/3 13:51 +""" + +import logging + +import allure +import pytest + +from common.checkResult import asserting +from scripts.log import Log +from scripts.readYamlFile import ini_yaml +from common.basePage import apisend +from test_suite.page.saasApp import saasApp_pages + +Log() +__all__ = [ + 'pytest', + 'asserting', + 'Log', + 'ini_yaml', + 'logging', + 'allure', + 'apisend', + 'saasApp_pages', +] \ No newline at end of file diff --git a/test_suite/page/saasApp_pages.py b/test_suite/page/saasApp/saasApp_pages.py similarity index 99% rename from test_suite/page/saasApp_pages.py rename to test_suite/page/saasApp/saasApp_pages.py index 373dade..8f0539d 100644 --- a/test_suite/page/saasApp_pages.py +++ b/test_suite/page/saasApp/saasApp_pages.py @@ -1,5 +1,5 @@ # coding:utf-8 -from test_suite.page import * +from test_suite.page.saasApp import * urlData = ini_yaml("urlData.yml") diff --git a/test_suite/page/saasApp_pages_1.py b/test_suite/page/saasApp/saasApp_pages_1.py similarity index 100% rename from test_suite/page/saasApp_pages_1.py rename to test_suite/page/saasApp/saasApp_pages_1.py diff --git a/test_suite/page/temple.py b/test_suite/page/saasApp/temple.py similarity index 100% rename from test_suite/page/temple.py rename to test_suite/page/saasApp/temple.py diff --git a/test_suite/page/third_pages.py b/test_suite/page/saasApp/third_pages.py similarity index 100% rename from test_suite/page/third_pages.py rename to test_suite/page/saasApp/third_pages.py diff --git a/test_suite/page/third_pages_1.py b/test_suite/page/saasApp/third_pages_1.py similarity index 97% rename from test_suite/page/third_pages_1.py rename to test_suite/page/saasApp/third_pages_1.py index 8d44569..e45e0cf 100644 --- a/test_suite/page/third_pages_1.py +++ b/test_suite/page/saasApp/third_pages_1.py @@ -1,5 +1,5 @@ # coding:utf-8 -from test_suite.page import * +from test_suite.page.saasApp import * urlData = ini_yaml("thirdUrl.yml") diff --git a/test_suite/page/saasWeb/__init__.py b/test_suite/page/saasWeb/__init__.py new file mode 100644 index 0000000..d19dd19 --- /dev/null +++ b/test_suite/page/saasWeb/__init__.py @@ -0,0 +1,31 @@ +# coding:utf-8 +""" +@author: 井松 +@contact: 529548204@qq.com +@file: __init__.py.py +@time: 2021/12/3 13:52 +""" + + +import logging + +import allure +import pytest + +from common.checkResult import asserting +from scripts.log import Log +from scripts.readYamlFile import ini_yaml +from common.basePage import apisend +from test_suite.page.saasWeb import saasWeb_pages + +Log() +__all__ = [ + 'pytest', + 'asserting', + 'Log', + 'ini_yaml', + 'logging', + 'allure', + 'apisend', + 'saasWeb_pages', +] \ No newline at end of file diff --git a/test_suite/page/saasWeb/saasApp_pages_1.py b/test_suite/page/saasWeb/saasApp_pages_1.py new file mode 100644 index 0000000..153f08b --- /dev/null +++ b/test_suite/page/saasWeb/saasApp_pages_1.py @@ -0,0 +1,45 @@ +# coding:utf-8 +from test_suite.page.saasWeb import * + +urlData = ini_yaml("urlData.yml") + + +def login(casedata): + data = urlData["login"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"],rel=data["relevance"]) + return res, restime + + +def firmwareList(casedata): + data = urlData["firmwareList"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"],rel=data["relevance"]) + return res, restime + + +def firmwareAdd(casedata): + data = urlData["firmwareAdd"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"],rel=data["relevance"]) + return res, restime + + +def firmwareDel(casedata): + data = urlData["firmwareDel"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"],rel=data["relevance"]) + return res, restime + + +def firmwareDetail(casedata): + data = urlData["firmwareDetail"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"],rel=data["relevance"]) + return res, restime + diff --git a/test_suite/page/saasWeb/saasWeb_pages.py b/test_suite/page/saasWeb/saasWeb_pages.py new file mode 100644 index 0000000..153f08b --- /dev/null +++ b/test_suite/page/saasWeb/saasWeb_pages.py @@ -0,0 +1,45 @@ +# coding:utf-8 +from test_suite.page.saasWeb import * + +urlData = ini_yaml("urlData.yml") + + +def login(casedata): + data = urlData["login"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"],rel=data["relevance"]) + return res, restime + + +def firmwareList(casedata): + data = urlData["firmwareList"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"],rel=data["relevance"]) + return res, restime + + +def firmwareAdd(casedata): + data = urlData["firmwareAdd"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"],rel=data["relevance"]) + return res, restime + + +def firmwareDel(casedata): + data = urlData["firmwareDel"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"],rel=data["relevance"]) + return res, restime + + +def firmwareDetail(casedata): + data = urlData["firmwareDetail"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"],rel=data["relevance"]) + return res, restime + diff --git a/test_suite/page/saasWeb/saasWeb_pages_1.py b/test_suite/page/saasWeb/saasWeb_pages_1.py new file mode 100644 index 0000000..153f08b --- /dev/null +++ b/test_suite/page/saasWeb/saasWeb_pages_1.py @@ -0,0 +1,45 @@ +# coding:utf-8 +from test_suite.page.saasWeb import * + +urlData = ini_yaml("urlData.yml") + + +def login(casedata): + data = urlData["login"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"],rel=data["relevance"]) + return res, restime + + +def firmwareList(casedata): + data = urlData["firmwareList"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"],rel=data["relevance"]) + return res, restime + + +def firmwareAdd(casedata): + data = urlData["firmwareAdd"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"],rel=data["relevance"]) + return res, restime + + +def firmwareDel(casedata): + data = urlData["firmwareDel"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"],rel=data["relevance"]) + return res, restime + + +def firmwareDetail(casedata): + data = urlData["firmwareDetail"] + logging.info("{}".format(casedata["info"])) + res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], + data=casedata["data"],rel=data["relevance"]) + return res, restime + diff --git a/test_suite/testcase/saasApp/conftest.py b/test_suite/testcase/saasApp/conftest.py index de2f889..a62b901 100644 --- a/test_suite/testcase/saasApp/conftest.py +++ b/test_suite/testcase/saasApp/conftest.py @@ -1,5 +1,5 @@ # coding:utf-8 -from test_suite.page.saasApp_pages import * +from test_suite.page.saasApp.saasApp_pages import * paramData = ini_yaml("loginData.yml")["login"][0] thirdParamData = ini_yaml("loginData.yml")["login"][0] @@ -11,7 +11,7 @@ def setup_login(): data = urlData["login"] logging.info("{}".format(paramData["info"])) res, restime = apisend(address=data["address"], method=data["method"], headers=paramData["headers"], - data=paramData["data"]) + data=paramData["data"],rel=data["relevance"]) logging.info("前置请求结束") return res @@ -25,5 +25,5 @@ def third_login(): data = thirdURL["weblogin"] logging.info("{}".format(paramData["info"])) res, restime = apisend(address=data["address"], method=data["method"], headers=paramData["headers"], - data=paramData["data"]) + data=paramData["data"],rel=data["relevance"]) return res diff --git a/test_suite/testcase/saasApp/test_alarm.py b/test_suite/testcase/saasApp/test_alarm.py index 7c56e4f..77e645c 100644 --- a/test_suite/testcase/saasApp/test_alarm.py +++ b/test_suite/testcase/saasApp/test_alarm.py @@ -6,9 +6,8 @@ @time: 2021/9/14 16:40 """ from test_suite.page.saasApp_pages import * -from test_suite.page.third_pages import * paramData = ini_yaml("alarmData.yml") -thirdData = ini_yaml("thirdData.yml") + class Test_alarm(object): # def setup(self): diff --git a/test_suite/testcase/saasApp/test_function.py b/test_suite/testcase/saasApp/test_function.py index 93f4b4e..6b608b9 100644 --- a/test_suite/testcase/saasApp/test_function.py +++ b/test_suite/testcase/saasApp/test_function.py @@ -5,7 +5,7 @@ @file: test_function.py @time: 2021/10/8 15:02 """ -from test_suite.page.saasApp_pages import * +from test_suite.page.saasApp.saasApp_pages import * # from test_suite.page.third_pages import * paramData = ini_yaml("functionData.yml") # thirdData = ini_yaml("thirdData.yml") diff --git a/test_suite/testcase/saasApp/test_homePage.py b/test_suite/testcase/saasApp/test_homePage.py index c62a5b7..17a399b 100644 --- a/test_suite/testcase/saasApp/test_homePage.py +++ b/test_suite/testcase/saasApp/test_homePage.py @@ -1,5 +1,5 @@ # coding:utf-8 -from test_suite.page.saasApp_pages import * +from test_suite.page.saasApp.saasApp_pages import * paramData = ini_yaml("homePageData.yml") diff --git a/test_suite/testcase/saasApp/test_login.py b/test_suite/testcase/saasApp/test_login.py index c8980c1..dc09f94 100644 --- a/test_suite/testcase/saasApp/test_login.py +++ b/test_suite/testcase/saasApp/test_login.py @@ -1,7 +1,7 @@ # coding:utf-8 import pytest -from test_suite.page.saasApp_pages import * +from test_suite.page.saasApp.saasApp_pages import * paramData = ini_yaml("loginData.yml") diff --git a/test_suite/testcase/saasApp/test_power.py b/test_suite/testcase/saasApp/test_power.py index 1a76d8b..8bbffe7 100644 --- a/test_suite/testcase/saasApp/test_power.py +++ b/test_suite/testcase/saasApp/test_power.py @@ -5,8 +5,8 @@ @file: test_power.py @time: 2021/9/17 16:56 """ -from test_suite.page.saasApp_pages import * -from test_suite.page.third_pages import * +from test_suite.page.saasApp.saasApp_pages import * +from test_suite.page.saasApp.third_pages import * paramData = ini_yaml("powerData.yml") thirdData = ini_yaml("thirdData.yml") diff --git a/test_suite/testcase/saasApp/test_region.py b/test_suite/testcase/saasApp/test_region.py index 1bb74c2..bcd387c 100644 --- a/test_suite/testcase/saasApp/test_region.py +++ b/test_suite/testcase/saasApp/test_region.py @@ -5,7 +5,7 @@ @file: test_region.py @time: 2021/10/8 16:11 """ -from test_suite.page.saasApp_pages import * +from test_suite.page.saasApp.saasApp_pages import * # from test_suite.page.third_pages import * paramData = ini_yaml("regionData.yml") # thirdData = ini_yaml("thirdData.yml") diff --git a/test_suite/testcase/saasWeb/conftest.py b/test_suite/testcase/saasWeb/conftest.py new file mode 100644 index 0000000..bcbabb6 --- /dev/null +++ b/test_suite/testcase/saasWeb/conftest.py @@ -0,0 +1,22 @@ +# coding:utf-8 +""" +@author: 井松 +@contact: 529548204@qq.com +@file: conftest.py +@time: 2021/12/3 14:25 +""" +from test_suite.page.saasWeb.saasWeb_pages import * + +paramData = ini_yaml("loginData.yml")["login"][0] + + +@pytest.fixture(scope="module") +def setup_Login(): + logging.info("前置请求登录") + data = urlData["login"] + logging.info("{}".format(paramData["info"])) + res, restime = apisend(host=data["host"],address=data["address"], method=data["method"], headers=paramData["headers"], + data=paramData["data"],rel=data["relevance"]) + + logging.info("前置请求结束") + return res diff --git a/test_suite/testcase/saasWeb/test_firmware.py b/test_suite/testcase/saasWeb/test_firmware.py new file mode 100644 index 0000000..29b8490 --- /dev/null +++ b/test_suite/testcase/saasWeb/test_firmware.py @@ -0,0 +1,53 @@ +# coding:utf-8 +""" +@author: 井松 +@contact: 529548204@qq.com +@file: test_firmware.py +@time: 2021/12/3 14:30 +""" +import pytest + +from test_suite.page.saasWeb.saasWeb_pages import * + +paramData = ini_yaml("firmwareData.yml") + + +class Test_firmwareList(object): + # def setup(self): + # self.re = saasPages() + # ids=[i["info"] for i in paramData["login"]] + @allure.story("Test_firmwareList") + @pytest.mark.parametrize('casedata', paramData["firmwareList"], ids=[i["info"] for i in paramData["firmwareList"]]) + @pytest.mark.flaky(reruns=1, reruns_delay=1) + @pytest.mark.run(order=2) + def test_firmwareList(self, casedata, setup_webLogin): + casedata["headers"]["Authorization"] = "JWT " + setup_webLogin["data"]["token"] + res, restime = firmwareList(casedata) + asserting(hope_res=casedata["assert"], real_res=res, re_time=restime) + + @allure.story("Test_firmwareAdd") + @pytest.mark.parametrize('casedata', paramData["firmwareAdd"], ids=[i["info"] for i in paramData["firmwareAdd"]]) + @pytest.mark.flaky(reruns=1, reruns_delay=1) + @pytest.mark.run(order=1) + def test_firmwareAdd(self, casedata, setup_webLogin): + casedata["headers"]["Authorization"] = "JWT " + setup_webLogin["data"]["token"] + res, restime = firmwareAdd(casedata) + asserting(hope_res=casedata["assert"], real_res=res, re_time=restime) + + @allure.story("Test_firmwareDel") + @pytest.mark.parametrize('casedata', paramData["firmwareDel"], ids=[i["info"] for i in paramData["firmwareDel"]]) + @pytest.mark.flaky(reruns=1, reruns_delay=1) + @pytest.mark.run(order=3) + def test_firmwareDel(self, casedata, setup_Login): + casedata["headers"]["Authorization"] = "JWT " + setup_Login["data"]["token"] + res, restime = firmwareDel(casedata) + asserting(hope_res=casedata["assert"], real_res=res, re_time=restime) + + @allure.story("Test_firmwareDetail") + @pytest.mark.parametrize('casedata', paramData["firmwareDetail"], ids=[i["info"] for i in paramData["firmwareDetail"]]) + @pytest.mark.flaky(reruns=1, reruns_delay=1) + @pytest.mark.run(order=2) + def test_firmwareDetail(self, casedata, setup_webLogin): + # casedata["headers"]["Authorization"] = "JWT " + setup_webLogin["data"]["token"] + res, restime = firmwareDetail(casedata) + asserting(hope_res=casedata["assert"], real_res=res, re_time=restime) \ No newline at end of file diff --git a/test_suite/testcase/saasWeb/test_login.py b/test_suite/testcase/saasWeb/test_login.py new file mode 100644 index 0000000..26f1b83 --- /dev/null +++ b/test_suite/testcase/saasWeb/test_login.py @@ -0,0 +1,27 @@ +# coding:utf-8 +""" +@author: 井松 +@contact: 529548204@qq.com +@file: test_login.py +@time: 2021/12/3 13:54 +""" +import pytest + +from test_suite.page.saasWeb.saasWeb_pages import * + +paramData = ini_yaml("loginData.yml") + + +class Test_login(object): + # def setup(self): + # self.re = saasPages() + # ids=[i["info"] for i in paramData["login"]] + @allure.story("Test_login") + @pytest.mark.parametrize('casedata', paramData["login"], ids=[i["info"] for i in paramData["login"]]) + @pytest.mark.flaky(reruns=1, reruns_delay=1) + @pytest.mark.run(order=1) + def test_login(self, casedata): + res, restime = login(casedata) + asserting(hope_res=casedata["assert"], real_res=res,re_time=restime) + + -- Gitee From 3c6c4e484ac4cca0e35db336e2d8af380c3b9c25 Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Wed, 22 Dec 2021 16:59:40 +0800 Subject: [PATCH 27/30] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test_suite/testcase/saasWeb/test_firmware.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test_suite/testcase/saasWeb/test_firmware.py b/test_suite/testcase/saasWeb/test_firmware.py index 29b8490..eae3d2c 100644 --- a/test_suite/testcase/saasWeb/test_firmware.py +++ b/test_suite/testcase/saasWeb/test_firmware.py @@ -20,8 +20,8 @@ class Test_firmwareList(object): @pytest.mark.parametrize('casedata', paramData["firmwareList"], ids=[i["info"] for i in paramData["firmwareList"]]) @pytest.mark.flaky(reruns=1, reruns_delay=1) @pytest.mark.run(order=2) - def test_firmwareList(self, casedata, setup_webLogin): - casedata["headers"]["Authorization"] = "JWT " + setup_webLogin["data"]["token"] + def test_firmwareList(self, casedata, setup_Login): + casedata["headers"]["Authorization"] = "JWT " + setup_Login["data"]["token"] res, restime = firmwareList(casedata) asserting(hope_res=casedata["assert"], real_res=res, re_time=restime) @@ -29,8 +29,8 @@ class Test_firmwareList(object): @pytest.mark.parametrize('casedata', paramData["firmwareAdd"], ids=[i["info"] for i in paramData["firmwareAdd"]]) @pytest.mark.flaky(reruns=1, reruns_delay=1) @pytest.mark.run(order=1) - def test_firmwareAdd(self, casedata, setup_webLogin): - casedata["headers"]["Authorization"] = "JWT " + setup_webLogin["data"]["token"] + def test_firmwareAdd(self, casedata, setup_Login): + casedata["headers"]["Authorization"] = "JWT " + setup_Login["data"]["token"] res, restime = firmwareAdd(casedata) asserting(hope_res=casedata["assert"], real_res=res, re_time=restime) @@ -47,7 +47,7 @@ class Test_firmwareList(object): @pytest.mark.parametrize('casedata', paramData["firmwareDetail"], ids=[i["info"] for i in paramData["firmwareDetail"]]) @pytest.mark.flaky(reruns=1, reruns_delay=1) @pytest.mark.run(order=2) - def test_firmwareDetail(self, casedata, setup_webLogin): - # casedata["headers"]["Authorization"] = "JWT " + setup_webLogin["data"]["token"] + def test_firmwareDetail(self, casedata, setup_Login): + casedata["headers"]["Authorization"] = "JWT " + setup_Login["data"]["token"] res, restime = firmwareDetail(casedata) asserting(hope_res=casedata["assert"], real_res=res, re_time=restime) \ No newline at end of file -- Gitee From 86bd5e4dc2ee4b5857f7135b0ebfb037449e7a37 Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Wed, 22 Dec 2021 17:54:37 +0800 Subject: [PATCH 28/30] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/newProject.py | 9 +++ scripts/writePage.py | 70 ++++++++++---------- test_suite/page/saasWeb/saasWeb_pages.py | 31 +++++---- test_suite/testcase/saasWeb/__init__.py | 23 +++++++ test_suite/testcase/saasWeb/conftest.py | 7 +- test_suite/testcase/saasWeb/test_firmware.py | 26 +++++--- test_suite/testcase/saasWeb/test_login.py | 17 ++--- 7 files changed, 107 insertions(+), 76 deletions(-) create mode 100644 scripts/newProject.py create mode 100644 test_suite/testcase/saasWeb/__init__.py diff --git a/scripts/newProject.py b/scripts/newProject.py new file mode 100644 index 0000000..51ecca1 --- /dev/null +++ b/scripts/newProject.py @@ -0,0 +1,9 @@ +# coding:utf-8 +""" +@author: 井松 +@contact: 529548204@qq.com +@file: newProject.py +@time: 2021/12/22 15:52 +""" +def newProject(test_name): + pass \ No newline at end of file diff --git a/scripts/writePage.py b/scripts/writePage.py index d063974..b7774e4 100644 --- a/scripts/writePage.py +++ b/scripts/writePage.py @@ -10,56 +10,52 @@ Log() import logging -def getFilePathList(path, filetype): - pathList = [] +def getFilePathList(path): + filename = [] + # 获取所有文件下的子文件名称 for root, dirs, files in os.walk(path): - for file in files: - if file.endswith(filetype): - pathList.append(os.path.join(root, file)) - return pathList + # 过滤所有空文件 + if files: + for i in files: + # 判断只返回 yaml 的文件 + if '.yml' in i: + filename.append(str(i).split(".")[0]) + return filename def write_pages(_file,pagename): t1 = time.time() - path = dir_manage('${pro_dir}$') + dir_manage('${test_suite}$') + dir_manage('${page_dir}$') + dir_manage('${test_name}$') - print(path) + path = dir_manage('${pro_dir}$') + dir_manage('${test_suite}$') + dir_manage('${testcase}$') + dir_manage('${test_name}$') + filelist = getFilePathList(path) + print(filelist) with open(path +"/" +r"{}".format(pagename), 'w+', encoding='utf-8') as f: - f.write("""# coding:utf-8\nfrom test_suite.page.{} import *\n\nurlData = ini_yaml("{}")\n\n""".format(dir_manage('${test_name}$'),_file)) - yml_data = ini_yaml(_file) - - for item in yml_data.items(): - logging.debug("正在生成page文件.{}".format(str(item))) - f.write(""" -def {testtitle}(casedata):""".format(testtitle=item[0])) - f.write(""" - data = urlData["{0}"]""".format(item[0]) - ) - f.write( - """ - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"])""" - - ) - if item[1]["relevanceCheck"]: - f.write( -""" - return {casedata["info"]:res}, restime\n\n""" - ) - else : - f.write( -""" - return res, restime\n\n""" - ) + f.write("""from test_suite.testcase.saasWeb import * + +paramData = ini_yaml("loginData.yml") + +class Test_login(object): + @allure.story("Test_login") + @pytest.mark.parametrize('casedata', paramData["login"]["case"], + ids=[i["info"] for i in paramData["login"]["case"]]) + @pytest.mark.flaky(reruns=1, reruns_delay=1) + @pytest.mark.run(order=1) + def test_login(self, casedata): + res, restime = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"], + headers=casedata["headers"], + data=casedata["data"], rel=casedata["relevance"]) + asserting(hope_res=casedata["assert"], real_res=res, re_time=restime)""") + t2 = time.time() - t1 print(t2) # def write_case(f): +# for i in f: # + if __name__ == '__main__': # ym_path = r'thirdUrl.yml' # pagenames = "third_pages_1.py" @@ -70,4 +66,6 @@ if __name__ == '__main__': # ym_path = r'urlData.yml' # pagenames = "saasWeb_pages_1.py" - write_pages(ym_path,pagenames) + # write_pages(ym_path,pagenames) + l = getFilePathList(r"D:\apitest\test_suite\datas\saasWeb") + print(l) diff --git a/test_suite/page/saasWeb/saasWeb_pages.py b/test_suite/page/saasWeb/saasWeb_pages.py index 153f08b..5bcfd63 100644 --- a/test_suite/page/saasWeb/saasWeb_pages.py +++ b/test_suite/page/saasWeb/saasWeb_pages.py @@ -1,45 +1,44 @@ # coding:utf-8 from test_suite.page.saasWeb import * -urlData = ini_yaml("urlData.yml") + def login(casedata): - data = urlData["login"] logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) + res, restime = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"], headers=casedata["headers"], + data=casedata["data"],rel=casedata["relevance"]) return res, restime def firmwareList(casedata): - data = urlData["firmwareList"] + logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) + res, restime = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"], headers=casedata["headers"], + data=casedata["data"],rel=casedata["relevance"]) return res, restime def firmwareAdd(casedata): - data = urlData["firmwareAdd"] + logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) + res, restime = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"], headers=casedata["headers"], + data=casedata["data"],rel=casedata["relevance"]) return res, restime def firmwareDel(casedata): - data = urlData["firmwareDel"] + logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) + res, restime = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"], headers=casedata["headers"], + data=casedata["data"],rel=casedata["relevance"]) return res, restime def firmwareDetail(casedata): - data = urlData["firmwareDetail"] + logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) + res, restime = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"], headers=casedata["headers"], + data=casedata["data"],rel=casedata["relevance"]) return res, restime diff --git a/test_suite/testcase/saasWeb/__init__.py b/test_suite/testcase/saasWeb/__init__.py new file mode 100644 index 0000000..910bcd2 --- /dev/null +++ b/test_suite/testcase/saasWeb/__init__.py @@ -0,0 +1,23 @@ +# coding:utf-8 + +import logging + +import allure +import pytest + +from common.checkResult import asserting +from scripts.log import Log +from scripts.readYamlFile import ini_yaml +from common.basePage import apisend + + +Log() +__all__ = [ + 'pytest', + 'asserting', + 'Log', + 'ini_yaml', + 'logging', + 'allure', + 'apisend', +] \ No newline at end of file diff --git a/test_suite/testcase/saasWeb/conftest.py b/test_suite/testcase/saasWeb/conftest.py index bcbabb6..6c41f42 100644 --- a/test_suite/testcase/saasWeb/conftest.py +++ b/test_suite/testcase/saasWeb/conftest.py @@ -7,16 +7,15 @@ """ from test_suite.page.saasWeb.saasWeb_pages import * -paramData = ini_yaml("loginData.yml")["login"][0] +paramData = ini_yaml("loginData.yml")["login"]["case"][0] @pytest.fixture(scope="module") def setup_Login(): logging.info("前置请求登录") - data = urlData["login"] logging.info("{}".format(paramData["info"])) - res, restime = apisend(host=data["host"],address=data["address"], method=data["method"], headers=paramData["headers"], - data=paramData["data"],rel=data["relevance"]) + res, restime = apisend(host=paramData["host"],address=paramData["address"], method=paramData["method"], headers=paramData["headers"], + data=paramData["data"],rel=paramData["relevance"]) logging.info("前置请求结束") return res diff --git a/test_suite/testcase/saasWeb/test_firmware.py b/test_suite/testcase/saasWeb/test_firmware.py index eae3d2c..1153e68 100644 --- a/test_suite/testcase/saasWeb/test_firmware.py +++ b/test_suite/testcase/saasWeb/test_firmware.py @@ -7,22 +7,21 @@ """ import pytest -from test_suite.page.saasWeb.saasWeb_pages import * +from test_suite.testcase.saasWeb import * paramData = ini_yaml("firmwareData.yml") class Test_firmwareList(object): - # def setup(self): - # self.re = saasPages() - # ids=[i["info"] for i in paramData["login"]] @allure.story("Test_firmwareList") @pytest.mark.parametrize('casedata', paramData["firmwareList"], ids=[i["info"] for i in paramData["firmwareList"]]) @pytest.mark.flaky(reruns=1, reruns_delay=1) @pytest.mark.run(order=2) def test_firmwareList(self, casedata, setup_Login): casedata["headers"]["Authorization"] = "JWT " + setup_Login["data"]["token"] - res, restime = firmwareList(casedata) + res, restime = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"], + headers=casedata["headers"], + data=casedata["data"], rel=casedata["relevance"]) asserting(hope_res=casedata["assert"], real_res=res, re_time=restime) @allure.story("Test_firmwareAdd") @@ -31,7 +30,9 @@ class Test_firmwareList(object): @pytest.mark.run(order=1) def test_firmwareAdd(self, casedata, setup_Login): casedata["headers"]["Authorization"] = "JWT " + setup_Login["data"]["token"] - res, restime = firmwareAdd(casedata) + res, restime = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"], + headers=casedata["headers"], + data=casedata["data"], rel=casedata["relevance"]) asserting(hope_res=casedata["assert"], real_res=res, re_time=restime) @allure.story("Test_firmwareDel") @@ -40,14 +41,19 @@ class Test_firmwareList(object): @pytest.mark.run(order=3) def test_firmwareDel(self, casedata, setup_Login): casedata["headers"]["Authorization"] = "JWT " + setup_Login["data"]["token"] - res, restime = firmwareDel(casedata) + res, restime = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"], + headers=casedata["headers"], + data=casedata["data"], rel=casedata["relevance"]) asserting(hope_res=casedata["assert"], real_res=res, re_time=restime) @allure.story("Test_firmwareDetail") - @pytest.mark.parametrize('casedata', paramData["firmwareDetail"], ids=[i["info"] for i in paramData["firmwareDetail"]]) + @pytest.mark.parametrize('casedata', paramData["firmwareDetail"], + ids=[i["info"] for i in paramData["firmwareDetail"]]) @pytest.mark.flaky(reruns=1, reruns_delay=1) @pytest.mark.run(order=2) def test_firmwareDetail(self, casedata, setup_Login): casedata["headers"]["Authorization"] = "JWT " + setup_Login["data"]["token"] - res, restime = firmwareDetail(casedata) - asserting(hope_res=casedata["assert"], real_res=res, re_time=restime) \ No newline at end of file + res, restime = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"], + headers=casedata["headers"], + data=casedata["data"], rel=casedata["relevance"]) + asserting(hope_res=casedata["assert"], real_res=res, re_time=restime) diff --git a/test_suite/testcase/saasWeb/test_login.py b/test_suite/testcase/saasWeb/test_login.py index 26f1b83..fddc5b5 100644 --- a/test_suite/testcase/saasWeb/test_login.py +++ b/test_suite/testcase/saasWeb/test_login.py @@ -5,23 +5,20 @@ @file: test_login.py @time: 2021/12/3 13:54 """ -import pytest -from test_suite.page.saasWeb.saasWeb_pages import * +from test_suite.testcase.saasWeb import * paramData = ini_yaml("loginData.yml") class Test_login(object): - # def setup(self): - # self.re = saasPages() - # ids=[i["info"] for i in paramData["login"]] @allure.story("Test_login") - @pytest.mark.parametrize('casedata', paramData["login"], ids=[i["info"] for i in paramData["login"]]) + @pytest.mark.parametrize('casedata', paramData["login"]["case"], + ids=[i["info"] for i in paramData["login"]["case"]]) @pytest.mark.flaky(reruns=1, reruns_delay=1) @pytest.mark.run(order=1) def test_login(self, casedata): - res, restime = login(casedata) - asserting(hope_res=casedata["assert"], real_res=res,re_time=restime) - - + res, restime = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"], + headers=casedata["headers"], + data=casedata["data"], rel=casedata["relevance"]) + asserting(hope_res=casedata["assert"], real_res=res, re_time=restime) -- Gitee From c43c86980ab35ba6e8ebbee3559ca4a92b393ce6 Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Thu, 23 Dec 2021 14:35:56 +0800 Subject: [PATCH 29/30] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E6=A1=86=E6=9E=B6?= =?UTF-8?q?=E9=80=BB=E8=BE=91=20=E4=BC=98=E5=8C=96yaml=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 142 ++++++++---------- img.png | Bin 15218 -> 0 bytes img_1.png | Bin 38193 -> 0 bytes img_2.png | Bin 38960 -> 0 bytes scripts/newProject.py | 42 +++++- scripts/readYamlFile.py | 3 +- scripts/{writePage.py => writeCase.py} | 59 +++++--- setupMain.py | 6 +- test_suite/page/__init__.py | 1 - test_suite/page/saasApp/__init__.py | 30 ---- test_suite/page/saasApp/saasApp_pages.py | 141 ----------------- test_suite/page/saasApp/saasApp_pages_1.py | 141 ----------------- test_suite/page/saasApp/temple.py | 7 - test_suite/page/saasApp/third_pages.py | 37 ----- test_suite/page/saasApp/third_pages_1.py | 37 ----- test_suite/page/saasWeb/saasApp_pages_1.py | 45 ------ test_suite/page/saasWeb/saasWeb_pages.py | 44 ------ test_suite/page/saasWeb/saasWeb_pages_1.py | 45 ------ .../saasWeb => testcase/fengling}/__init__.py | 10 +- test_suite/testcase/fengling/conftest.py | 2 + test_suite/testcase/saasWeb/conftest.py | 6 +- test_suite/testcase/saasWeb/test_firmware.py | 55 +++---- test_suite/testcase/saasWeb/test_login.py | 13 +- 23 files changed, 177 insertions(+), 689 deletions(-) delete mode 100644 img.png delete mode 100644 img_1.png delete mode 100644 img_2.png rename scripts/{writePage.py => writeCase.py} (47%) delete mode 100644 test_suite/page/__init__.py delete mode 100644 test_suite/page/saasApp/__init__.py delete mode 100644 test_suite/page/saasApp/saasApp_pages.py delete mode 100644 test_suite/page/saasApp/saasApp_pages_1.py delete mode 100644 test_suite/page/saasApp/temple.py delete mode 100644 test_suite/page/saasApp/third_pages.py delete mode 100644 test_suite/page/saasApp/third_pages_1.py delete mode 100644 test_suite/page/saasWeb/saasApp_pages_1.py delete mode 100644 test_suite/page/saasWeb/saasWeb_pages.py delete mode 100644 test_suite/page/saasWeb/saasWeb_pages_1.py rename test_suite/{page/saasWeb => testcase/fengling}/__init__.py (66%) create mode 100644 test_suite/testcase/fengling/conftest.py diff --git a/README.md b/README.md index 363aadc..ba245cd 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,6 @@ └─ 项目文件夹 名称同config中 testname一致 ├─ urlData.yml # 所有 url 具体格式参考下面YAML URL格式说明 └─ login.yml # 用例数据 格式参考下面YAML PARAM格式说明 - ├─page # 所有请求封装 通过writePage.py根据url的yaml文件生成 ├─testcase └─ 项目文件夹 名称同config中 testname一致 # 测试用例 └─ test_login.py @@ -27,84 +26,66 @@ ``` yaml login: -- info: "用户名登录-成功" - headers: { - "Content-Type": "application/json" - } - data: - param: { - "username": "finsiot","password": "$caches(pwd)$" # 读取缓存值 + name: "登录" + token: false + order: 1 + case: + - info: "用户名登录-成功" + host: 'host' + address: '/v1/apps/$url(region_id)$/' # $url(region_id)$ 正则匹配参数中的路径参数 + method: 'post' + relevance: + - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 + path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 + name: 'code' + - cachefrom: 'response' # response : 从结果中获取 body : 从参数中获取 + path: '$.data' + name: 'data' + headers: { + "Content-Type": "application/json" } - urlparam: { - id: 123 - }# 路径参数 v1/api/$url(id)$/ - assert: - jsonpath: - # 关联验证 - - { - "path": "$.data.expense_trend[0].peak_hour.peak_hour", - "value": "$json($.data.trend[0].data.peak_hour)$", # 期望值为其他接口请求数据 - "asserttype": "==", - "relevance": "web电费统计" # 根据特定返回结果获取 为被关联验证接口的info + data: + param: { + "username": "finsiot","password": "$caches(pwd)$" # 读取缓存值 } - - { - "path": "$.code", - "value": 0, - "asserttype": "==", - "relevance": - } - - { - "path": "$.data.id", - "value": 196, - "asserttype": "==", - "relevance": - } - sqlassert: - - { - "datas": [ - { - "path": "$.data.id", - "name": "id" - }, - { - "path": "$.data.username", - "name": "username" - }, - ], - "sql": "select * from saas.user where username = '****'", - # 取数据库查询出的第一条数据进行验证 如果存在 列名 username 值为$.data.username则通过 + urlparam: { + id: 123 + }# 路径参数 v1/api/$url(id)$/ + assert: + jsonpath: + # 关联验证 + - { + "path": "$.data.expense_trend[0].peak_hour.peak_hour", + "value": "123", + "asserttype": "==" } - time: 2 -``` - - -## yaml url格式 - -``` yaml -regionList: - name: '区域列表' - host: 'host' - address: '/v1/apps/$url(region_id)$/' # $url(region_id)$ 正则匹配参数中的路径参数 - method: 'get' - relevance: - - cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取 - path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值 - name: 'code' - - cachefrom: 'response' # response : 从结果中获取 body : 从参数中获取 - path: '$.data' - name: 'data' - relevanceCheck: true - # true为断言验证存在关联验证 此接口为被关联验证接口 接口结果为 期望值 - # 不存关联验证时 为空或者false - # 断言验证关联 + - { + "path": "$.code", + "value": 0, + "asserttype": "==" + } + - { + "path": "$.data.id", + "value": 196, + "asserttype": "==" + } + sqlassert: + - { + "datas": [ + { + "path": "$.data.id", + "name": "id" + }, + { + "path": "$.data.username", + "name": "username" + }, + ], + "sql": "select * from saas.user where username = '****'", + # 取数据库查询出的第一条数据进行验证 如果存在 列名 username 值为$.data.username则通过 + } + time: 2 ``` - -##关联验证 -多接口 -![img_2.png](img_2.png) - 多接口结果合并成一个字典传给 third_datas -单接口 - 两种方法 同多接口类似 或者直接传结果给third_data ## config.ini ```ini @@ -146,9 +127,8 @@ charset = utf8 webhook = secret = ``` - -## 其他 -jsonpath断言中value支持根据正则关联其他接口数据 -![img.png](img.png) -![img_1.png](img_1.png) - \ No newline at end of file +## 操作方法 +1. 新建config/config.ini文件 格式如上例子 +2. 执行scripts/newProject.py +3. 在生成的test_suite/datas/名称 文件夹下增加yaml测试用例 格式如上 +4. 执行writeCase.py生成测试脚本 关于token 需要根据自己项目情况修改writeCase.py的内容 (第59行) \ No newline at end of file diff --git a/img.png b/img.png deleted file mode 100644 index 12b9ea0bcec2d7a385441f8a6bdbebabf66185bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15218 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 z 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 t1Fii(Ujopu=vFhiZ%STr?y*~f| diff --git a/scripts/newProject.py b/scripts/newProject.py index 51ecca1..80d1279 100644 --- a/scripts/newProject.py +++ b/scripts/newProject.py @@ -5,5 +5,43 @@ @file: newProject.py @time: 2021/12/22 15:52 """ -def newProject(test_name): - pass \ No newline at end of file +from config.confManage import dir_manage +from scripts.mkDir import mk_dir +testname = dir_manage('${test_name}$') +pro_path = dir_manage('${pro_dir}$') + dir_manage('${test_suite}$') +casepath = pro_path + dir_manage('${case_dir}$') + testname + +datapath = pro_path + dir_manage('${data_dir}$') + testname +def newProject(): + mk_dir(casepath) + mk_dir(datapath) + with open(casepath + "/" + r"{}".format("__init__.py"), 'w', encoding='utf-8') as f: + f.write("""# coding:utf-8 + +import logging + +import allure +import pytest + +from common.checkResult import asserting +from scripts.log import Log +from scripts.readYamlFile import ini_yaml +from common.basePage import apisend + + +Log() +__all__ = [ + 'pytest', + 'asserting', + 'Log', + 'ini_yaml', + 'logging', + 'allure', + 'apisend', +]""") + with open(casepath + "/" + r"{}".format("conftest.py"), 'w', encoding='utf-8') as f: + f.write(f"""# coding:utf-8 +from test_suite.testcase.{testname} import *""") + +if __name__ == '__main__': + newProject() \ No newline at end of file diff --git a/scripts/readYamlFile.py b/scripts/readYamlFile.py index fa5cfd6..ae000d4 100644 --- a/scripts/readYamlFile.py +++ b/scripts/readYamlFile.py @@ -24,7 +24,8 @@ def ini_yaml(filename, path=datapath): if __name__ == '__main__': print(datapath) # get_yaml_data(r"F:\api2.0\config\runConfig.yml") - # runConfig_dict = ini_yaml(r'tmUrlData.yml',path= r"D:\apitest\test_suite\datas\troubleManage") + runConfig_dict = ini_yaml(r'login.yml')["login"]["case"] + print(runConfig_dict) # # case_level = runConfig_dict[0]["address"].format(**{"home_id": "123"}) # print(runConfig_dict) diff --git a/scripts/writePage.py b/scripts/writeCase.py similarity index 47% rename from scripts/writePage.py rename to scripts/writeCase.py index b7774e4..a61c77e 100644 --- a/scripts/writePage.py +++ b/scripts/writeCase.py @@ -23,27 +23,48 @@ def getFilePathList(path): return filename -def write_pages(_file,pagename): +def write_pages(_file): t1 = time.time() - path = dir_manage('${pro_dir}$') + dir_manage('${test_suite}$') + dir_manage('${testcase}$') + dir_manage('${test_name}$') - filelist = getFilePathList(path) - print(filelist) - with open(path +"/" +r"{}".format(pagename), 'w+', encoding='utf-8') as f: - f.write("""from test_suite.testcase.saasWeb import * - -paramData = ini_yaml("loginData.yml") + testname = dir_manage('${test_name}$') + pro_path = dir_manage('${pro_dir}$') + dir_manage('${test_suite}$') + casepath = pro_path + dir_manage('${case_dir}$') + testname -class Test_login(object): - @allure.story("Test_login") - @pytest.mark.parametrize('casedata', paramData["login"]["case"], - ids=[i["info"] for i in paramData["login"]["case"]]) + datapath = pro_path + dir_manage('${data_dir}$') + testname + filelist = getFilePathList(datapath) + print(filelist) + for file in filelist: + filedata = ini_yaml(file + ".yml") + if "test_" + file + ".py" in os.listdir(casepath): + pass + else: + with open(casepath + "/" + r"{}".format("test_" + file + ".py"), 'w', encoding='utf-8') as f: + f.write(f"""from test_suite.testcase.{testname} import * + +paramData = ini_yaml("{file}.yml") + +class Test_{file}(object):""") + for item in filedata: + order = filedata[item]["order"] + + f.write(f""" + @allure.story("Test_{file}") + @pytest.mark.parametrize('casedata', paramData["{item}"]["case"], + ids=[i["info"] for i in paramData["{item}"]["case"]]) @pytest.mark.flaky(reruns=1, reruns_delay=1) - @pytest.mark.run(order=1) - def test_login(self, casedata): + @pytest.mark.run(order={order})""") + + if filedata[item]["token"]: + f.write(f""" + def test_{item}(self, casedata, setup_Login): + casedata["headers"]["Authorization"] = setup_Login""") # token类型是 Authorization + else: + f.write(f""" + def test_{item}(self, casedata):""") + f.write(""" res, restime = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"], headers=casedata["headers"], data=casedata["data"], rel=casedata["relevance"]) - asserting(hope_res=casedata["assert"], real_res=res, re_time=restime)""") + asserting(hope_res=casedata["assert"], real_res=res, re_time=restime)\n""") t2 = time.time() - t1 print(t2) @@ -54,8 +75,6 @@ class Test_login(object): # - - if __name__ == '__main__': # ym_path = r'thirdUrl.yml' # pagenames = "third_pages_1.py" @@ -66,6 +85,6 @@ if __name__ == '__main__': # ym_path = r'urlData.yml' # pagenames = "saasWeb_pages_1.py" - # write_pages(ym_path,pagenames) - l = getFilePathList(r"D:\apitest\test_suite\datas\saasWeb") - print(l) + write_pages(ym_path) + # l = getFilePathList(r"D:\apitest\test_suite\datas\saasWeb") + # print(l) diff --git a/setupMain.py b/setupMain.py index 5ba800a..c2bba69 100644 --- a/setupMain.py +++ b/setupMain.py @@ -21,16 +21,16 @@ def run(): test_case_path = project_path + dir_manage('${test_suite}$') + dir_manage('${case_dir}$') + dir_manage( '${test_name}$') # temp地址变量 - temp_path = project_path + dir_manage('${report_xml_dir}$') + "temp/" + localtime + '/' + temp_path = project_path + dir_manage('${report_xml_dir}$') + "temp/" # html地址变量 - html_path = project_path + dir_manage('${report_html_dir}$') + date + '/' + html_path = project_path + dir_manage('${report_html_dir}$') # 如果不存在地址路径则创建文件夹 mk_dir(temp_path) mk_dir(html_path) # 执行命令行 args = ['-s', '-q', test_case_path, '--alluredir', temp_path] # args = ['-s', '-q', test_case_path,] - # pytest.main(args) + pytest.main(args) cmd = 'allure generate %s -o %s -c' % (temp_path, html_path) os.system(cmd) diff --git a/test_suite/page/__init__.py b/test_suite/page/__init__.py deleted file mode 100644 index f512dea..0000000 --- a/test_suite/page/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# coding:utf-8 diff --git a/test_suite/page/saasApp/__init__.py b/test_suite/page/saasApp/__init__.py deleted file mode 100644 index 970b2ad..0000000 --- a/test_suite/page/saasApp/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -# coding:utf-8 -""" -@author: 井松 -@contact: 529548204@qq.com -@file: __init__.py.py -@time: 2021/12/3 13:51 -""" - -import logging - -import allure -import pytest - -from common.checkResult import asserting -from scripts.log import Log -from scripts.readYamlFile import ini_yaml -from common.basePage import apisend -from test_suite.page.saasApp import saasApp_pages - -Log() -__all__ = [ - 'pytest', - 'asserting', - 'Log', - 'ini_yaml', - 'logging', - 'allure', - 'apisend', - 'saasApp_pages', -] \ No newline at end of file diff --git a/test_suite/page/saasApp/saasApp_pages.py b/test_suite/page/saasApp/saasApp_pages.py deleted file mode 100644 index 8f0539d..0000000 --- a/test_suite/page/saasApp/saasApp_pages.py +++ /dev/null @@ -1,141 +0,0 @@ -# coding:utf-8 -from test_suite.page.saasApp import * - -urlData = ini_yaml("urlData.yml") - - -def login(casedata): - data = urlData["login"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def forgetPassword(casedata): - data = urlData["forgetPassword"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def mobileCode(casedata): - data = urlData["mobileCode"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def todayTask(casedata): - data = urlData["todayTask"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def companyName(casedata): - data = urlData["companyName"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def companyPower(casedata): - data = urlData["companyPower"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def deviceState(casedata): - data = urlData["deviceState"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def companyAlarm(casedata): - data = urlData["companyAlarm"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def todayTrend(casedata): - data = urlData["todayTrend"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def alarmStatistic(casedata): - data = urlData["alarmStatistic"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def alarmTrend(casedata): - data = urlData["alarmTrend"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def alarmRank(casedata): - data = urlData["alarmRank"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def alarmDistribute(casedata): - data = urlData["alarmDistribute"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def powerToday(casedata): - data = urlData["powerToday"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def powerFees(casedata): - data = urlData["powerFees"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def functionList(casedata): - data = urlData["functionList"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def regionList(casedata): - data = urlData["regionList"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - diff --git a/test_suite/page/saasApp/saasApp_pages_1.py b/test_suite/page/saasApp/saasApp_pages_1.py deleted file mode 100644 index 373dade..0000000 --- a/test_suite/page/saasApp/saasApp_pages_1.py +++ /dev/null @@ -1,141 +0,0 @@ -# coding:utf-8 -from test_suite.page import * - -urlData = ini_yaml("urlData.yml") - - -def login(casedata): - data = urlData["login"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def forgetPassword(casedata): - data = urlData["forgetPassword"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def mobileCode(casedata): - data = urlData["mobileCode"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def todayTask(casedata): - data = urlData["todayTask"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def companyName(casedata): - data = urlData["companyName"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def companyPower(casedata): - data = urlData["companyPower"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def deviceState(casedata): - data = urlData["deviceState"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def companyAlarm(casedata): - data = urlData["companyAlarm"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def todayTrend(casedata): - data = urlData["todayTrend"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def alarmStatistic(casedata): - data = urlData["alarmStatistic"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def alarmTrend(casedata): - data = urlData["alarmTrend"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def alarmRank(casedata): - data = urlData["alarmRank"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def alarmDistribute(casedata): - data = urlData["alarmDistribute"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def powerToday(casedata): - data = urlData["powerToday"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def powerFees(casedata): - data = urlData["powerFees"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def functionList(casedata): - data = urlData["functionList"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def regionList(casedata): - data = urlData["regionList"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - diff --git a/test_suite/page/saasApp/temple.py b/test_suite/page/saasApp/temple.py deleted file mode 100644 index c749cd1..0000000 --- a/test_suite/page/saasApp/temple.py +++ /dev/null @@ -1,7 +0,0 @@ -# coding:utf-8 -""" -@author: 井松 -@contact: 529548204@qq.com -@file: temple.py -@time: 2021/9/14 16:44 -""" diff --git a/test_suite/page/saasApp/third_pages.py b/test_suite/page/saasApp/third_pages.py deleted file mode 100644 index 8d44569..0000000 --- a/test_suite/page/saasApp/third_pages.py +++ /dev/null @@ -1,37 +0,0 @@ -# coding:utf-8 -from test_suite.page import * - -urlData = ini_yaml("thirdUrl.yml") - - -def weblogin(casedata): - data = urlData["weblogin"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) - return res, restime - - -def webalarm(casedata): - data = urlData["webalarm"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) - return res, restime - - -def webFees(casedata): - data = urlData["webFees"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) - return {casedata["info"]:res}, restime - - -def webPower(casedata): - data = urlData["webPower"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) - return {casedata["info"]:res}, restime - diff --git a/test_suite/page/saasApp/third_pages_1.py b/test_suite/page/saasApp/third_pages_1.py deleted file mode 100644 index e45e0cf..0000000 --- a/test_suite/page/saasApp/third_pages_1.py +++ /dev/null @@ -1,37 +0,0 @@ -# coding:utf-8 -from test_suite.page.saasApp import * - -urlData = ini_yaml("thirdUrl.yml") - - -def weblogin(casedata): - data = urlData["weblogin"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) - return res, restime - - -def webalarm(casedata): - data = urlData["webalarm"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) - return res, restime - - -def webFees(casedata): - data = urlData["webFees"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) - return {casedata["info"]:res}, restime - - -def webPower(casedata): - data = urlData["webPower"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"]) - return {casedata["info"]:res}, restime - diff --git a/test_suite/page/saasWeb/saasApp_pages_1.py b/test_suite/page/saasWeb/saasApp_pages_1.py deleted file mode 100644 index 153f08b..0000000 --- a/test_suite/page/saasWeb/saasApp_pages_1.py +++ /dev/null @@ -1,45 +0,0 @@ -# coding:utf-8 -from test_suite.page.saasWeb import * - -urlData = ini_yaml("urlData.yml") - - -def login(casedata): - data = urlData["login"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def firmwareList(casedata): - data = urlData["firmwareList"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def firmwareAdd(casedata): - data = urlData["firmwareAdd"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def firmwareDel(casedata): - data = urlData["firmwareDel"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def firmwareDetail(casedata): - data = urlData["firmwareDetail"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - diff --git a/test_suite/page/saasWeb/saasWeb_pages.py b/test_suite/page/saasWeb/saasWeb_pages.py deleted file mode 100644 index 5bcfd63..0000000 --- a/test_suite/page/saasWeb/saasWeb_pages.py +++ /dev/null @@ -1,44 +0,0 @@ -# coding:utf-8 -from test_suite.page.saasWeb import * - - - - -def login(casedata): - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"], headers=casedata["headers"], - data=casedata["data"],rel=casedata["relevance"]) - return res, restime - - -def firmwareList(casedata): - - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"], headers=casedata["headers"], - data=casedata["data"],rel=casedata["relevance"]) - return res, restime - - -def firmwareAdd(casedata): - - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"], headers=casedata["headers"], - data=casedata["data"],rel=casedata["relevance"]) - return res, restime - - -def firmwareDel(casedata): - - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"], headers=casedata["headers"], - data=casedata["data"],rel=casedata["relevance"]) - return res, restime - - -def firmwareDetail(casedata): - - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"], headers=casedata["headers"], - data=casedata["data"],rel=casedata["relevance"]) - return res, restime - diff --git a/test_suite/page/saasWeb/saasWeb_pages_1.py b/test_suite/page/saasWeb/saasWeb_pages_1.py deleted file mode 100644 index 153f08b..0000000 --- a/test_suite/page/saasWeb/saasWeb_pages_1.py +++ /dev/null @@ -1,45 +0,0 @@ -# coding:utf-8 -from test_suite.page.saasWeb import * - -urlData = ini_yaml("urlData.yml") - - -def login(casedata): - data = urlData["login"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def firmwareList(casedata): - data = urlData["firmwareList"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def firmwareAdd(casedata): - data = urlData["firmwareAdd"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def firmwareDel(casedata): - data = urlData["firmwareDel"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - - -def firmwareDetail(casedata): - data = urlData["firmwareDetail"] - logging.info("{}".format(casedata["info"])) - res, restime = apisend(host=data["host"], address=data["address"], method=data["method"], headers=casedata["headers"], - data=casedata["data"],rel=data["relevance"]) - return res, restime - diff --git a/test_suite/page/saasWeb/__init__.py b/test_suite/testcase/fengling/__init__.py similarity index 66% rename from test_suite/page/saasWeb/__init__.py rename to test_suite/testcase/fengling/__init__.py index d19dd19..910bcd2 100644 --- a/test_suite/page/saasWeb/__init__.py +++ b/test_suite/testcase/fengling/__init__.py @@ -1,11 +1,4 @@ # coding:utf-8 -""" -@author: 井松 -@contact: 529548204@qq.com -@file: __init__.py.py -@time: 2021/12/3 13:52 -""" - import logging @@ -16,7 +9,7 @@ from common.checkResult import asserting from scripts.log import Log from scripts.readYamlFile import ini_yaml from common.basePage import apisend -from test_suite.page.saasWeb import saasWeb_pages + Log() __all__ = [ @@ -27,5 +20,4 @@ __all__ = [ 'logging', 'allure', 'apisend', - 'saasWeb_pages', ] \ No newline at end of file diff --git a/test_suite/testcase/fengling/conftest.py b/test_suite/testcase/fengling/conftest.py new file mode 100644 index 0000000..ec838ac --- /dev/null +++ b/test_suite/testcase/fengling/conftest.py @@ -0,0 +1,2 @@ +# coding:utf-8 +from test_suite.testcase.fengling import * \ No newline at end of file diff --git a/test_suite/testcase/saasWeb/conftest.py b/test_suite/testcase/saasWeb/conftest.py index 6c41f42..85c1bf9 100644 --- a/test_suite/testcase/saasWeb/conftest.py +++ b/test_suite/testcase/saasWeb/conftest.py @@ -5,9 +5,9 @@ @file: conftest.py @time: 2021/12/3 14:25 """ -from test_suite.page.saasWeb.saasWeb_pages import * +from test_suite.testcase.saasWeb import * -paramData = ini_yaml("loginData.yml")["login"]["case"][0] +paramData = ini_yaml("login.yml")["login"]["case"][0] @pytest.fixture(scope="module") @@ -18,4 +18,4 @@ def setup_Login(): data=paramData["data"],rel=paramData["relevance"]) logging.info("前置请求结束") - return res + return "JWT " + res["data"]["token"] diff --git a/test_suite/testcase/saasWeb/test_firmware.py b/test_suite/testcase/saasWeb/test_firmware.py index 1153e68..644cd71 100644 --- a/test_suite/testcase/saasWeb/test_firmware.py +++ b/test_suite/testcase/saasWeb/test_firmware.py @@ -1,58 +1,51 @@ -# coding:utf-8 -""" -@author: 井松 -@contact: 529548204@qq.com -@file: test_firmware.py -@time: 2021/12/3 14:30 -""" -import pytest - from test_suite.testcase.saasWeb import * + +paramData = ini_yaml("firmware.yml") -paramData = ini_yaml("firmwareData.yml") - - -class Test_firmwareList(object): - @allure.story("Test_firmwareList") - @pytest.mark.parametrize('casedata', paramData["firmwareList"], ids=[i["info"] for i in paramData["firmwareList"]]) +class Test_firmware(object): + @allure.story("Test_firmware") + @pytest.mark.parametrize('casedata', paramData["firmwareList"]["case"], + ids=[i["info"] for i in paramData["firmwareList"]["case"]]) @pytest.mark.flaky(reruns=1, reruns_delay=1) - @pytest.mark.run(order=2) + @pytest.mark.run(order=1) def test_firmwareList(self, casedata, setup_Login): - casedata["headers"]["Authorization"] = "JWT " + setup_Login["data"]["token"] + casedata["headers"]["Authorization"] = setup_Login res, restime = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"], headers=casedata["headers"], data=casedata["data"], rel=casedata["relevance"]) asserting(hope_res=casedata["assert"], real_res=res, re_time=restime) - @allure.story("Test_firmwareAdd") - @pytest.mark.parametrize('casedata', paramData["firmwareAdd"], ids=[i["info"] for i in paramData["firmwareAdd"]]) + @allure.story("Test_firmware") + @pytest.mark.parametrize('casedata', paramData["firmwareAdd"]["case"], + ids=[i["info"] for i in paramData["firmwareAdd"]["case"]]) @pytest.mark.flaky(reruns=1, reruns_delay=1) @pytest.mark.run(order=1) def test_firmwareAdd(self, casedata, setup_Login): - casedata["headers"]["Authorization"] = "JWT " + setup_Login["data"]["token"] + casedata["headers"]["Authorization"] = setup_Login res, restime = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"], headers=casedata["headers"], data=casedata["data"], rel=casedata["relevance"]) asserting(hope_res=casedata["assert"], real_res=res, re_time=restime) - @allure.story("Test_firmwareDel") - @pytest.mark.parametrize('casedata', paramData["firmwareDel"], ids=[i["info"] for i in paramData["firmwareDel"]]) + @allure.story("Test_firmware") + @pytest.mark.parametrize('casedata', paramData["firmwareDetail"]["case"], + ids=[i["info"] for i in paramData["firmwareDetail"]["case"]]) @pytest.mark.flaky(reruns=1, reruns_delay=1) - @pytest.mark.run(order=3) - def test_firmwareDel(self, casedata, setup_Login): - casedata["headers"]["Authorization"] = "JWT " + setup_Login["data"]["token"] + @pytest.mark.run(order=2) + def test_firmwareDetail(self, casedata, setup_Login): + casedata["headers"]["Authorization"] = setup_Login res, restime = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"], headers=casedata["headers"], data=casedata["data"], rel=casedata["relevance"]) asserting(hope_res=casedata["assert"], real_res=res, re_time=restime) - @allure.story("Test_firmwareDetail") - @pytest.mark.parametrize('casedata', paramData["firmwareDetail"], - ids=[i["info"] for i in paramData["firmwareDetail"]]) + @allure.story("Test_firmware") + @pytest.mark.parametrize('casedata', paramData["firmwareDel"]["case"], + ids=[i["info"] for i in paramData["firmwareDel"]["case"]]) @pytest.mark.flaky(reruns=1, reruns_delay=1) - @pytest.mark.run(order=2) - def test_firmwareDetail(self, casedata, setup_Login): - casedata["headers"]["Authorization"] = "JWT " + setup_Login["data"]["token"] + @pytest.mark.run(order=3) + def test_firmwareDel(self, casedata, setup_Login): + casedata["headers"]["Authorization"] = setup_Login res, restime = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"], headers=casedata["headers"], data=casedata["data"], rel=casedata["relevance"]) diff --git a/test_suite/testcase/saasWeb/test_login.py b/test_suite/testcase/saasWeb/test_login.py index fddc5b5..303bbcb 100644 --- a/test_suite/testcase/saasWeb/test_login.py +++ b/test_suite/testcase/saasWeb/test_login.py @@ -1,15 +1,6 @@ -# coding:utf-8 -""" -@author: 井松 -@contact: 529548204@qq.com -@file: test_login.py -@time: 2021/12/3 13:54 -""" - from test_suite.testcase.saasWeb import * - -paramData = ini_yaml("loginData.yml") - + +paramData = ini_yaml("login.yml") class Test_login(object): @allure.story("Test_login") -- Gitee From cb9ad738508359ef31cccee69cb3403bc5b0a5a8 Mon Sep 17 00:00:00 2001 From: jing song <529548204@qq.com> Date: Thu, 23 Dec 2021 14:45:16 +0800 Subject: [PATCH 30/30] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E6=A1=86=E6=9E=B6?= =?UTF-8?q?=E9=80=BB=E8=BE=91=20=E4=BC=98=E5=8C=96yaml=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ba245cd..944322f 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,8 @@ ``` yaml login: name: "登录" - token: false - order: 1 + token: false # 根据bool值判断此接口是否使用token + order: 1 # 执行顺序 @pytest.mark.run(order=1) case: - info: "用户名登录-成功" host: 'host' @@ -131,4 +131,5 @@ secret = 1. 新建config/config.ini文件 格式如上例子 2. 执行scripts/newProject.py 3. 在生成的test_suite/datas/名称 文件夹下增加yaml测试用例 格式如上 -4. 执行writeCase.py生成测试脚本 关于token 需要根据自己项目情况修改writeCase.py的内容 (第59行) \ No newline at end of file +4. 执行writeCase.py生成测试脚本 关于token 需要根据自己项目情况修改writeCase.py的内容 (第59行) +5. 执行/setupMain.py开始测试 \ No newline at end of file -- Gitee