locust实战教程4-登录

准备工作

首先需要解决两个问题:

1、用什么发请求(目前是http)

2、测试结果的统计(如响应时间,失败次数,表格等等),毕竟有清晰的结果统计和展示,对于性能的分析十分有参考意义

先从一个简单的例子看下这两个问题怎样解决,以经典的访问百度为例

demo.py

from locust import task, SequentialTaskSet
from locust.contrib.fasthttp import FastHttpUser
import time, requests

class MockUserTask(SequentialTaskSet):

    @task
    def curl_baidu(self):
        self.client.get("http://www.baidu.com/")
    
    @task
    def sleep_to_end(self):
        requests.get("http://www.baidu.com/")
        time.sleep(100)
    
class UserInfo(FastHttpUser):
    tasks = [MockUserTask]

抓包结果如下:

 可以看到有两次目标为百度的get的请求

换到locust统计图上看:

locust只统计了一次,即自己调用requests发的请求是不会被收入统计的

实际上从官方文档上实现自定义的协议测试时就可以了解到统计成功和响应时间的实现了,

self.locust_events.request_success.fire
self.locust_events.request_failure.fire

 因此两个问题的答案如下:

1、如果是http请求,使用self.client即可

2、继承的FastHttpUser中实现了结果的统计

 同时可以扩展下:

非http请求,可以参考官方的实现,定义一个你要用到的协议的AgreementUser

也可以直接使用第三方模块(我自己用的websocket就是这样),使用locust_events.request_failure.fire来统计时间,成功失败次数(会在websocket中讲解)

【至于要不要改写成高性能(gevent实现)的协议客户端,则是看你需求了,可参看社区中locust-plugins】

现在问题解决了,就可以完成项目中的登录功能了

实现登录

在实际项目中,由于登录会在各处都需要使用,而且登录中一些拼接规则也是通用的,所以我们可以实现一个get_token的公共类,

实现刷新token

项目中需要定时刷新token,且后续请求都要换成新token,可以实现一个刷新token的线程类

import threading, time
import sys
sys.path.append('../')
from test_model_global import box_login, get_cfg, general_request, get_token
class GetIdAndRefreshTokenThread(threading.Thread):

    box_token = ''
    box_id = ''

    refresh_token_url = "/mhauth/refresh"
    def __init__(self, **kargs):
        super().__init__()
        self.token_refresh_time = kargs.get("token_refresh_time")
        self.user_name = kargs.get("user_name")
        self.http_client = kargs.get("http_client")
        self.stop_flag = False
    
    def setStopFlag(self, flag):
        self.stop_flag == flag

    def getBoxToken(self):
        return self.box_token
    
    def getBoxId(self):
        return self.box_id

    def run(self):
        cfg_mes = get_cfg.GetCfgMes('../test_model_global/global_cfg.ini').getGlobalCfgLogin()
        box_login_account_cfg = {"server_ip": cfg_mes[0], 
                                "user_name": self.user_name, 
                                "appkey": cfg_mes[2],
                                "http_client": self.http_client }
      
        ret = box_login.BoxLogin(**box_login_account_cfg).boxLogin()
        if ret == -1:
            print("error:login default")
            return False
        self.box_token = ret[3]
        self.box_id = ret[1]
        payload = {"refresh_token":ret[0]}
        cfg_mes = get_cfg.GetCfgMes('../test_model_global/global_cfg.ini').getGlobalCfgTest()
        while True:
            if self.stop_flag:
                break
            else:
                box_refresh_token_cfg = {"server_ip": cfg_mes[0],
                                        "realtime_token": self.box_token,
                                        "ruquest_url": self.refresh_token_url,
                                        "http_client": self.http_client }
                time.sleep(int(self.token_refresh_time)*60)
                self.box_token = general_request.GeneralRequest(**box_refresh_token_cfg).generalPost(payload, get_ret=True)["token"]

其中初始化参数中需要传入self.client发起http请求

在run中先登录拿到第一个token,再实现一个死循环,隔一段时间调用一次刷新token的接口

通过一个开关来退出死循环(测试结束时关闭请求发送)

