《Web接口开发与自动化测试基于Python语言》--第15章

第15章 接口性能测试

上一章介绍项目的部署也是为性能测试做铺垫,只有对已经部署的项目做性能测试才有意义,因为中间价Nginx、uWSGI是影响系统性能的重要一环。

15.1 Locust性能测试工具

LoadRunner、JMeter都是非常好用的性能测试工具。

Locust同样也是性能测试工具,完全基于Python语言,采用Pure Python描述测试脚本,并且HTTP请求完全基于Requests库,除了HTTP/HTTPS协议外,Locust还可以测试其他协议的系统,只需要采用Python调用对应的库进行请求描述即可。

LoadRunner、JMeter采用进程、线程的方式,这种方式很难在单机上模拟出较高的并发压力。
Locust的并发机制摒弃了进程和线程,采用协程(gevent)的机制。协程避免了系统级资源的调度,因此可以大幅度提高单机的并发能力。

15.1.1 安装Locust

如果是python 2.*版本,则使用pip进行安装;
如果是python 3版本,则建议从GitHub克隆岛本地进行安装。

Locust安装目录下的setup.py文件的要求:

install_requires=["gevent>=1.1.2", "flask>=0.10.1", "requests>=2.9.1", "msgpack-python>=0.4.2", "six>=1.10.0", "pyzmq==15.2.0"]
  • gevent:在Python中实现协程的一个第三方库,协程,又称微线程(Coroutine),使用gevent可以获得极高的并发性能;

  • flask:Python的一个Web开发框架,它与Djanog的地位相当;

  • requests:做HTTP接口测试的库;

  • msgpack-python:一种快速、紧凑的二进制序列化格式,适用于类似JSON的数据;

  • six:它提供了一些简单的工具来封装Python 2和Python 3之间的差异性;

  • pyzmq:如果你打算把Locust运行在多个进程/机器,建议安装pyzmq;

试运行一下locust,查看帮助信息:

root@TEST:~# locust --help
Usage: locust [options] [LocustClass [LocustClass2 ... ]]

Options:
  -h, --help            show this help message and exit
  -H HOST, --host=HOST  Host to load test in the following format:
                        http://10.21.32.33
  --web-host=WEB_HOST   Host to bind the web interface to. Defaults to '' (all
                        interfaces)
  -P PORT, --port=PORT, --web-port=PORT
                        Port on which to run web host
  -f LOCUSTFILE, --locustfile=LOCUSTFILE
                        Python module file to import, e.g. '../other.py'.
                        Default: locustfile
  --csv=CSVFILEBASE, --csv-base-name=CSVFILEBASE
                        Store current request stats to files in CSV format.
  --master              Set locust to run in distributed mode with this
                        process as master
  --slave               Set locust to run in distributed mode with this
                        process as slave
  --master-host=MASTER_HOST
                        Host or IP address of locust master for distributed
                        load testing. Only used when running with --slave.
                        Defaults to 127.0.0.1.
  --master-port=MASTER_PORT
                        The port to connect to that is used by the locust
                        master for distributed load testing. Only used when
                        running with --slave. Defaults to 5557. Note that
                        slaves will also connect to the master node on this
                        port + 1.
  --master-bind-host=MASTER_BIND_HOST
                        Interfaces (hostname, ip) that locust master should
                        bind to. Only used when running with --master.
                        Defaults to * (all available interfaces).
  --master-bind-port=MASTER_BIND_PORT
                        Port that locust master should bind to. Only used when
                        running with --master. Defaults to 5557. Note that
                        Locust will also use this port + 1, so by default the
                        master node will bind to 5557 and 5558.
  --expect-slaves=EXPECT_SLAVES
                        How many slaves master should expect to connect before
                        starting the test (only when --no-web used).
  --no-web              Disable the web interface, and instead start running
                        the test immediately. Requires -c and -r to be
                        specified.
  -c NUM_CLIENTS, --clients=NUM_CLIENTS
                        Number of concurrent Locust users. Only used together
                        with --no-web
  -r HATCH_RATE, --hatch-rate=HATCH_RATE
                        The rate per second in which clients are spawned. Only
                        used together with --no-web
  -t RUN_TIME, --run-time=RUN_TIME
                        Stop after the specified amount of time, e.g. (300s,
                        20m, 3h, 1h30m, etc.). Only used together with --no-
                        web
  -L LOGLEVEL, --loglevel=LOGLEVEL
                        Choose between DEBUG/INFO/WARNING/ERROR/CRITICAL.
                        Default is INFO.
  --logfile=LOGFILE     Path to log file. If not set, log will go to
                        stdout/stderr
  --print-stats         Print stats in the console
  --only-summary        Only print the summary stats
  --no-reset-stats      Do not reset statistics once hatching has been
                        completed
  -l, --list            Show list of possible locust classes and exit
  --show-task-ratio     print table of the locust classes' task execution
                        ratio
  --show-task-ratio-json
                        print json data of the locust classes' task execution
                        ratio
  -V, --version         show program's version number and exit

