├── .idea ├── .name ├── vcs.xml ├── misc.xml ├── inspectionProfiles │ └── profiles_settings.xml ├── modules.xml └── Taffy.iml ├── Util ├── OATool │ ├── __init__.py │ └── OAT.py ├── commonTool │ ├── __init__.py │ └── configUtil.py ├── locustTool │ ├── __init__.py │ ├── template │ │ ├── import_template │ │ ├── task_template │ │ ├── function_template │ │ └── locustfile_template │ └── locustUtil.py ├── redisTool │ ├── __init__.py │ └── redisUtil.py ├── securityTool │ ├── __init__.py │ └── securityUtil.py ├── hessianTool │ ├── __init__.py │ └── hessianUtil.py ├── webserviceTool │ ├── __init__.py │ └── webServiceUtil.py ├── DBTool │ ├── __init__.py │ ├── baseUtil.py │ ├── mysqlUtil.py │ └── dbUtil.py ├── checkTool │ ├── __init__.py │ ├── resultCheck.py │ └── checkUtil.py ├── httpTool │ ├── __init__.py │ ├── httpParam.py │ └── httpUtil.py ├── seleniumTool │ ├── __init__.py │ ├── loginPageUtil.py │ └── basePageUtil.py └── __init__.py ├── requirements.txt ├── config ├── selenium.yml ├── test.yml └── locust.yml ├── Tests ├── run_locust.py ├── test_Selenium.py ├── locustfile.py └── test_demo.py ├── setup.py └── README.md /.idea/.name: -------------------------------------------------------------------------------- 1 | Taffy -------------------------------------------------------------------------------- /Util/OATool/__init__.py: -------------------------------------------------------------------------------- 1 | from .OAT import * 2 | -------------------------------------------------------------------------------- /Util/commonTool/__init__.py: -------------------------------------------------------------------------------- 1 | from .configUtil import * -------------------------------------------------------------------------------- /Util/locustTool/__init__.py: -------------------------------------------------------------------------------- 1 | from .locustUtil import * -------------------------------------------------------------------------------- /Util/redisTool/__init__.py: -------------------------------------------------------------------------------- 1 | from .redisUtil import * 2 | -------------------------------------------------------------------------------- /Util/securityTool/__init__.py: -------------------------------------------------------------------------------- 1 | from .securityUtil import * -------------------------------------------------------------------------------- /Util/hessianTool/__init__.py: -------------------------------------------------------------------------------- 1 | from .hessianUtil import * 2 | -------------------------------------------------------------------------------- /Util/webserviceTool/__init__.py: -------------------------------------------------------------------------------- 1 | from .webServiceUtil import * -------------------------------------------------------------------------------- /Util/locustTool/template/import_template: -------------------------------------------------------------------------------- 1 | from $file import $class -------------------------------------------------------------------------------- /Util/DBTool/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from .dbUtil import * 3 | -------------------------------------------------------------------------------- /Util/checkTool/__init__.py: -------------------------------------------------------------------------------- 1 | from .checkUtil import * 2 | from .resultCheck import * 3 | -------------------------------------------------------------------------------- /Util/httpTool/__init__.py: -------------------------------------------------------------------------------- 1 | from .httpParam import * 2 | from .httpUtil import * 3 | -------------------------------------------------------------------------------- /Util/seleniumTool/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from .basePageUtil import * 3 | from .loginPageUtil import * 4 | -------------------------------------------------------------------------------- /Util/locustTool/template/task_template: -------------------------------------------------------------------------------- 1 | @task($weight) 2 | def $class_$function(self): 3 | self.client.$class_$function() -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | nose 2 | redis 3 | redis_py_cluster 4 | requests 5 | suds-jurko 6 | pycryptodome 7 | DBUtils 8 | six 9 | selenium 10 | locust 11 | locustio 12 | PyYAML 13 | python-hessian 14 | PyMySQL -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Util/__init__.py: -------------------------------------------------------------------------------- 1 | from .checkTool import * 2 | from .commonTool import * 3 | from .DBTool import * 4 | from .hessianTool import * 5 | from .httpTool import * 6 | from .OATool import * 7 | from .redisTool import * 8 | from .securityTool import * 9 | from .seleniumTool import * 10 | from .webserviceTool import * 11 | from .locustTool import * 12 | -------------------------------------------------------------------------------- /config/selenium.yml: -------------------------------------------------------------------------------- 1 | --- 2 | site: 3 | url: http://mail.126.com 4 | title: 网易 5 | tips: 帐号或密码错误 6 | suffix: "@126.com" 7 | user: tafffy 8 | passwd: lovesoo1314 9 | 10 | loc: 11 | username_loc: (By.NAME, 'email') 12 | password_loc: (By.NAME, 'password') 13 | submit_loc: (By.ID, 'dologin') 14 | span_loc: (By.ID, 'nerror') 15 | dynpw_loc: (By.ID, 'lbDynPw') 16 | userid_loc: (By.ID, 'spnUid') 17 | logout_loc: (By.ID, '_mail_component_41_41') -------------------------------------------------------------------------------- /.idea/Taffy.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /config/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | Http: 3 | Host: http://www.baidu.com 4 | Port: 80 5 | Path: "/" 6 | 7 | Httpbin: 8 | Host: http://httpbin.org 9 | Port: 80 10 | Path: "/" 11 | 12 | Dubbo: 13 | Host: http://192.168.57.1 14 | Port: 10888 15 | Service: com.service.dubbo.api 16 | Interface: AccountService 17 | 18 | Mysql: 19 | Type: mysql 20 | Host: 192.168.1.1 21 | Port: 3306 22 | User: root 23 | Passwd: root 24 | Database: mysql 25 | charset: utf8 26 | 27 | Redis: 28 | Type: Redis 29 | Server: 172.31.1.1:6379:0 30 | Passwd: test 31 | 32 | Redis_Cluster: 33 | Type: Redis_Cluster 34 | Server: 192.168.57.1:7379,192.168.57.1:7380,192.168.57.1:7381 -------------------------------------------------------------------------------- /Util/locustTool/template/function_template: -------------------------------------------------------------------------------- 1 | def $class_$function(self): 2 | start_time = time.time() 3 | try: 4 | $class().$function() 5 | request_type = $class.__name__ 6 | name = $class().$function.__name__ 7 | except Exception as e: 8 | total_time = int((time.time() - start_time) * 1000) 9 | events.request_failure.fire(request_type=request_type, name=name, response_time=total_time, exception=e) 10 | else: 11 | total_time = int((time.time() - start_time) * 1000) 12 | events.request_success.fire(request_type=request_type, name=name, response_time=total_time, response_length=0) -------------------------------------------------------------------------------- /Util/locustTool/template/locustfile_template: -------------------------------------------------------------------------------- 1 | import time 2 | from locust import Locust, TaskSet, events, task 3 | import sys 4 | import os 5 | sys.path.append(os.path.abspath(os.path.dirname(__file__))) 6 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 7 | $import 8 | 9 | 10 | class HttpClient(object): 11 | def __init__(self): 12 | pass 13 | 14 | $function 15 | 16 | 17 | class HttpLocust(Locust): 18 | def __init__(self, *args, **kwargs): 19 | super(HttpLocust, self).__init__(*args, **kwargs) 20 | self.client = HttpClient() 21 | 22 | 23 | class ApiUser(HttpLocust): 24 | min_wait = $min_wait 25 | max_wait = $max_wait 26 | 27 | class task_set(TaskSet): 28 | $task 29 | -------------------------------------------------------------------------------- /Util/webserviceTool/webServiceUtil.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from suds.client import Client 3 | from suds.xsd.doctor import Import, ImportDoctor 4 | 5 | 6 | class WebServiceUtil(object): 7 | """webservices调用封装类""" 8 | 9 | def __init__(self): 10 | pass 11 | 12 | @classmethod 13 | def Invoke(cls, url, method, *args): 14 | """ 15 | 接口webservice方法,返回结果 16 | :param url: webservice访问地址 17 | :param method: 方法名 18 | :param args: 调用接口使用的参数 19 | """ 20 | try: 21 | print('Request: \t', args) 22 | imp = Import('http://www.w3.org/2001/XMLSchema', location='http://www.w3.org/2001/XMLSchema.xsd') 23 | imp.filter.add('http://' + url.split('/')[2]) 24 | client = Client(url, doctor=ImportDoctor(imp)) 25 | response = getattr(client.service, method)(*args) 26 | print('Response: \t', response) 27 | return response 28 | 29 | except Exception as e: 30 | print(e) 31 | -------------------------------------------------------------------------------- /Util/DBTool/baseUtil.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import sys 3 | import os 4 | import re 5 | import copy 6 | 7 | 8 | class BaseUtil(object): 9 | def __init__(self, connection): 10 | """Constructor""" 11 | self.connection = connection 12 | self.cursor = None 13 | 14 | def execute(self, sql, params=()): 15 | """""" 16 | if self.isQuery(sql): 17 | return self.executeQuery(sql, params) 18 | else: 19 | return self.executeNonQuery(sql, params) 20 | 21 | def close(self): 22 | if self.cursor: 23 | self.cursor.close() 24 | if self.connection: 25 | self.connection.close() 26 | 27 | def isQuery(self, sql): 28 | copy_sql = copy.copy(sql) 29 | if re.search('select ', copy_sql.lstrip(), re.IGNORECASE): 30 | return True 31 | else: 32 | return False 33 | 34 | def executeQuery(self, sql, params=()): 35 | raise NotImplementedError 36 | 37 | def executeNonQuery(self, sql, params=()): 38 | return NotImplementedError 39 | -------------------------------------------------------------------------------- /config/locust.yml: -------------------------------------------------------------------------------- 1 | --- 2 | #mode 运行模式(默认为0) 0:单例模式; 1:分布式 3 | #no-web 是否以no-web模式运行(默认为0) 0:否; 1:是 4 | #min_wait/max_wait 任务执行之间的最小、最大等待时间(默认为10/1000ms) 5 | 6 | #只有mode为1时,params中如下参数才有效:slaves_num,master_port 7 | #slaves_num slaves数目(默认为当前机器cpu核数) 8 | #master_port master绑定端口号(默认5557) 9 | 10 | #只有no-web为0时,params中如下参数才有效:port 11 | #port web端口号,默认8089 12 | 13 | #只有no-web为1时,params中如下参数才有效:csv,c,r,run_time 14 | #csv 运行结果文件名 15 | #c 并发用户数 16 | #r 每秒请求数 17 | #run_time 运行时间 18 | mode: 1 19 | no_web: 0 20 | min_wait: 10 21 | max_wait: 100 22 | params: 23 | slaves_num: 4 24 | master_port: 5557 25 | port: 8089 26 | csv: locust 27 | c: 10 28 | r: 10 29 | run_time: 5m 30 | #task 性能测试任务 31 | task: 32 | #file 测试文件名,支持相对路径如test_xxx/text_xxx_file.py 33 | #class 测试类 34 | #function 测试方法 35 | #weight 任务选择的概率权重(默认1) 36 | - file: test_demo.py 37 | class: test_demo 38 | function: test_httpbin_get 39 | weight: 2 40 | - file: test_demo.py 41 | class: test_demo 42 | function: test_httpbin_post 43 | weight: 1 44 | - file: test_demo.py 45 | class: test_demo 46 | function: test_webservice 47 | weight: 1 -------------------------------------------------------------------------------- /Util/DBTool/mysqlUtil.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import sys 3 | import os 4 | import pymysql 5 | from .baseUtil import * 6 | 7 | 8 | class MysqlUtil(BaseUtil): 9 | """数据库工具类,提供连接池以及执行sql语句的方法""" 10 | 11 | def __init__(self, connection): 12 | """Constructor""" 13 | super(MysqlUtil, self).__init__(connection) 14 | self.cursor = self.connection.cursor() 15 | 16 | def executeQuery(self, sql, params=()): 17 | data = [] 18 | result = self.cursor.execute(sql, params) 19 | data = self.cursor.fetchall() 20 | return data 21 | 22 | def executeNonQuery(self, sql, params=()): 23 | try: 24 | result = 0 25 | result = self.cursor.execute(sql, params) 26 | self.connection.commit() 27 | 28 | except Exception as e: 29 | if self.connection: 30 | self.connection.rollback() 31 | print(e) 32 | finally: 33 | if result: 34 | lastid = self.cursor.lastrowid 35 | if lastid: 36 | result = lastid 37 | return result 38 | -------------------------------------------------------------------------------- /Util/httpTool/httpParam.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import json 3 | from ..commonTool import * 4 | 5 | 6 | class HttpParam(object): 7 | """接口参数构造类,一般分为base,param,data等几部分,格式为json或xml""" 8 | 9 | def __init__(self, **kwargs): 10 | """构造函数 11 | :param 为key=value的形式 12 | """ 13 | self.data = { 14 | "ie": "utf-8", 15 | "tn": "baidu", 16 | "wd": "lovesoo" 17 | } 18 | self.data.update(kwargs) 19 | 20 | def GetJson(self): 21 | """ 22 | 返回json字符串 23 | """ 24 | return json.dumps(self.data, ensure_ascii=False, indent=4) 25 | 26 | def GetDict(self): 27 | """ 28 | 返回dict对象 29 | """ 30 | return self.data 31 | 32 | 33 | class HttpbinParam(object): 34 | """接口参数构造类,一般分为base,param,data等几部分,格式为json或xml""" 35 | 36 | def __init__(self, **kwargs): 37 | """构造函数. 38 | :param 为key=value的形式 39 | """ 40 | self.data = {} 41 | self.data.update(kwargs) 42 | 43 | def GetJson(self): 44 | """ 45 | 返回json字符串 46 | """ 47 | return json.dumps(self.data, ensure_ascii=False, indent=4) 48 | 49 | def GetDict(self): 50 | """ 51 | 返回dict对象 52 | """ 53 | return self.data 54 | -------------------------------------------------------------------------------- /Tests/run_locust.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import sys 4 | import os 5 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 6 | from Util import * 7 | import multiprocessing 8 | 9 | """ 10 | Please Visit http://localhost:8089/ 11 | """ 12 | 13 | 14 | def start_slave(locust_file, master_port): 15 | print('start slave pid:\t{0}'.format(os.getpid())) 16 | os.system('locust -f {0} --slave --master-port {1}'.format(locust_file, master_port)) 17 | 18 | 19 | if __name__ == '__main__': 20 | multiprocessing.freeze_support() 21 | LU = locustUtil() 22 | 23 | # 读取配置config/locust.yml,生成locustfile.py 24 | locust_file = LU.genLocustfile() 25 | 26 | # 生成locust运行命令 27 | locust_command = LU.getRunCommand() 28 | 29 | # 运行locust 30 | if 'master' in locust_command: 31 | # 分布式 32 | num = LU.cases['params'].get('slaves_num', multiprocessing.cpu_count()) 33 | master_port = LU.cases['params'].get('master_port', 5557) 34 | record = [] 35 | for i in range(num): 36 | process = multiprocessing.Process(target=start_slave, args=(locust_file, master_port)) 37 | process.start() 38 | record.append(process) 39 | 40 | print('start master pid:\t{0}'.format(os.getpid())) 41 | cmd = 'locust -f {0} {1}'.format(locust_file, locust_command) 42 | print('cmd:\t{0}'.format(cmd)) 43 | os.system(cmd) 44 | 45 | else: 46 | # 单例模式 47 | cmd = 'locust -f {0} {1}'.format(locust_file, locust_command) 48 | print('cmd:\t{0}'.format(cmd)) 49 | os.system(cmd) 50 | -------------------------------------------------------------------------------- /Util/commonTool/configUtil.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import os 3 | import sys 4 | import io 5 | import inspect 6 | import yaml 7 | 8 | 9 | def GetPath(module=None): 10 | """获取当前模块所在路径""" 11 | if not module: 12 | module = GetPath 13 | cur_module = inspect.getmodule(module) 14 | path = os.path.dirname(cur_module.__file__) 15 | return path 16 | 17 | 18 | ROOT = os.path.abspath(os.path.join(GetPath(sys.modules['Util']), os.path.pardir)) 19 | # ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir)) 20 | 21 | 22 | class ConfigUtil(object): 23 | 24 | @classmethod 25 | def getall(cls, path='/config/test.yml'): 26 | """获取配置文件中的配置,返回string""" 27 | filepath = ROOT + path 28 | return yaml.load(io.open(filepath, 'r', encoding='utf-8')) 29 | 30 | @classmethod 31 | def get(cls, section, option='', path='/config/test.yml'): 32 | """获取配置文件中的配置,返回string""" 33 | filepath = ROOT + path 34 | config = yaml.load(io.open(filepath, 'r', encoding='utf-8')) 35 | if option: 36 | result = config[section][option] 37 | else: 38 | result = config[section] 39 | return str(result) if isinstance(result, (str, int)) else result 40 | 41 | @classmethod 42 | def getint(cls, section, option='', path='/config/test.yml'): 43 | """获取配置文件中的配置,返回int""" 44 | filepath = ROOT + path 45 | config = yaml.load(io.open(filepath, 'r', encoding='utf-8')) 46 | if option: 47 | return int(config[section][option]) 48 | else: 49 | return int(config[section]) 50 | 51 | 52 | if __name__ == '__main__': 53 | print(ConfigUtil.getall('/config/selenium.yml')) 54 | -------------------------------------------------------------------------------- /Util/checkTool/resultCheck.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from .checkUtil import * 3 | import re 4 | 5 | 6 | class resultCheck_baidu(object): 7 | @staticmethod 8 | def check_title(response, key): 9 | # 校验key是否匹配返回页面title 10 | re_title = re.compile('(.*)') # 搜索页面title正则表达式 11 | title = re.search(re_title, response).groups()[0] 12 | print('Search Result Title:%s' % title) 13 | ok_(key in title, 'Title Check Error!%s not in %s' % (key, title)) 14 | 15 | @staticmethod 16 | def check_results(response, key): 17 | # 校验key是否匹配搜索结果的名称或者URL 18 | re_name = re.compile('>(.*)') # 搜索结果name正则表达式 19 | re_url = re.compile('style="text-decoration:none;">(.*)', '').replace('', '') 25 | url = url.replace('', '').replace('', '').replace(' ', '').replace('...', '') 26 | print('Search Results Name:%s\tURL:%s' % (name, url)) 27 | if key.lower() not in (name + url).lower(): 28 | assert False, 'Search Results Check Error!%s not in %s' % (key, name + url) 29 | return True 30 | 31 | 32 | class resultCheck_httpbin(object): 33 | def __init__(self): 34 | pass 35 | 36 | @staticmethod 37 | def check_get(response, params): 38 | # 调用check_dict方法比较字典 39 | # 比较返回结果args字段是否与所传参数一致 40 | CheckUtil.check_dict(response['args'], params.GetDict()) 41 | 42 | @staticmethod 43 | def check_post(response, params): 44 | # 调用check_dict方法比较字典 45 | # 比较返回结果form字段是否与所传参数一致 46 | CheckUtil.check_dict(response['form'], params.GetDict()) 47 | -------------------------------------------------------------------------------- /Util/seleniumTool/loginPageUtil.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | """ 4 | loginPageUtil.py 5 | 页面基本操作方法,如open,input_username,input_password,click_submit 6 | """ 7 | 8 | from selenium.webdriver.common.by import By 9 | from .basePageUtil import basePage 10 | from Util import * 11 | selenium_yml = '/config/selenium.yml' 12 | 13 | 14 | class loginPage(basePage): 15 | # 继承basePage类 16 | loc = ConfigUtil.get('loc', path=selenium_yml) 17 | 18 | # 定位器,通过元素属性定位元素对象 19 | username_loc = eval(loc['username_loc']) 20 | password_loc = eval(loc['password_loc']) 21 | submit_loc = eval(loc['submit_loc']) 22 | span_loc = eval(loc['span_loc']) 23 | dynpw_loc = eval(loc['dynpw_loc']) 24 | userid_loc = eval(loc['userid_loc']) 25 | logout_loc = eval(loc['logout_loc']) 26 | # 操作 27 | # 通过继承覆盖(Overriding)方法:如果子类和父类的方法名相同,优先用子类自己的方法。 28 | # 打开网页 29 | 30 | def open(self): 31 | # 调用page中的_open打开连接 32 | self._open(self.url, self.pagetitle) 33 | # 输入用户名:调用send_keys对象,输入用户名 34 | 35 | def input_username(self, username): 36 | self.send_keys(self.username_loc, username, clear_first=True) 37 | # 输入密码:调用send_keys对象,输入密码 38 | 39 | def input_password(self, password): 40 | self.send_keys(self.password_loc, password, clear_first=True) 41 | # 点击登录:调用send_keys对象,点击登录 42 | 43 | def click_submit(self): 44 | self.find_element(self.submit_loc).click() 45 | # 用户名或密码不合理是Tip框内容展示 46 | 47 | def show_span(self): 48 | try: 49 | tips = self.find_element(self.span_loc) 50 | if tips: 51 | return tips.text 52 | else: 53 | return None 54 | except Exception as e: 55 | print(e) 56 | # 切换登录模式为动态密码登录(IE下有效) 57 | 58 | def swich_DynPw(self): 59 | self.find_element(self.dynpw_loc).click() 60 | # 登录成功页面中的用户ID查找 61 | 62 | def show_userid(self): 63 | return self.find_element(self.userid_loc).text 64 | # 登陆退出:调用send_keys对象,点击退出 65 | 66 | def click_logout(self): 67 | self.find_element(self.logout_loc).click() 68 | -------------------------------------------------------------------------------- /Tests/test_Selenium.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import sys 3 | import os 4 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 5 | from Util import * 6 | from selenium import webdriver 7 | import time 8 | selenium_yml = '/config/selenium.yml' 9 | 10 | 11 | class test_login(object): 12 | def __init__(self): 13 | pass 14 | 15 | @classmethod 16 | def setUpClass(cls): 17 | site = ConfigUtil.get('site', path=selenium_yml) 18 | cls.driver = webdriver.Chrome() 19 | cls.driver.implicitly_wait(30) 20 | cls.url = site['url'] 21 | cls.title = site['title'] 22 | cls.tips = site['tips'] 23 | cls.suffix = site['suffix'] 24 | cls.user = site['user'] 25 | cls.passwd = site['passwd'] 26 | 27 | @classmethod 28 | def tearDownClass(cls): 29 | cls.driver.quit() 30 | 31 | @staticmethod 32 | def login(self, user, passwd, tips=''): 33 | # 定义通用login方法 34 | print('Login user: %s ,passwd: %s' % (user, passwd)) 35 | # 声明loginPage对象 36 | login_page = loginPage(self.driver, self.url, self.title) 37 | # 打开页面 38 | login_page.open() 39 | time.sleep(1) 40 | # 切换到登录框Frame 41 | login_page.switch_frame('x-URS-iframe') 42 | time.sleep(1) 43 | # 输入用户名 44 | login_page.input_username(user) 45 | # 输入密码 46 | login_page.input_password(passwd) 47 | # 点击登录 48 | login_page.click_submit() 49 | time.sleep(1) 50 | if tips: 51 | # 登陆失败校验提示信息 52 | fail_tips = login_page.show_span() 53 | print('Login Failed: %s' % fail_tips) 54 | assert tips == fail_tips, 'Check Login Error Tips Failed!' 55 | else: 56 | # 登陆成功校验UserID 57 | login_userID = login_page.show_userid() 58 | # 点击退出 59 | login_page.click_logout() 60 | time.sleep(1) 61 | print('Login UserID: %s' % login_userID) 62 | assert user + self.suffix == login_userID, 'Check UserID Failed!' 63 | 64 | def test_BVT(self): 65 | # 测试用例:用户登陆成功 66 | test_login.login(self, self.user, self.passwd) 67 | 68 | def test_Err_User(self): 69 | # 测试用例:用户名错误,登陆失败 70 | test_login.login(self, 'adcedfg', self.passwd, self.tips) 71 | 72 | def test_Err_Passwd(self): 73 | # 测试用例:密码错误,登陆失败 74 | test_login.login(self, self.user, '123456', self.tips) 75 | -------------------------------------------------------------------------------- /Util/DBTool/dbUtil.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import sys 3 | import os 4 | import pymysql 5 | import pymssql 6 | from DBUtils import PooledDB 7 | from .mysqlUtil import * 8 | from ..commonTool import * 9 | 10 | 11 | class DBUtil(object): 12 | """数据库工具类,提供连接池以及执行sql语句的方法,目前支持mysql及sqlserver""" 13 | pools = {} 14 | 15 | def __init__(self): 16 | pass 17 | 18 | @classmethod 19 | def getCon(cls, confSection, confFile='/config/test.yml'): 20 | """创建连接池,并从池中返回对应的数据库处理工具类 21 | :param confSection: 配置名 22 | :type confSection: string 23 | :param database: 数据库名 24 | :type database: string 25 | """ 26 | database = ConfigUtil.get(confSection, 'Database', confFile) 27 | key = confSection + ':' + database 28 | clz = '' 29 | if key not in cls.pools: 30 | dbType = ConfigUtil.get(confSection, 'Type', confFile) 31 | mincached = 10 32 | maxcached = 20 33 | maxshared = 15 34 | if dbType == 'mysql': 35 | conf = ConfigUtil.get(confSection, path=confFile) 36 | host = conf.get('Host') 37 | port = conf.get('Port') 38 | user = conf.get('User') 39 | pwd = conf.get('Passwd') 40 | charset = conf.get('charset', 'utf8') 41 | 42 | dbParam = {'host': host, 'port': port, 'user': user, 'passwd': pwd, 'db': database, 'charset': charset, 'cursorclass': pymysql.cursors.DictCursor} 43 | pool = PooledDB.PooledDB(pymysql, mincached, maxcached, maxshared, **dbParam) 44 | clz = MysqlUtil 45 | cls.pools[key] = (pool, clz) 46 | else: 47 | raise NotImplementedError 48 | 49 | databasePool = cls.pools[key][0] 50 | clz = cls.pools[key][1] 51 | 52 | return clz(databasePool.connection()) 53 | 54 | @classmethod 55 | def execute(cls, sql, params=(), confSection='Mysql', confFile='/config/test.yml'): 56 | """执行mysql语句,支持动态语法 57 | :param sql: mysql语句,动态语法时包含占位符%s 58 | :type sql: string 59 | :param params: 如果为动态语句,为动态参数的数组,数组长度与sql中的占位符个数一致 60 | :type params: list 61 | :param database: 数据库名 62 | :type database: string 63 | :param confSection: 配置名,根据配置名去配置文件读取相应的配置 64 | :type confSection: string 65 | :param confSection: 配置文件 66 | :type confSection: string 67 | """ 68 | try: 69 | data = [] 70 | instance = cls.getCon(confSection, confFile) 71 | data = instance.execute(sql, params) 72 | except Exception as e: 73 | print(e) 74 | finally: 75 | instance.close() 76 | return data 77 | -------------------------------------------------------------------------------- /Util/httpTool/httpUtil.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import json 3 | import requests 4 | from ..commonTool import * 5 | 6 | 7 | class HttpUtil(object): 8 | '''基础调用类,包括基本的http method''' 9 | @classmethod 10 | def get(cls, name, confSection='Http', confFile='/config/test.yml', **kwargs): 11 | """http get method 12 | :param params: 接口参数 13 | :type params: dict 14 | :param name: 接口名 15 | :type name: string 16 | :param confSection: 配置名,根据配置名去配置文件读取相应的配置 17 | :type confSection: string 18 | """ 19 | host = ConfigUtil.get(confSection, 'Host', confFile) 20 | port = ConfigUtil.get(confSection, 'Port', confFile) 21 | path = ConfigUtil.get(confSection, 'Path', confFile) 22 | url = host + ':' + port + path + name 23 | response = requests.get(url, **kwargs) 24 | response.raise_for_status() 25 | return response 26 | 27 | @classmethod 28 | def post(cls, name, confSection='Http', confFile='/config/test.yml', **kwargs): 29 | """http post methode 30 | :param params: 接口queryString 31 | :type params: dict 32 | :param body: 接口 post body 33 | :type body: string 34 | :param name: 接口名 35 | :type name: string 36 | :param confSection: 配置名,根据配置名去配置文件读取相应的配置 37 | :type confSection: string 38 | """ 39 | host = ConfigUtil.get(confSection, 'Host', confFile) 40 | port = ConfigUtil.get(confSection, 'Port', confFile) 41 | path = ConfigUtil.get(confSection, 'Path', confFile) 42 | url = host + ':' + port + path + name 43 | response = requests.post(url, **kwargs) 44 | response.raise_for_status() 45 | return response 46 | 47 | 48 | class BaiduHttpUtil(object): 49 | """百度搜索调用类""" 50 | 51 | def __init__(self, ): 52 | pass 53 | 54 | @classmethod 55 | def get(cls, name, srequest): 56 | params = srequest.GetDict() 57 | print('Request:\t', json.dumps(params, ensure_ascii=False)) 58 | response = HttpUtil.get(name, params=params) 59 | print('Response:\t', response) 60 | return response.text 61 | 62 | 63 | class HttpbinUtil(object): 64 | """httpbin通用调用类""" 65 | confSection = 'Httpbin' 66 | 67 | def __init__(self, ): 68 | pass 69 | 70 | @classmethod 71 | def get(cls, name, srequest): 72 | params = srequest.GetDict() 73 | print('Request:\t', json.dumps(params, ensure_ascii=False)) 74 | response = HttpUtil.get(name, cls.confSection, params=params) 75 | print('Response:\t', response.text) 76 | return response.json() 77 | 78 | @classmethod 79 | def post(cls, name, srequest): 80 | params = srequest.GetDict() 81 | print('Request:\t', json.dumps(params, ensure_ascii=False)) 82 | response = HttpUtil.post(name, cls.confSection, data=params) 83 | print('Response:\t', response.text) 84 | return response.json() 85 | -------------------------------------------------------------------------------- /Util/hessianTool/hessianUtil.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | from pyhessian.client import HessianProxy 3 | from pyhessian import protocol 4 | import json 5 | import time 6 | import datetime 7 | from ..commonTool import * 8 | 9 | 10 | def FormatObject(obj): 11 | try: 12 | if hasattr(obj, '__dict__'): 13 | if '_Object__meta_type' in obj.__dict__: 14 | obj.__dict__.pop('_Object__meta_type') 15 | obj = obj.__dict__ 16 | for i in list(obj.keys()): 17 | if hasattr(obj[i], '__dict__') or hasattr(obj[i], 'has_key'): 18 | obj[i] = FormatObject(obj[i]) 19 | elif hasattr(obj[i], '__iter__'): 20 | obj[i] = [FormatObject(j) for j in obj[i]] 21 | elif hasattr(obj[i], 'time'): 22 | obj[i] = obj[i].strftime("%Y-%m-%d %H:%M:%S") 23 | return obj 24 | elif hasattr(obj, 'has_key'): 25 | for i in list(obj.keys()): 26 | if hasattr(obj[i], '__dict__') or hasattr(obj[i], 'has_key'): 27 | obj[i] = FormatObject(obj[i]) 28 | elif hasattr(obj[i], '__iter__'): 29 | obj[i] = [FormatObject(j) for j in obj[i]] 30 | elif hasattr(obj[i], 'time'): 31 | obj[i] = obj[i].strftime("%Y-%m-%d %H:%M:%S") 32 | return obj 33 | return obj 34 | except Exception as e: 35 | print(e) 36 | 37 | 38 | def benchmark(func): 39 | import time 40 | 41 | def wrapper(*args, **kwargs): 42 | t = time.clock() 43 | res = func(*args, **kwargs) 44 | print("Cost:\t%ss\r\n" % (time.clock() - t)) 45 | return res 46 | return wrapper 47 | 48 | 49 | class HessianUtil(object): 50 | def __init__(self): 51 | pass 52 | 53 | @classmethod 54 | @benchmark 55 | def Invoke(cls, method, *req, **kwargs): 56 | try: 57 | def date_handler(obj): return ( 58 | obj.isoformat() 59 | if isinstance(obj, datetime.datetime) 60 | or isinstance(obj, datetime.date) 61 | else None 62 | ) 63 | 64 | confSection = kwargs.get('confSection', 'Dubbo') 65 | confFile = kwargs.get('confFile', '/config/test.yml') 66 | 67 | conf = ConfigUtil.get(confSection, path=confFile) 68 | 69 | host = conf.get('Host') 70 | port = conf.get('Port') 71 | service = conf.get('Service') 72 | interface = conf.get('Interface') 73 | 74 | url = host + ':' + str(port) + '/' + service + '.' + interface 75 | 76 | print('\r\nInvoke Hessian Interface:\r\nURL:\t', url) 77 | print('Method:\t', method) 78 | 79 | res = FormatObject(getattr(HessianProxy(url, timeout=60), method)(*req)) 80 | 81 | print('Req:\t', [FormatObject(i) for i in req]) 82 | print('Res:\t', json.dumps(res, default=date_handler, ensure_ascii=False)) 83 | 84 | return res 85 | except Exception as e: 86 | print(e) 87 | -------------------------------------------------------------------------------- /Tests/locustfile.py: -------------------------------------------------------------------------------- 1 | import time 2 | from locust import Locust, TaskSet, events, task 3 | import sys 4 | import os 5 | sys.path.append(os.path.abspath(os.path.dirname(__file__))) 6 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 7 | from test_demo import test_demo 8 | 9 | 10 | class HttpClient(object): 11 | def __init__(self): 12 | pass 13 | 14 | def test_demo_test_httpbin_get(self): 15 | start_time = time.time() 16 | try: 17 | test_demo().test_httpbin_get() 18 | request_type = test_demo.__name__ 19 | name = test_demo().test_httpbin_get.__name__ 20 | except Exception as e: 21 | total_time = int((time.time() - start_time) * 1000) 22 | events.request_failure.fire(request_type=request_type, name=name, response_time=total_time, exception=e) 23 | else: 24 | total_time = int((time.time() - start_time) * 1000) 25 | events.request_success.fire(request_type=request_type, name=name, response_time=total_time, response_length=0) 26 | 27 | def test_demo_test_httpbin_post(self): 28 | start_time = time.time() 29 | try: 30 | test_demo().test_httpbin_post() 31 | request_type = test_demo.__name__ 32 | name = test_demo().test_httpbin_post.__name__ 33 | except Exception as e: 34 | total_time = int((time.time() - start_time) * 1000) 35 | events.request_failure.fire(request_type=request_type, name=name, response_time=total_time, exception=e) 36 | else: 37 | total_time = int((time.time() - start_time) * 1000) 38 | events.request_success.fire(request_type=request_type, name=name, response_time=total_time, response_length=0) 39 | 40 | def test_demo_test_webservice(self): 41 | start_time = time.time() 42 | try: 43 | test_demo().test_webservice() 44 | request_type = test_demo.__name__ 45 | name = test_demo().test_webservice.__name__ 46 | except Exception as e: 47 | total_time = int((time.time() - start_time) * 1000) 48 | events.request_failure.fire(request_type=request_type, name=name, response_time=total_time, exception=e) 49 | else: 50 | total_time = int((time.time() - start_time) * 1000) 51 | events.request_success.fire(request_type=request_type, name=name, response_time=total_time, response_length=0) 52 | 53 | 54 | class HttpLocust(Locust): 55 | def __init__(self, *args, **kwargs): 56 | super(HttpLocust, self).__init__(*args, **kwargs) 57 | self.client = HttpClient() 58 | 59 | 60 | class ApiUser(HttpLocust): 61 | min_wait = 10 62 | max_wait = 100 63 | 64 | class task_set(TaskSet): 65 | @task(1) 66 | def test_demo_test_webservice(self): 67 | self.client.test_demo_test_webservice() 68 | 69 | @task(1) 70 | def test_demo_test_httpbin_post(self): 71 | self.client.test_demo_test_httpbin_post() 72 | 73 | @task(2) 74 | def test_demo_test_httpbin_get(self): 75 | self.client.test_demo_test_httpbin_get() 76 | -------------------------------------------------------------------------------- /Util/redisTool/redisUtil.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from ..commonTool import * 3 | import redis 4 | from rediscluster import StrictRedisCluster 5 | from rediscluster.connection import ClusterConnectionPool 6 | 7 | 8 | class RedisUtil(object): 9 | '''redis方法封装,支持redis及redis cluster''' 10 | pools = {} 11 | 12 | def __init__(self): 13 | pass 14 | 15 | @classmethod 16 | def getCon(cls, confSection, confFile='/config/test.yml'): 17 | """从redis的连接池中获取一个redis连接,如果没有则创建一个连接池 18 | :param confSection: 配置的section名 19 | :type confSection: string 20 | """ 21 | try: 22 | key = confSection 23 | type = ConfigUtil.get(confSection, 'Type', confFile) 24 | if key not in cls.pools: 25 | server = ConfigUtil.get(confSection, 'Server', confFile) 26 | if type == 'Redis': 27 | host, port, db = server.split(':') 28 | passWd = ConfigUtil.get(confSection, 'Passwd', confFile) 29 | pool = redis.ConnectionPool(host=host, port=port, db=db, password=passWd) 30 | elif type == 'Redis_Cluster': 31 | startup_nodes = [{"host": s.split(':')[0], "port": s.split(':')[1]} for s in server.split(',')] 32 | pool = ClusterConnectionPool(startup_nodes=startup_nodes) 33 | cls.pools[key] = pool 34 | else: 35 | pass 36 | pool = cls.pools[key] 37 | if type == 'Redis': 38 | r = redis.Redis(connection_pool=pool) 39 | elif type == 'Redis_Cluster': 40 | r = StrictRedisCluster(connection_pool=pool, decode_responses=True) 41 | return r 42 | except Exception as e: 43 | print(e) 44 | 45 | @classmethod 46 | def execute(cls, command, key, *args, **kwargs): 47 | """执行redis命令 48 | :param command: 使用的redis命令 49 | :type command: string 50 | :param key: 执行命令的redis的key,需要替换的参数用%s表示 51 | :type key: string 52 | :param *args: 任意传入参数,开头的数值会与key中的%s匹配 53 | :type *args: 直接输入值 54 | :param **kwargs: 任意传入参数,如果包含confSection,会作为需要连接的redis地址,需要在config中添加对应的section 55 | :type **kwargs: 直接输入值,需要写成key = value的形式 56 | """ 57 | try: 58 | # 判断是否传入confSection及confFile 59 | if "confSection" in kwargs and "confFile" in kwargs: 60 | r = cls.getCon(kwargs["confSection"], kwargs["confFile"]) 61 | del kwargs["confSection"] 62 | del kwargs["confFile"] 63 | elif "confSection" in kwargs: 64 | r = cls.getCon(kwargs["confSection"]) 65 | del kwargs["confSection"] 66 | elif "confFile" in kwargs: 67 | r = cls.getCon("Redis", kwargs["confFile"]) 68 | del kwargs["confFile"] 69 | else: 70 | r = cls.getCon("Redis") 71 | s_count = key.count("%s") 72 | if s_count != 0: 73 | key_result = key % args[0:s_count] 74 | args_new = args[s_count:] 75 | return getattr(r, command)(key_result, *args_new, **kwargs) 76 | else: 77 | return getattr(r, command)(key, *args, **kwargs) 78 | except Exception as e: 79 | print(e) 80 | -------------------------------------------------------------------------------- /Util/seleniumTool/basePageUtil.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | """ 4 | basePageUtil.py 5 | 基础类basePage,封装所有页面都公用的方法, 6 | 定义open函数,重定义find_element,switch_frame,send_keys等函数。 7 | 在初始化方法中定义驱动driver,url,pagetitle 8 | """ 9 | 10 | from selenium.webdriver.support.wait import WebDriverWait 11 | from selenium.webdriver.support import expected_conditions as EC 12 | from selenium.webdriver import ActionChains 13 | 14 | 15 | class basePage(object): 16 | """ 17 | basePage封装所有页面都公用的方法,例如driver, url ,FindElement等 18 | """ 19 | # 初始化driver,url,pagetitle等 20 | # 实例化basePage类时,最先执行的就是__init__方法,该方法的入参,其实就是basePage类的入参。 21 | # __init__方法不能有返回值,只能返回None 22 | # self只实例本身,相较于类Page而言。 23 | 24 | def __init__(self, selenium_driver, url, pagetitle='', downloadir='', pageurl=''): 25 | self.driver = selenium_driver 26 | self.url = url 27 | self.pagetitle = pagetitle 28 | self.downloadir = downloadir 29 | self.pageurl = pageurl 30 | # 通过title断言进入的页面是否正确。 31 | # 使用title获取当前窗口title,检查输入的title是否在当前title中,返回比较结果(True 或 False) 32 | 33 | def on_page(self, pagetitle): 34 | return pagetitle in self.driver.title 35 | # 打开页面,并校验页面链接是否加载正确 36 | # 以单下划线_开头的方法,在使用import *时,该方法不会被导入,保证该方法为类私有的。 37 | 38 | def _open(self, url, pagetitle='', pageurl=''): 39 | # 使用get打开访问链接地址 40 | self.driver.get(url) 41 | self.driver.maximize_window() 42 | print(self.driver.title, self.driver.current_url) 43 | if pagetitle: 44 | # 使用assert进行校验,打开的窗口title是否与配置的title一致。调用on_page()方法 45 | assert self.on_page(pagetitle), "Check Page Error:\t%s" % url 46 | if pageurl: 47 | # 校验打开后的url与传入url是否一致 48 | assert pageurl == self.driver.current_url, '{0}!={1}'.format(pageurl, self.driver.current_url) 49 | # 定义open方法,调用_open()进行打开链接 50 | 51 | def open(self): 52 | self._open(self.url, self.pagetitle, self.pageurl) 53 | # 重写元素定位方法 54 | 55 | def find_element(self, loc): 56 | try: 57 | # 等待元素可见 58 | return WebDriverWait(self.driver, 5).until(EC.visibility_of_element_located(loc)) 59 | except Exception as e: 60 | elements = WebDriverWait(self.driver, 5).until(EC.visibility_of_any_elements_located(loc)) 61 | return elements[0] if elements else False 62 | # 重写元素定位方法 63 | 64 | def find_elements(self, loc): 65 | try: 66 | # 等待元素可见 67 | return WebDriverWait(self.driver, 5).until(EC.visibility_of_any_elements_located(loc)) 68 | except BaseException: 69 | print('page {0} does not have locator {1}'.format(self, loc)) 70 | # 重写switch_frame方法 71 | 72 | def switch_frame(self, loc): 73 | return self.driver.switch_to_frame(loc) 74 | # 重写switch_frame方法 75 | 76 | def switch_window(self, loc): 77 | return self.driver.switch_to_window(loc) 78 | # 定义script方法,用于执行js脚本 79 | 80 | def script(self, src): 81 | self.driver.execute_script(src) 82 | # 重写定义send_keys方法 83 | 84 | def send_keys(self, loc, vaule, clear_first=True, click_first=True): 85 | try: 86 | if click_first: 87 | self.find_element(loc).click() 88 | if clear_first: 89 | self.find_element(loc).clear() 90 | self.find_element(loc).send_keys(vaule) 91 | except AttributeError: 92 | print('%s page does not have "%s" locator' % (self, loc)) 93 | # 重写鼠标悬停方法 94 | 95 | def move_to_element(self, element='', loc=''): 96 | if loc: 97 | element = self.find_element(loc) 98 | elif not element: 99 | assert False, 'Not Found Element' 100 | ActionChains(self.driver).move_to_element(element).perform() 101 | -------------------------------------------------------------------------------- /Util/OATool/OAT.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | from itertools import groupby 3 | from collections import OrderedDict 4 | import os 5 | 6 | 7 | def dataSplit(data): 8 | ds = [] 9 | mb = [sum([k for m, k in data['mk'] if m <= 10]), sum([k for m, k in data['mk'] if m > 10])] 10 | for i in data['data']: 11 | if mb[1] == 0: 12 | ds.append([int(d) for d in i]) 13 | elif mb[0] == 0: 14 | ds.append([int(i[n * 2:(n + 1) * 2]) for n in range(mb[1])]) 15 | else: 16 | part_1 = [int(j) for j in i[:mb[0]]] 17 | part_2 = [int(i[mb[0]:][n * 2:(n + 1) * 2]) for n in range(mb[1])] 18 | ds.append(part_1 + part_2) 19 | return ds 20 | 21 | 22 | class OAT(object): 23 | def __init__(self, OAFile=os.path.split(os.path.realpath(__file__))[0] + '/data/ts723_Designs.txt'): 24 | """ 25 | 初始化解析构造正交表对象,数据来源:http://support.sas.com/techsup/technote/ts723_Designs.txt 26 | """ 27 | self.data = {} 28 | # 解析正交表文件数据 29 | with open(OAFile, ) as f: 30 | # 定义临时变量 31 | key = '' 32 | value = [] 33 | pos = 0 34 | for i in f: 35 | i = i.strip() 36 | if 'n=' in i: 37 | if key and value: 38 | self.data[key] = dict(pos=pos, 39 | n=int(key.split('n=')[1].strip()), 40 | mk=[[int(mk.split('^')[0]), int(mk.split('^')[1])] for mk in key.split('n=')[0].strip().split(' ')], 41 | data=value) 42 | key = ' '.join([k for k in i.split(' ') if k]) 43 | value = [] 44 | pos += 1 45 | elif i: 46 | value.append(i) 47 | self.data[key] = dict(pos=pos, 48 | n=int(key.split('n=')[1].strip()), 49 | mk=[[int(mk.split('^')[0]), int(mk.split('^')[1])]for mk in key.split('n=')[0].strip().split(' ')], 50 | data=value) 51 | self.data = sorted(list(self.data.items()), key=lambda i: i[1]['pos']) 52 | 53 | @staticmethod 54 | def get(self, mk): 55 | """ 56 | 传入参数:mk列表,如[(2,3)],[(5,5),(2,1)] 57 | 1. 计算m,n,k 58 | m=max(m1,m2,m3,…) 59 | k=(k1+k2+k3+…) 60 | n=k1*(m1-1)+k2*(m2-1)+…kx*x-1)+1 61 | 2. 查询正交表 62 | 这里简单处理,只返回满足>=m,n,k条件的n最小数据,未做复杂的数组包含校验 63 | """ 64 | mk = sorted(mk, key=lambda i: i[0]) 65 | m = max([i[0] for i in mk]) 66 | k = sum([i[1] for i in mk]) 67 | n = sum([i[1] * (i[0] - 1) for i in mk]) + 1 68 | query_key = ' '.join(['^'.join([str(j) for j in i]) for i in mk]) 69 | for data in self.data: 70 | # 先查询是否有完全匹配的正交表数据 71 | if query_key in data[0]: 72 | return dataSplit(data[1]) 73 | # 否则返回满足>=m,n,k条件的n最小数据 74 | elif data[1]['n'] >= n and data[1]['mk'][0][0] >= m and data[1]['mk'][0][1] >= k: 75 | return dataSplit(data[1]) 76 | # 无结果 77 | return None 78 | 79 | def genSets(self, params, mode=0, num=1): 80 | """ 81 | 传入测试参数OrderedDict,调用正交表生成测试集 82 | mode:用例裁剪模式,取值0,1 83 | 0 宽松模式,只裁剪重复测试集 84 | 1 严格模式,除裁剪重复测试集外,还裁剪含None测试集(num为允许None测试集最大数目) 85 | """ 86 | sets = [] 87 | mk = [(k, len(list(v)))for k, v in groupby(list(params.items()), key=lambda x:len(x[1]))] 88 | data = OAT.get(self, mk) 89 | for d in data: 90 | # 根据正则表结果生成测试集 91 | q = OrderedDict() 92 | for index, (k, v) in zip(d, list(params.items())): 93 | try: 94 | q[k] = v[index] 95 | except IndexError: 96 | # 参数取值超出范围时,取None 97 | q[k] = None 98 | if q not in sets: 99 | if mode == 0: 100 | sets.append(q) 101 | elif mode == 1 and (len([v for v in list(q.values()) if v is None])) <= num: 102 | # 测试集裁剪,去除重复及含None测试集 103 | sets.append(q) 104 | return sets 105 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import sys 3 | import os 4 | 5 | """ 6 | Taffy运行lib库自定义配置安装 7 | 默认全部安装,可选最小配置 -m,或选择只需 --with /不需安装 --without的模块 8 | 可自选只需/不需安装的模块列表[redis,security,db,webservice,selenium,locust,hessian] 9 | 更多帮助请查看:python setup.py --help 10 | """ 11 | 12 | 13 | def markfile(modules=[], mark=True): 14 | try: 15 | # 注释/解除注释模块 16 | if not modules: 17 | for tfile in [init_file, requirements_file]: 18 | lines = open(tfile, 'r').readlines() 19 | flen = len(lines) 20 | for i in range(flen): 21 | if '#' not in lines[i] and mark: 22 | lines[i] = lines[i].replace(lines[i], '#' + lines[i]) 23 | elif '#' in lines[i] and not mark: 24 | lines[i] = lines[i].replace(lines[i], lines[i].strip('#')) 25 | open(tfile, 'w').writelines(lines) 26 | else: 27 | if 'security' in modules: 28 | modules.append('pycryptodome') 29 | if 'db' in modules: 30 | modules.append('sql') 31 | if 'webservice' in modules: 32 | modules.append('suds-jurko') 33 | for tfile in [init_file, requirements_file]: 34 | for module in modules: 35 | lines = open(tfile, 'r').readlines() 36 | flen = len(lines) 37 | for i in range(flen): 38 | if '#' not in lines[i] and mark and module.lower() in lines[i].lower(): 39 | lines[i] = lines[i].replace(lines[i], '#' + lines[i]) 40 | elif '#' in lines[i] and not mark and module.lower() in lines[i].lower(): 41 | lines[i] = lines[i].replace(lines[i], lines[i].strip('#')) 42 | open(tfile, 'w').writelines(lines) 43 | except Exception as e: 44 | print(e) 45 | 46 | 47 | if __name__ == '__main__': 48 | requirements_file = 'requirements.txt' 49 | init_file = 'Util/__init__.py' 50 | ROOT = os.path.abspath(os.path.dirname(__file__)) 51 | requirements_file = os.path.join(ROOT, requirements_file) 52 | init_file = os.path.join(ROOT, init_file) 53 | all_modules = [ 54 | 'redis', 55 | 'security', 56 | 'db', 57 | 'webservice', 58 | 'selenium', 59 | 'locust', 60 | 'hessian'] 61 | # 默认安装所有模块 62 | markfile(mark=False) 63 | if len(sys.argv) < 2: 64 | print('Taffy setup with all modules.') 65 | elif '-m' == sys.argv[1] or 'min' in sys.argv[1]: 66 | print('Taffy setup with the minimum modules.') 67 | markfile(all_modules) 68 | elif '-w' == sys.argv[1] or 'without' in sys.argv[1]: 69 | if not sys.argv[2:]: 70 | print('Taffy setup without the modules:', sys.argv[2:]) 71 | elif all([m in all_modules for m in sys.argv[2:]]): 72 | print('Taffy setup without the modules:', sys.argv[2:]) 73 | markfile(sys.argv[2:]) 74 | else: 75 | print('{0} not all in the supported modules list {1}'.format( 76 | sys.argv[2:], all_modules)) 77 | elif '-with' == sys.argv[1] or '--with' == sys.argv[1]: 78 | if all([m in all_modules for m in sys.argv[2:]]): 79 | print('Taffy setup with the modules:', sys.argv[2:]) 80 | markfile([m for m in all_modules if m not in sys.argv[2:]]) 81 | else: 82 | print('{0} not all in the supported modules list {1}'.format( 83 | sys.argv[2:], all_modules)) 84 | elif '-h' == sys.argv[1] or 'help' in sys.argv[1]: 85 | print('Taffy setup.py help document.') 86 | print('Usage: python setup.py [options]') 87 | print('Options:') 88 | print('\t-h, --help\tshow this help message and exit') 89 | print('\t-m, --min\tsetup with the minimum modules') 90 | print( 91 | '\t-w module A [module B...], --without module A [module B...]\tsetup without the modules, eg "--without redis security db".') 92 | print('\t--with module A [module B...]\tsetup only with the modules, eg "--with redis security db".') 93 | print('\t\tSupported setup with/without modules are [redis,security,db,webservice,selenium,locust,hessian]') 94 | exit(0) 95 | else: 96 | assert False, 'Not supported option and see --help for available options.' 97 | os.system('pip install -r {0}'.format(requirements_file)) 98 | -------------------------------------------------------------------------------- /Util/checkTool/checkUtil.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from nose.tools import * 3 | import json 4 | 5 | 6 | def is_json(json_str): 7 | try: 8 | json.loads(json_str) 9 | except (ValueError, TypeError): 10 | return False 11 | return True 12 | 13 | 14 | class CheckUtil(object): 15 | @staticmethod 16 | def check(expr, msg=None): 17 | """检查表达式expr是否为True,如果为False,则抛出异常并显示消息msg 18 | :param expr: 表达式 19 | :type expr: expression 20 | :param msg: 消息 21 | :type msg: string 22 | """ 23 | ok_(expr, msg) 24 | 25 | @staticmethod 26 | def check_return(data): 27 | """检查返回值是否正确 28 | :param data: 接口返回包的result部分 29 | :type data: json对象 30 | """ 31 | eq_(data['returncode'], '0000', '%s: ' % data['returncode'] + data['returndesc']) 32 | 33 | @staticmethod 34 | def check_equal(a, b, msg=''): 35 | fmsg = ': %r != %r ' % (a, b) 36 | eq_(a, b, msg + fmsg) 37 | 38 | @staticmethod 39 | def check_dict(data, jsondata, columns=None, keymaps=None): 40 | """检查两个字典是否相等 41 | :rtype: object 42 | :param data: 字典数据 43 | :type data: 字典或json对象 44 | :param jsondata: json数据 45 | :type jsondata: json对象 46 | :param columns: data中需要检查的key,空表示检查所有key 47 | :type columns: list 48 | :param keymaps: data和jsondata中可能有某些key名字不同,keymaps就是key的对应关系,空表示检查所有key 49 | :type keymaps: dict 50 | """ 51 | if columns is None: 52 | columns = [] 53 | if keymaps is None: 54 | keymaps = {} 55 | if isinstance(data, tuple): 56 | if len(data) != len(jsondata): 57 | assert False, 'len not equal, data=%s, jsondata=%s' % (data, jsondata) 58 | i = 0 59 | while i < len(data): 60 | if not CheckUtil.check_dict(data[i], jsondata[i], columns, keymaps): 61 | assert False, 'not equal, data=%s, jsondata=%s' % (data[i], jsondata[i]) 62 | i += 1 63 | return True 64 | elif isinstance(data, list): 65 | i = 0 66 | while i < len(data): 67 | if not CheckUtil.check_dict(data[i], jsondata[i], columns, keymaps): 68 | assert False, 'not equal, data=%s, jsondata=%s' % (data[i], jsondata[i]) 69 | i += 1 70 | return True 71 | elif isinstance(data, dict): 72 | for key in data: 73 | if not columns: 74 | if not keymaps.get(key, key) in jsondata: 75 | assert False, 'key: %s not in jsondata=%s' % (keymaps.get(key, key), jsondata) 76 | else: 77 | if not CheckUtil.check_dict(data[key], jsondata[keymaps.get(key, key)], columns, keymaps): 78 | assert False, 'not equal, data=%s, jsondata=%s' % (data[key], jsondata[keymaps.get(key, key)]) 79 | else: 80 | if key not in columns: 81 | continue 82 | else: 83 | if not keymaps.get(key, key) in jsondata: 84 | assert False, 'key: %s not in jsondata=%s' % (keymaps.get(key, key), jsondata) 85 | else: 86 | if not CheckUtil.check_dict(data[key], jsondata[keymaps.get(key, key)], columns, keymaps): 87 | assert False, 'not equal, data=%s, jsondata=%s' % (data[key], jsondata[keymaps.get(key, key)]) 88 | return True 89 | else: 90 | if isinstance(jsondata, str): 91 | jsondata = jsondata.encode('utf-8') 92 | if isinstance(data, str): 93 | data = data.encode('utf-8') 94 | try: 95 | assert data == jsondata, '%s != %s' % (data, jsondata) 96 | return data == jsondata 97 | except AssertionError: 98 | if is_json(data): 99 | data = json.loads(data) 100 | if not CheckUtil.check_dict(data, jsondata, columns, keymaps): 101 | assert False, 'not equal, data=%s, jsondata=%s' % (data, jsondata) 102 | return True 103 | else: 104 | return False 105 | 106 | 107 | if __name__ == '__main__': 108 | data = dict(returncode='0000', returndesc='success') 109 | jsondata = {'returncode': '0000', 'returndesc': 'success'} 110 | 111 | CheckUtil().check(1 == 1) 112 | CheckUtil().check_return(data) 113 | CheckUtil().check_equal('a', 'a') 114 | CheckUtil().check_dict(data, jsondata) 115 | -------------------------------------------------------------------------------- /Util/locustTool/locustUtil.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import yaml 3 | import os 4 | import io 5 | import multiprocessing 6 | 7 | 8 | class locustUtil(object): 9 | def __init__(self, locustyml='config/locust.yml'): 10 | project_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')) 11 | locustyml_path = os.path.join(project_path, locustyml) 12 | self.cases = yaml.load(io.open(locustyml_path, 'r', encoding='utf-8')) 13 | 14 | def getRunCommand(self): 15 | ''' 16 | 从locust.yml配置文件,获取locust运行命令 17 | ''' 18 | mode = self.cases.get('mode', 0) 19 | no_web = self.cases.get('no_web', 0) 20 | if mode: 21 | # 分布式模式 22 | slaves_num = self.cases['params'].get('slaves_num', multiprocessing.cpu_count()) 23 | master_port = self.cases['params'].get('master_port', 5557) 24 | if no_web: 25 | csv = self.cases['params'].get('csv', 'locust') 26 | c = self.cases['params'].get('c', 10) 27 | r = self.cases['params'].get('r', 2) 28 | run_time = self.cases['params'].get('run_time', '5m') 29 | return '--master --no-web --csv {0} -c {1} -r {2} --run-time {3} --expect-slaves {4} --master-bind-port {5}'.format(csv, c, r, run_time, slaves_num, master_port) 30 | else: 31 | port = self.cases['params'].get('port', 8089) 32 | return '--master -P {0} --master-bind-port {1}'.format(port, master_port) 33 | else: 34 | # 单例模式 35 | if no_web: 36 | csv = self.cases['params'].get('csv', 'locust') 37 | c = self.cases['params'].get('c', 10) 38 | r = self.cases['params'].get('r', 2) 39 | run_time = self.cases['params'].get('run_time', '5m') 40 | return '--no-web --csv {0} -c{1} -r{2} --run-time {3}'.format(csv, c, r, run_time) 41 | else: 42 | port = self.cases['params'].get('port', 8089) 43 | return '-P {0}'.format(port) 44 | 45 | def genLocustfile(self, name='locustfile.py'): 46 | """ 47 | 使用locust.yml配置文件及template/下模板,生成性能测试脚本 48 | """ 49 | # 获取最大,最小等待时间 50 | min_wait = str(self.cases.get('min_wait', 100)) 51 | max_wait = str(self.cases.get('max_wait', 1000)) 52 | tasks = self.cases['task'] 53 | res = [] 54 | # 先对tasks中clas+function进行去重处理 55 | tasks = list(dict((i["class"] + i["function"], i) for i in tasks).values()) 56 | # 依次生成import,func,task 57 | for task in tasks: 58 | # 定义临时dict 59 | tmp = {} 60 | # 获取task详细配置 61 | file_name = task.get('file').strip('.py').replace('/', '.').replace('\\', '.') 62 | class_name = task.get('class') 63 | function_name = task.get('function') 64 | weight = str(task.get('weight', 1)) 65 | with io.open(os.path.join(os.path.dirname(__file__), 'template/import_template'), encoding='utf-8') as import_template: 66 | import_content = import_template.read() 67 | import_content = import_content.replace('$file', file_name) 68 | import_content = import_content.replace('$class', class_name) 69 | tmp['import'] = import_content 70 | with io.open(os.path.join(os.path.dirname(__file__), 'template/function_template'), encoding='utf-8') as function_template: 71 | function_content = function_template.read() 72 | function_content = function_content.replace('$class', class_name) 73 | function_content = function_content.replace('$function', function_name) 74 | tmp['function'] = function_content 75 | with io.open(os.path.join(os.path.dirname(__file__), 'template/task_template'), encoding='utf-8') as task_template: 76 | task_content = task_template.read() 77 | task_content = task_content.replace('$weight', weight) 78 | task_content = task_content.replace('$class', class_name) 79 | task_content = task_content.replace('$function', function_name) 80 | tmp['task'] = task_content 81 | res.append(tmp) 82 | # 生成完整的import,function,task内容 83 | import_content = '\r\n'.join(set([i['import'] for i in res])) 84 | function_content = '\r\n\r\n'.join(set([i['function'] for i in res])) 85 | task_content = '\r\n\r\n'.join(set([i['task'] for i in res])) 86 | with io.open(os.path.join(os.path.dirname(__file__), 'template/locustfile_template'), encoding='utf-8') as locustfile_template: 87 | with io.open(name, mode='w', encoding='utf-8') as f: 88 | locustfile_content = locustfile_template.read() 89 | locustfile_content = locustfile_content.replace('$import', import_content) 90 | locustfile_content = locustfile_content.replace('$function', function_content) 91 | locustfile_content = locustfile_content.replace('$min_wait', min_wait) 92 | locustfile_content = locustfile_content.replace('$max_wait', max_wait) 93 | locustfile_content = locustfile_content.replace('$task', task_content) 94 | f.write(locustfile_content) 95 | return name 96 | -------------------------------------------------------------------------------- /Tests/test_demo.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import sys 3 | import os 4 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 5 | from Util import * 6 | from functools import partial 7 | 8 | 9 | class test_demo(object): 10 | """接口测试demo""" 11 | 12 | def __init__(self, ): 13 | pass 14 | 15 | @staticmethod 16 | def baidu(wd): 17 | name = 's' 18 | sreq = HttpParam(wd=wd) 19 | sres = BaiduHttpUtil.get(name, sreq) 20 | # 检查百度搜索返回页面标题 21 | resultCheck_baidu.check_title(sres, wd) 22 | # 检查百度页面返回内容 23 | resultCheck_baidu.check_results(sres, wd) 24 | 25 | def test_http(self): 26 | # http接口调用demo 27 | # 校验输入不同类型的wd时,百度是否均可正常搜索返回结果 28 | # wd分类:英文,数字 29 | wd_list = ['taffy', '12345'] 30 | for wd in wd_list: 31 | f = partial(test_demo.baidu, wd) 32 | f.description = 'search: {0}'.format(wd) 33 | yield (f,) 34 | 35 | def test_httpbin_get(self): 36 | # httpbin.org接口测试demo 37 | # get请求 38 | name = 'get' 39 | sreq = HttpbinParam(name='Taffy', description='Taffy is a Test Automation Framework based on nosetests') 40 | sres = HttpbinUtil.get(name, sreq) 41 | resultCheck_httpbin.check_get(sres, sreq) 42 | 43 | def test_httpbin_post(self): 44 | # httpbin.org接口测试demo 45 | # post请求 46 | name = 'post' 47 | sreq = HttpbinParam(name='Taffy', description='Taffy is a Test Automation Framework based on nosetests') 48 | sres = HttpbinUtil.post(name, sreq) 49 | resultCheck_httpbin.check_post(sres, sreq) 50 | 51 | @nottest 52 | def test_hessian(self): 53 | # 通过hessian协议调用dubbo接口 demo 54 | method = 'delete' 55 | req = protocol.object_factory('com.service.dubbo.base.req.BaseRequest') 56 | id = 123456789 57 | HessianUtil.Invoke(method, req, id) 58 | 59 | @nottest 60 | def test_webservice(self): 61 | # webservice接口调用demo 62 | url = 'http://www.webxml.com.cn/WebServices/ValidateEmailWebService.asmx?wsdl' 63 | WebServiceUtil.Invoke(url, 'ValidateEmailAddress', 'lovesoo@qq.com') 64 | 65 | @nottest 66 | def test_db(self): 67 | # 数据库操作demo 68 | print(DBUtil.execute('select * from user;')) 69 | 70 | @nottest 71 | def test_OA(self): 72 | # 正交表设计测试用例demo 73 | oat = OAT() 74 | case1 = OrderedDict([('K1', [0, 1]), 75 | ('K2', [0, 1]), 76 | ('K3', [0, 1])]) 77 | case2 = OrderedDict([('A', ['A1', 'A2', 'A3']), 78 | ('B', ['B1', 'B2', 'B3', 'B4']), 79 | ('C', ['C1', 'C2', 'C3']), 80 | ('D', ['D1', 'D2'])]) 81 | case3 = OrderedDict([('对比度', ['正常', '极低', '低', '高', '极高']), 82 | ('色彩效果', ['无', '黑白', '棕褐色', '负片', '水绿色']), 83 | ('感光度', ['自动', 100, 200, 400, 800]), 84 | ('白平衡', ['自动', '白炽光', '日光', '荧光', '阴光']), 85 | ('照片大小', ['5M', '3M', '2M', '1M', 'VGA']), 86 | ('闪光模式', ['开', '关'])]) 87 | case4 = OrderedDict([('A', ['A1', 'A2', 'A3', 'A4', 'A5', 'A6']), 88 | ('B', ['B1']), 89 | ('C', ['C1'])]) 90 | print(json.dumps(oat.genSets(case1), indent=4, ensure_ascii=False)) 91 | print(json.dumps(oat.genSets(case2), indent=4, ensure_ascii=False)) 92 | print(json.dumps(oat.genSets(case3), indent=4, ensure_ascii=False)) 93 | print(json.dumps(oat.genSets(case4), indent=4, ensure_ascii=False)) 94 | print(json.dumps(oat.genSets(case4, 1, 0), indent=4, ensure_ascii=False)) 95 | print(json.dumps(oat.genSets(case4, 1, 1), indent=4, ensure_ascii=False)) 96 | print(json.dumps(oat.genSets(case4, 1, 2), indent=4, ensure_ascii=False)) 97 | print(json.dumps(oat.genSets(case4, 1, 3), indent=4, ensure_ascii=False)) 98 | 99 | @nottest 100 | def test_redis(self): 101 | # redis/redis cluster操作 demo 102 | 103 | print(RedisUtil.execute('sismember', 'device:valid', 'abc')) 104 | print(RedisUtil.execute("get", "userSession:%s", "12345", confSection='Redis_Cluster')) 105 | 106 | @nottest 107 | def test_security(self): 108 | # 加密方法使用demo 109 | import string 110 | import binascii 111 | 112 | sec = Security() 113 | key_8 = string.ascii_lowercase[:8] 114 | key_16 = string.ascii_lowercase[:16] 115 | data = 'Taffy is a Test Automation Framework based on nosetests.' 116 | 117 | print('DES:', binascii.b2a_hex(sec.getDES(key_8, data)).decode('utf-8')) # des 118 | print('Decode DES:', sec.decodeDES(key_8, sec.getDES(key_8, data)).decode('utf-8')) # decode des 119 | print('DES3:', binascii.b2a_hex(sec.getDES3(key_16, data)).decode('utf-8')) # desc3 120 | print('Decode DES3:', sec.decodeDES3(key_16, sec.getDES3(key_16, data)).decode('utf-8')) # decode desc3 121 | print('HMAC_SHA1:', sec.getHMAC_SHA1(key_8, data)) # sha1 122 | print('SHA:', sec.getSHA(data)) # sha 123 | print('MD5:', sec.getMD5(data)) # md5 124 | print('AES:', binascii.b2a_hex(sec.getAES(key_16, data)).decode('utf-8')) # aes 125 | print('Base64:', sec.getBase64(data).decode('utf-8')) # base64 126 | print('Decode Base64:', sec.decodeBase64(sec.getBase64(data)).decode('utf-8')) # decode base64 127 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Taffy 2 | 3 | Taffy is a Test Automation Framework based on nosetests. 4 | 5 | Taffy is usesd mainly to test interface including Http, dubbo/hessian, Webservice, Socket and etc. 6 | 7 | Taffy also provided encapsulation and realized the interfaces of data check, config read, DB / redis operations, data encryption / decryption and etc. 8 | 9 | The basic useage can be found at Tests/ folder. 10 | 11 | Taffy是基于nosetests的自动化测试框架。 12 | 13 | Taffy主要适用于服务端接口(包含且不限于Http, Dubbo/hessian, Webservice, Socket等协议)功能及性能自动化测试;也可集成Selenium, Appium进行WEB或APP的自动化测试。 14 | 15 | Taffy同时封装实现了配置读取、数据对比、DB/Redis操作、数据加解密、正交表生成测试用例等工具类。 16 | 17 | 基本用法可以参考[Tests/](https://github.com/lovesoo/Taffy/tree/master/Tests)目录。 18 | 19 | 欢迎加入QQ群交流讨论:[25452556](https://jq.qq.com/?_wv=1027&k=5pqB0UV) 20 | 21 | # 目录 22 | - [Taffy](#taffy) 23 | - [0. 更新记录](#0-更新记录) 24 | - [1. 运行环境](#1-运行环境) 25 | - [2. 项目结构](#2-项目结构) 26 | - [3. 环境部署](#3-环境部署) 27 | - [3.1 Python](#31-python) 28 | - [3.2 IDE](#32-ide) 29 | - [3.3 Lib](#33-lib) 30 | - [3.4 PyCharm配置](#34-pycharm配置) 31 | - [4. 测试编写执行及报告导出](#4-测试编写执行及报告导出) 32 | - [4.1 功能自动化测试](#41-功能自动化测试) 33 | - [4.1.1 测试编写](#411-测试编写) 34 | - [4.1.2 测试执行](#412-测试执行) 35 | - [4.1.3 测试报告](#413-测试报告) 36 | - [4.2 性能测试](#42-性能测试) 37 | - [4.2.1 配置config/locust.yml](#421-配置configlocustyml) 38 | - [4.2.2 运行locust](#422-运行locust) 39 | - [4.2.3 测试报告](#423-测试报告) 40 | - [5. 参考资料](#5-参考资料) 41 | - [6. 联络方式](#6-联络方式) 42 | - [7. 附录](#7-附录) 43 | - [7.1 locust框架集成使用说明](#71-locust框架集成使用说明) 44 | - [7.2 nose编写测试用例方法](#72-nose编写测试用例方法) 45 | - [7.3 Jenkins集成](#73-jenkins集成) 46 | 47 | # 0. 更新记录 48 | 49 | 20181010 v1.7 Python 3.7版本适配,现已支持Python2.7 - 3.7 50 | 51 | 20171030 v1.6 支持模块自定义配置安装,详见[**setup.py**](https://github.com/lovesoo/Taffy/blob/master/setup.py) 52 | 53 | 20171015 v1.5 新增《[**Taffy入门教学视频**](http://v.youku.com/v_show/id_XMzA4NTk2MDI5Mg==.html)》 54 | 55 | 20171010 v1.4 支持分布式模式运行locust 56 | 57 | 20171009 v1.3 统一配置文件格式为YAML 58 | 59 | 20170928 v1.2 集成locust,相同脚本可同时进行功能自动化及性能测试,详见[**附录7-1**](#71-locust框架集成使用说明) 60 | 61 | 20170922 v1.1 集成selenium,新增相关测试demo 62 | 63 | 20170920 v1.0 发布第一个版本,支持http/hessian/webservice等类型接口功能自动化测试,并提供相关Util工具类 64 | 65 | # 1. 运行环境 66 | - macOS,linux,windows 67 | - nose 1.3.7 68 | - python 2.7 - 3.7 69 | 70 | # 2. 项目结构 71 | 1) config 配置文件 72 | 2) Tests 测试用例 73 | 3) Util 工具类 74 | - checkTool 数据比较 75 | - commonTool 配置文件读取 76 | - DBTool 数据库操作 77 | - hessianTool hessian接口 78 | - httpTool http接口 79 | - locustTool locust集成 80 | - OATool 正交表设计测试用例 81 | - redisTool redis/redis cluster操作 82 | - securityTool 数据加解密 83 | - seleniumTool selenium集成 84 | - webserviceTool webservice接口 85 | 86 | # 3. 环境部署 87 | ## 3.1 Python 88 | 89 | 请根据需要下载Python 2.7或3.7版本:https://www.python.org/downloads/ 90 | 91 | ## 3.2 IDE 92 | 93 | 推荐使用PyCharm:http://www.jetbrains.com/pycharm/ 94 | 95 | ## 3.3 Lib 96 | 97 | [requirements.txt ](https://github.com/lovesoo/Taffy/blob/master/requirements.txt)中存放了Taffy用到的第三方lib库,可以运行[` python setup.py`](https://github.com/lovesoo/Taffy/blob/master/setup.py)进行模块安装配置,命令如下: 98 | 99 | ``` 100 | # 默认安装全部模块 101 | $ python setup.py 102 | 103 | # -h或--help,查看帮助 104 | $ python setup.py -h 105 | ``` 106 | 107 | ## 3.4 PyCharm配置 108 | 109 | 1) 运行PyCharm,打开下载的项目:taffy 110 | 111 | 2) 「File」–>「Settings 」–>「Project:Taffy」->「Project Interpreter」,配置Python interpreter为当前python版本安装目录 112 | 113 | 3) 「File」–>「Settings 」–>「Tools」->「Python Integrated Tools」–>「Nosetests」,配置Default test runner为Nosetests 114 | 115 | 4) 「Run」–>「Edit Configurations」–>「Defaults」->「Python」,配置Python interpreter为当前python版本安装目录 116 | 117 | 5) 「Run」–>「Edit Configurations」–>「Defaults」->「Python tests」–>「Nosetests」,配置Python interpreter为当前python版本安装目录,并在Interpreter options中填入-s用以显示nose运行及调试信息 118 | 119 | # 4. 测试编写执行及报告导出 120 | ## 4.1 功能自动化测试 121 | 122 | ### 4.1.1 测试编写 123 | 124 | taffy目前只支持nose方式编写测试用例,详见[附录7-2](#72-nose编写测试用例方法) 125 | 126 | 后续可扩展支持其他方式,如以excel,csv,yaml等数据驱动形式保存用例 127 | 128 | ### 4.1.2 测试执行 129 | 130 | 可以使用两种方式执行功能自动化测试脚本: 131 | 132 | 1) 图形用户界面GUI 133 | 134 | 在PyCharm中,选中测试文件,如Tests/test_demo.py 135 | 136 | 鼠标右键选择Run 'Nosetests in test_demo.py'即可执行测试 137 | 138 | 快捷键:Ctrl+Shift+F10,在脚本中使用会单独执行选中的test class或func 139 | 140 | 2) 命令行界面CLI 141 | 142 | 在PyCharm下方Terminal终端中,输入命令执行测试: 143 | 144 | ``` 145 | # 执行测试文件test_demo.py 146 | $ nosetests -v Tests/test_demo.py 147 | 148 | # 单独执行测试文件test_demo.py中测试类test_demo下的test_http测试方法 149 | $ nosetests -v Tests/test_demo.py:test_demo.test_http 150 | ``` 151 | 更多nosetests运行选项,请参考[nostests官方文档](http://nose.readthedocs.io/en/latest/man.html) 152 | 153 | ### 4.1.3 测试报告 154 | 155 | 功能自动化测试执行完成后,在Pycharm左下方Run窗口的Testing toolbar中,选择“Export Test Results”按钮即可导出测试报告 156 | 157 | 详见[《PyCharm运行Nosetests并导出测试报告》](http://lovesoo.org/pycharm-run-nosetests-and-exports-test-report.html) 158 | 159 | ## 4.2 性能测试 160 | 161 | ### 4.2.1 配置config/locust.yml 162 | 163 | ### 4.2.2 运行locust 164 | 165 | 运行test_locust.py生成locustfile及执行性能测试,命令如下: 166 | 167 | ``` 168 | $ cd Taffy\Tests 169 | $ python test_locust.py 170 | ``` 171 | 172 | ### 4.2.3 测试报告 173 | 174 | 1) 普通模式 175 | 176 | locust以普通模式运行时,可在[web页面](http://localhost:8089/)实时查看运行结果,包括请求数,响应时间,RPS,失败率等 177 | 178 | 测试执行完成后可在WEB页面下载CSV格式测试报告(选择Download Data -> Download response time distribution CSV) 179 | 180 | 2) no-web模式 181 | 182 | locust以no-web模式运行时,csv格式数据会定时保存在运行目录下,如locust_distribution.csv和locust_requests.csv 183 | 184 | Taffy集成locust性能测试框架使用说明,详见[附录7-1](#71-locust框架集成使用说明) 185 | 186 | 187 | # 5. 参考资料 188 | 189 | 1. http://nose.readthedocs.io/en/latest/index.html 190 | 191 | 2. https://docs.python.org/dev/library/unittest.html 192 | 193 | 3. https://docs.locust.io/en/latest/ 194 | 195 | 4. http://www.cnblogs.com/yufeihlf/p/5764099.html 196 | 197 | 198 | # 6. 联络方式 199 | 200 | QQ交流群:[25452556](https://jq.qq.com/?_wv=1027&k=5pqB0UV) 201 | 202 | 203 | # 7. 附录 204 | 205 | ## 7.1 locust框架集成使用说明 206 | 207 | [《Taffy集成Locust性能测试框架使用说明》](http://lovesoo.org/taffy-using-locust-performance-testing.html) 208 | 209 | ## 7.2 nose编写测试用例方法 210 | 211 | [《nose框架编写测试用例方法》](http://lovesoo.org/nose-writing-tests.html) 212 | 213 | ## 7.3 Jenkins集成 214 | 215 | [《Jenkins集成taffy进行自动化测试并输出测试报告》](http://lovesoo.org/jenkins-integrated-taffy-for-automated-testing-and-output-test-reports.html) -------------------------------------------------------------------------------- /Util/securityTool/securityUtil.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import base64 3 | from Crypto.Hash import MD5 4 | from Crypto.Hash import SHA 5 | from Crypto.Hash import HMAC 6 | from Crypto.Cipher import DES 7 | from Crypto.Cipher import DES3 8 | from Crypto.Cipher import AES 9 | from Crypto.Util import Padding 10 | 11 | 12 | class Security(object): 13 | """加密类,定义常用加密方法""" 14 | 15 | def __init__(self): 16 | pass 17 | 18 | def getDES(self, key, data, mode=DES.MODE_ECB, block_size=8, style='pkcs7'): 19 | """ 20 | DES加密 21 | :param key: 秘钥key 22 | :param data: 未加密数据 23 | :param mode: 加密模式 24 | :var MODE_ECB: :ref:`Electronic Code Book (ECB) ` 25 | :var MODE_CBC: :ref:`Cipher-Block Chaining (CBC) ` 26 | :var MODE_CFB: :ref:`Cipher FeedBack (CFB) ` 27 | :var MODE_OFB: :ref:`Output FeedBack (OFB) ` 28 | :var MODE_CTR: :ref:`CounTer Mode (CTR) ` 29 | :var MODE_OPENPGP: :ref:`OpenPGP Mode ` 30 | :var MODE_EAX: :ref:`EAX Mode ` 31 | 32 | :param block_size: 填充block大小:默认为8 33 | :param style: 填充算法:‘pkcs7’(default),‘iso7816’or‘x923’ 34 | :return: 加密结果 byte string 35 | """ 36 | 37 | data = Padding.pad(data.encode('utf-8'), block_size=block_size, style=style) 38 | cipher = DES.new(key.encode('utf-8'), mode=mode) 39 | 40 | return cipher.encrypt(data) 41 | 42 | def decodeDES(self, key, data, mode=DES.MODE_ECB, block_size=8, style='pkcs7'): 43 | """ 44 | DES解密 45 | :param key: 秘钥key 46 | :param data: 未加密数据 47 | :param mode: 加密模式 48 | :var MODE_ECB: :ref:`Electronic Code Book (ECB) ` 49 | :var MODE_CBC: :ref:`Cipher-Block Chaining (CBC) ` 50 | :var MODE_CFB: :ref:`Cipher FeedBack (CFB) ` 51 | :var MODE_OFB: :ref:`Output FeedBack (OFB) ` 52 | :var MODE_CTR: :ref:`CounTer Mode (CTR) ` 53 | :var MODE_OPENPGP: :ref:`OpenPGP Mode ` 54 | :var MODE_EAX: :ref:`EAX Mode ` 55 | 56 | :param block_size: 填充block大小:默认为8 57 | :param style: 填充算法:‘pkcs7’(default),‘iso7816’or‘x923’ 58 | :return: 解密结果 byte string 59 | """ 60 | cipher = DES.new(key.encode('utf-8'), mode=mode) 61 | plaintext = cipher.decrypt(data) 62 | plaintext = Padding.unpad(plaintext, block_size=block_size, style=style) 63 | return plaintext 64 | 65 | def getDES3(self, key, data, mode=DES3.MODE_ECB, block_size=8, style='pkcs7'): 66 | """ 67 | DES3加密 68 | :param key: 秘钥key 69 | :param data: 未加密数据 70 | :param mode: 加密模式 71 | :var MODE_ECB: :ref:`Electronic Code Book (ECB) ` 72 | :var MODE_CBC: :ref:`Cipher-Block Chaining (CBC) ` 73 | :var MODE_CFB: :ref:`Cipher FeedBack (CFB) ` 74 | :var MODE_OFB: :ref:`Output FeedBack (OFB) ` 75 | :var MODE_CTR: :ref:`CounTer Mode (CTR) ` 76 | :var MODE_OPENPGP: :ref:`OpenPGP Mode ` 77 | :var MODE_EAX: :ref:`EAX Mode ` 78 | 79 | :param block_size: 填充block大小:默认为8 80 | :param style: 填充算法:‘pkcs7’(default),‘iso7816’or‘x923’ 81 | :return: 加密结果 byte string 82 | """ 83 | data = Padding.pad(data.encode('utf-8'), block_size=block_size, style=style) 84 | cipher = DES3.new(key.encode('utf-8'), mode=mode) 85 | return cipher.encrypt(data) 86 | 87 | def decodeDES3(self, key, data, mode=DES3.MODE_ECB, block_size=8, style='pkcs7'): 88 | """ 89 | DES3解密 90 | :param key: 秘钥key 91 | :param data: 未加密数据 92 | :param mode: 加密模式 93 | :var MODE_ECB: :ref:`Electronic Code Book (ECB) ` 94 | :var MODE_CBC: :ref:`Cipher-Block Chaining (CBC) ` 95 | :var MODE_CFB: :ref:`Cipher FeedBack (CFB) ` 96 | :var MODE_OFB: :ref:`Output FeedBack (OFB) ` 97 | :var MODE_CTR: :ref:`CounTer Mode (CTR) ` 98 | :var MODE_OPENPGP: :ref:`OpenPGP Mode ` 99 | :var MODE_EAX: :ref:`EAX Mode ` 100 | 101 | :param block_size: 填充block大小:默认为8 102 | :param style: 填充算法:‘pkcs7’(default),‘iso7816’or‘x923’ 103 | :return: 解密结果 byte string 104 | """ 105 | cipher = DES3.new(key, mode=mode) 106 | plaintext = cipher.decrypt(data) 107 | plaintext = Padding.unpad(plaintext, block_size=block_size, style=style) 108 | return plaintext 109 | 110 | def getHMAC_SHA1(self, secret, data): 111 | """获取HMAC-SHA1 112 | :param secret: 秘钥key 113 | :type secret: string 114 | :param data: 未加密数据 115 | :type data: string 116 | """ 117 | h = HMAC.new(secret.encode('utf-8'), digestmod=SHA) 118 | h.update(data.encode('utf-8')) 119 | return h.hexdigest() 120 | 121 | def getSHA(self, data): 122 | """获取SHA 123 | :param data: 未加密数据 124 | :type data: string 125 | """ 126 | m = SHA.new() 127 | m.update(data.encode('utf-8')) 128 | return m.hexdigest() 129 | 130 | def getMD5(self, data): 131 | """获取MD5 132 | :param data: 未加密数据 133 | :type data: string 134 | """ 135 | m = MD5.new() 136 | m.update(data.encode('utf-8')) 137 | return m.hexdigest() 138 | 139 | def getAES(self, key, data, mode=AES.MODE_ECB, block_size=8, style='pkcs7'): 140 | """AES加密 141 | :param key: 秘钥key 142 | :param data: 未加密数据 143 | :param mode: 加密模式 144 | :var MODE_ECB: :ref:`Electronic Code Book (ECB) ` 145 | :var MODE_CBC: :ref:`Cipher-Block Chaining (CBC) ` 146 | :var MODE_CFB: :ref:`Cipher FeedBack (CFB) ` 147 | :var MODE_OFB: :ref:`Output FeedBack (OFB) ` 148 | :var MODE_CTR: :ref:`CounTer Mode (CTR) ` 149 | :var MODE_OPENPGP: :ref:`OpenPGP Mode ` 150 | :var MODE_EAX: :ref:`EAX Mode ` 151 | 152 | #这里密钥key 长度必须为16(AES-128)、24(AES-192)、或32(AES-256)Bytes 长度.目前AES-128足够用 153 | 154 | :param block_size: 填充block大小:默认为8 155 | :param style: 填充算法:‘pkcs7’(default),‘iso7816’or‘x923’ 156 | :return: 加密结果 byte string 157 | """ 158 | 159 | data = Padding.pad(data.encode('utf-8'), block_size=block_size, style=style) 160 | cipher = AES.new(key.encode('utf-8'), mode=mode) 161 | return cipher.encrypt(data) 162 | 163 | def getBase64(self, data): 164 | """返回base64 165 | :param data: 转换前数据 166 | :type data: string 167 | """ 168 | return base64.b64encode(data.encode('utf-8')) 169 | 170 | def decodeBase64(self, data): 171 | """返回base64 172 | :param data: 转换前数据 173 | :type data: string 174 | """ 175 | return base64.b64decode(data) 176 | --------------------------------------------------------------------------------