├── .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 |
4 |
5 |
6 |
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 |
10 |
11 |
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 |
--------------------------------------------------------------------------------