15.1.2 性能测试案例

对于Web应用来说,它本质上是由一个个的Web页面构成,一般我们可以通过不同的URL地址来得到不同的页面。

1. 编写性能测试脚本

使用Locust编写一个简单性能测试行为表述脚本,创建文件:locustfile.py

from locust import HttpLocust, TaskSet, task

# 定义用户行为
class UserBehavior(TaskSet):

    @task
    def baidu_page(self):
        self.client.get("/")

class WebsiteUser(HttpLocust):
    task_set = UserBehavior
    min_wait = 3000
    max_wait = 6000

UserBehavior类继承TaskSet类,用于描述用户行为;

baidu_page()方法表示一个用户行为,访问百度首页,使用@task装饰该方式为一个事务;

client.get()用于指定请求的路径“/”,因为是百度首页,所以指定为根路径。

WebsiteUser类用于设置性能测试:

  • task_set:指向一个定义的用户行为类;

  • min_wait:执行事务之间用户等待时间的下界,单位:毫秒;

  • max_wait:执行事务之间用户等待时间的上界,单位:毫秒;

2. 执行性能测试

首先,启动性能测试:

root@TEST:~# locust -f locustfile.py --host=https://www.baidu.com
[2017-11-12 22:38:23,417] CSG-TEST/INFO/locust.main: Starting web monitor at *:8089
[2017-11-12 22:38:23,418] CSG-TEST/INFO/locust.main: Starting Locust 0.8

其中:

-f:指定性能测试脚本文件;

–host:指定被测试应用的URL地址,注意访问百度使用的HTTPS协议;

通过浏览器访问:http://127.0.0.1:8089(Locust启动网络监控器,默认端口号为8089),如图:

《Web接口开发与自动化测试基于Python语言》--第15章_第1张图片

其中:

Number of users to simulate:设置模拟用户数;

Hatch rate(users spawned/second):每秒产生(启动)的虚拟用户数;

单击“Start swarming”按钮,开始运行性能测试,各个参数如下:

  • Type:请求的类型,例如GET/POST;

  • Name:请求的路径,这里为百度首页,即https://www.baidu.com/;

  • request:当前请求的数量;

  • fails:当前请求失败的数量;

  • Median:中间值,单位毫秒,一半的服务器响应时间低于该值,而另一半高于该值;

  • Average:平均值,单位毫秒,所有请求的平均响应时间;

  • Min:请求的最小服务器响应时间,单位毫秒;

  • Max:请求的最大服务器响应时间,单位毫秒;

  • Content Size:单个请求的大小,单位字节;

  • reqs/sec:每秒钟请求的个数。

15.2 发布会系统性能测试