定义一个getBoxToken的函数来返回token

登录代码如下:

import requests, json, hashlib, re, base64, time


class GetToken():

    def __init__(self, **kargs):
        self.server_ip = kargs.get("server_ip")
        self.user_name = kargs.get("user_name")
        self.user_password = kargs.get("user_password")
        self.appkey = kargs.get("appkey")
        self.http_client =kargs.get("http_client")

    def myMd5(self, md5_str):
        m = hashlib.md5()
        b = md5_str.encode(encoding = "utf-8")
        m.update(b)
        ha1_md5 = m.hexdigest()
        return ha1_md5

    def getAuthorization(self, web_token):
        web_authorization = base64.b64encode(bytes(web_token + ":" + str(int(time.time())) + ":v1", encoding = "utf-8"))
        return web_authorization

    def loginDevice(self, login_url):
        device_login_url = self.server_ip + login_url
        with self.http_client.post(device_login_url, data = json.dumps({"username":self.user_name, "type":"box", "appkey":self.appkey, "isandroid":True}), catch_response=True) as response:
            if response.status_code == 401:
                response.success()
            else:
                response.failure("error code: "+ str(response.status_code))
                return -1
        r = response
        r_authenticate = r.headers['Www-Authenticate']
        digest_realm = eval(r_authenticate.split(',')[0].split('=')[1])
        nonce = eval(r_authenticate.split(',')[1].split('=')[1])
        password = ""
        for i in self.user_name[-8:]:
            password = password + nonce[int(i)]
        response = self.myMd5(self.myMd5(self.user_name + ":" + digest_realm + ":" + password) + nonce)
        auth_str = "Digest username="+ "\"" + self.user_name + "\"" + "," + "realm="+ "\"" + digest_realm + "\""+ "," + "nonce="+ "\"" + nonce + "\"" + "," + "response="+ "\"" + response + "\""
        headers = {
            "Authorization": auth_str
        }
        r1 = self.http_client.post(device_login_url, headers = headers)
        if r.text != None:
            r1_text_json = json.loads(r1.text)
            if r1.status_code == 200 and "token" in r1_text_json:
                r1_text_eval = eval(r1.text)
                return r1_text_eval["refresh_token"], r1_text_eval["userid"], r1_text_eval["expires"], r1_text_eval["token"]
            else:
                return -1
        else:
            return -1



    def loginWeb(self, login_url):
        weblogin_payload = {
        "username": self.user_name,
        "type": "web",
        "appkey": self.appkey
        }
    
        headers = {"Content-Type": "application/json;charset=UTF-8"}
        web_url = self.server_ip + login_url
        # print("o(╥﹏╥)o")
        r = requests.post(web_url, data=json.dumps(weblogin_payload), timeout=60)
        # print(r.status_code) 
        response_data = json.loads(r.text)
        # print(response_data)
        str_pat = re.compile(r'\"(.*)\"')
        # print(response_data["realm"], response_data["nonce"])
        md5_realm = str_pat.findall(response_data["realm"])
        md5_nonce = response_data["nonce"]
        # print(md5_realm[0])
        # print(md5_nonce)
        md5_str = self.user_name + ":" + md5_realm[0] + ":" + self.user_password
        # print(md5_str)
        response = self.myMd5(self.myMd5(md5_str) + md5_nonce)
        auth_str = 'Digest realm=' + "\"" + md5_realm[0] + "\"" + ',' + "username=" + "\"" + self.user_name + "\"" + "," + \
               "nonce=" + "\"" + md5_nonce + "\"" + ',' + 'response=' + "\"" + response + "\""
        # print(auth_str)
        headers = {
            "Authorization": auth_str
        }
        # print( md5_realm[0],str(md5_nonce), response)
        r1 = requests.post(web_url, headers=headers)
        # print(r1.status_code)
        r1_text_json = json.loads(r1.text)
        # print(r1_text_json)
        if "token" in r1_text_json:
            # print(r1_text_json["token"])
            return r1_text_json["token"]
    