性能测试涉及的知识点非常多,包括:

  • 性能测试的需求分析: 客户需求、新系统性能验证、旧系统扩容、优化系统瓶颈等;

  • 性能测试工具的选型: 商业工具LoadRunner、开源工具JMeter、Locust,或者自研性能工具;

  • 性能测试环境准备: 软件环境、硬件环境、网络环境;

  • 性能测试业务分析: 针对哪些业务做性能测试;

  • 性能测试数据准备: 准备性能测试所需要的基础数据;

  • 性能测试执行策略: 不同业务的用户分配比例,运行时长、思考时间、集合点的设置等;

  • 性能测试监控: 中间件的监控、数据库服务器的监控、系统服务器的监控;

  • 性能测试分析与调优: 分析整个系统各个部分的监控结果;对程序处理过程优化,程序算法优化,中间件各种配置参数的调整,数据库SQL语句、索引、表结构的优化;

15.2.1 性能测试准备

性能测试目的: 发布会签到系统、新系统能力验证;

业务分析: 根据发布会签到系统的应用场景,主要包括发布会管理页面、嘉宾管理页面、嘉宾查询功能和发布会签到功能;

性能测试环境:

《Web接口开发与自动化测试基于Python语言》--第15章_第2张图片

测试数据准备:

  • 发布会数据:10条

  • 嘉宾数据:3000条

  • 待签到嘉宾:3000条

测试数据构造:

  • 执行SQL语句,分别使sign_event、sign_guest两张表的create_time字段在插入数据时直接取当前时间:
ALTER TABLE `sign_event` CHANGE `create_time` `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE `sign_guest` CHANGE `create_time` `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
  • 通过Python脚本批量生成3000条插入数据的SQL语句:
f = open("guests.txt", 'w')

for i in range(1, 3001):
    str_i = str(i)
    realname = "jack" + str_i
    phone = 13800110000 + i
    email = "jack" + str_i + "@mail.com"
    sql = 'INSERT INTO sign_guest (realname, phone, email, sign, event_id) VALUES ("'+realname+'", '+str(phone)+', "'+email+'", 0, 1);'
    f.write(sql)
    f.write("\n")

f.close()
  • 将生成好的SQL语句,在SQL命令行中执行,生成测试数据。

15.2.2 编写性能测试脚本

使用Locust编写性能测试脚本:locustfile.py

#! /usr/bin/python
# -*- coding:utf-8 -*-

from locust import HttpLocust, TaskSet, task

# Web性能测试
class UserBehavior(TaskSet):

    def on_start(self):
        """
        on_start is called when a Locust start before any task is scheduled
        """
        self.login()

    def login(self):
        self.client.post("/login_action", {"username":"admin", "password":"admin123456"})

    @task(2)
    def event_manage(self):
        self.client.get("/event_manage/")

    @task(2)
    def guest_manage(self):
        self.client.get("/guest_manage/")

    @task(1)
    def search_manage(self):
        self.client.get("/search_phone/", params={"phone":"13800112451"})

class WebsiteUser(HttpLocust):
    task_set = UserBehavior
    min_wait = 3000
    max_wait = 6000
  • 通过@task()装饰的方法为一个事务,方法的参数用于指定该行为的执行权重。参数越大,每次被虚拟用户只需的概率越高,如果不设置,则默认为1,发布会管理页、嘉宾管理页和嘉宾搜索功能的执行权重比例为2:2:1。

  • min_wait、max_wait用于指定用户执行事务之间暂停的下限和上限,即3-6秒;

  • 每个事务的请求路径、是GET请求还是POST请求、是否需要传参数等,都可以根据Django项目中对视图函数的定义来决定,调用方法与Requests库基本相同。

15.2.3 执行性能测试

启动性能测试:

>locust -f locustfile.py --host=http://127.0.0.1:8000
  • 通过浏览器访问Locust工具:http://127.0.0.1:8000

  • Number of users to simulate:设置模拟用户数为100

  • Hatch rate(users spawned/second):每秒产生(启动)的用户数为10,即每秒启动10个模拟用户

单击“Start swarming”按钮,运行性能测试,单击“New test”按钮,重新设置虚拟用户数并允许性能测试。

15.3 接口性能测试

接口性能测试相比系统性能测试要简单许多,不用考虑业务场景和用户行为,只需要模拟调用接口,验证接口的最大处理能力即可。

嘉宾签到系统里,发布会现场需要多通道并行对嘉宾进行签到,所以,需要充分验证签到接口的并发签到处理能力。

15.3.1 编写接口性能测试脚本

locustfile.py

#!/usr/bin/python
# -*- coding:utf-8 -*-

from locust import HttpLocust, TaskSet, task
from random import randint

# Web接口测试
class UserBehavior(TaskSet):

    @task()
    def user_sign(self):
        number = randint(1, 3001)
        phone = 13800110000 + number
        str_phone = str(phone)
        self.client.post("/api/user_sign/", data={"eid":"1", "phone":str_phone})

class WebsiteUser(HttpLocust):
    task_set = UserBehavior
    min_wait = 0
    max_wait = 0

15.3.2 执行接口性能测试

使用locust命令启动性能测试,通过参数设置运行测试:

locust -f locustfile.py --host=http://127.0.0.1:8089 --no-web -c 10 -r 10 -n 3000

其中:

–no-web:表示不使用web界面运行测试

-c:设置虚拟用户数

-r:设置每秒启动虚拟用户数

-n:设置请求个数

15.3.3 多线程测试接口性能

重复签到和签到失败是两个不同的服务器处理过程,注意不能混淆。

如果需要对3000个嘉宾计算多长时间内可以完成全部签到,那么可以使用Python的多线程技术来实现这个需求:
thread_if_test.py

#!/usr/bin/python
# -*- coding:utf-8 -*-

import requests
import threading
from time import time

# 定义接口基本地址
base_url = "http://127.0.0.1:8089"

# 签到线程
def sign_thread(start_user, end_user):
    for i in range(start_user, end_user):
        phone = 13800110000 + i
        datas = {"eid":"1", "phone":phone}
        r = requests.post(base_url+'/api/user_sing/', data=datas)
        result = r.json()
        try:
            assert result['message'] == "sign success"
        except AssertionError as e:
            print "phone:" + str(phone) + ", user sign fail!"

# 设置用户分组(即5个线程)
lists = {1:601, 601:1201, 1201:1801, 1801:2401, 2401:3001}

# 创建线程数组
threads = []

# 创建线程
for start_user, end_user in lists.items():
    t = threading.Thread(target=sign_thread, args=(start_user, end_user))
    threads.append(t)

if __name__ == '__main__':
    # 开始时间
    start_time = time()
    # 启动线程
    for i in range(len(lists)):
        threads[i].start()
    for i in range(len(lists)):
        threads[i].join()
    # 结束时间
    end_time = time()
    print "start time: " + str(start_time)
    print "end time: " + str(end_time)
    print "run time: " + str(end_time - start_time)

将3000个数平均分为5组,放到字典中,其中每一组数通过线程类Thread,调用sign_thread()函数生成一个线程。所以,是5个线程(可以理解为“虚拟用户数”)并发调用接口测试。

start()方法用于启动线程,join()方法用于守护线程。

相比专业性能测试工具,这个多线程测试程序要简陋得多,但是直接编程的灵活性也是工具所不具备的。

总结

本章主要以介绍Locust性能测试工具的使用,在实际项目中,我们会遇到各种性能需求,如何去验证性能是否满足需求,工具只是一种达成目标的手段,选取适合的工具和方法,会事半功倍。

工具有工具的好处,直接编写脚本有脚本的好处,两者可以取长补短,针对不同的场景,结合使用。

至此,本书也就全部完成了,这是自己进入IT行业以来,可以惭愧的说,是第一本完整读完的书,很感谢虫师,带自己进入了一个全新(起码对自己这个一直是手工测试的懒人来说)的世界,现在自己大部分的时间都是在写脚本,想着如何能省事的去做,而不是单单的傻傻的去手工执行用例了。我觉得这就是这本书最大的受益之处了,改变了一个人的做事思维方式,提升效率,也提升了自己技能水平。

最后还是要说一句感谢!在这个感恩节的日子,感谢自己的坚持,感谢虫师这本书,感谢自己愛的人,感谢愛自己的人。

你可能感兴趣的:(Python)