一个典型的挑战登录,根据实际项目中业务流程来即可(如果用jmeter的话  上述流程估计无法这么直接的完成,得点好多次按钮和一堆可读性差的beanshell)

封装的常用请求

业务中需要带鉴权的部分将其写到请求中,后续请求只用关注接口链接和参数即可  

import requests, json
import sys
sys.path.append('../')
from test_model_global import get_token

class GeneralRequest(get_token.GetToken):
    def __init__(self, **kargs):
        super().__init__(**kargs)
        self.server_ip = kargs.get("server_ip")
        self.realtime_token = kargs.get("realtime_token")
        self.ruquest_url = kargs.get("ruquest_url")
        self.http_client = kargs.get("http_client")

    def generalPost(self, payload, get_ret = False):
        headers = {"Content-Type": "application/json;charset=UTF-8"}
        headers["Authorization"] = 'Basic {}'.format(str(super().getAuthorization(self.realtime_token), encoding="utf-8"))
        r = self.http_client.post(self.server_ip + self.ruquest_url, headers = headers, json = payload)
        if get_ret == True:
            if r.text != None:
                r_text_json = json.loads(r.text)
                return r_text_json
            else:
                return None
        if get_ret == False:
            pass

    def generalGet(self, params, get_ret = False):
        headers = {"Content-Type": "application/json;charset=UTF-8"}
        headers["Authorization"] = 'Basic {}'.format(str(super().getAuthorization(self.realtime_token), encoding="utf-8"))
        r = self.http_client.get(self.server_ip + self.ruquest_url, headers = headers, params=params)
        if get_ret == True:
            r_text_json = json.loads(r.text)
            return r_text_json

    def generalPostData(self, payload, get_ret = False):
        headers = {"Content-Type": "application/json;charset=UTF-8"}

        headers["Authorization"] = 'Basic {}'.format(str(super().getAuthorization(self.realtime_token), encoding="utf-8"))
        r = self.http_client.post(self.server_ip + self.ruquest_url, headers = headers, data = json.dumps(payload))
        if get_ret == True:
            r_text_json = json.loads(r.text)
            return r_text_json

完成登录task

class MockUserTask(SequentialTaskSet):
    cfg_mes = get_cfg.GetCfgMes('../test_model_global/global_cfg.ini').getGlobalCfgTest()
    t_get_refresh_token = ''
    box_sn = ''
    # 开户销户放到单独的模块 
    # 业务流程1:登录并定时获取新的token,要用单独一个线程刷新token
    # 将user的fasthttp client作参数传给其他类中,可以将代码分离或者复用之前的requests模块的业务
    @task
    def get_refresh_token_task(self):
        self.box_sn = self.user.sn_queue.get()
        box_refresh_token_cfg = {"server_ip": self.cfg_mes[0],
                        "user_name": self.box_sn,
                        "token_refresh_time": self.cfg_mes[4],
                        "http_client": self.client}
        t_token = get_box_id_token_t.GetIdAndRefreshTokenThread(**box_refresh_token_cfg)
        t_token.setDaemon(False)
        t_token.start()
        self.t_get_refresh_token = t_token

class UserInfo(FastHttpUser):
    sn_queue = queue.Queue()
    for i in range(0, 400):
        sn_queue.put_nowait(str(999999900000 + int(i)))
    tasks = [MockUserTask]

这样在task中,我们关注的就是task本身的业务描述,第一个task就是完成登录并且提供了一个定时刷新token的引用调用getBoxToken: t_get_refresh_token.getBoxToken()(即线程类的对象),后续的请求中如:

    @task
    def heart_beat_notify(self):
        send_heart_beat_cfg = {"basic_authorization":'Basic {}'.format(str(get_token.GetToken().getAuthorization(self.t_get_refresh_token.getBoxToken()), encoding="utf-8")),
                                "locust_events":events}
        t_notify = send_heart_beat_and_notify_t.SendHeartBeatAndNotifyThread(**send_heart_beat_cfg)
        t_notify.setDaemon(False)
        t_notify.start()
        self.t_heart_beat_notify  = t_notify

t_get_refresh_token.getBoxToken()则是传入刷新后的token

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值