阅读804 返回首页    go iPhone_iPad_Mac_手机_平板_苹果apple


手工脚本编写指南__手工脚本编写_Lite用户使用手册_性能测试-阿里云

访问Lite控制台

手工编写测试脚本

1.概述

  Lite代码模式可以使用自定义代码实现更加丰富的压测行为,比如TCP、UDP协议,丰富的API让您变的无所不能;Lite所使用的脚本语言为Jython,Jython是Python的Java语言实现,它使用Python的语法和类库,运行在JVM中,和同一个JVM中的Java类可以实现无缝互操作。因此,使用Jython作为脚本语言可以最大程度的利用Python的简洁、高效,同时保留对Java语言的全面兼容。

  Lite为了使脚本的创建更加高效,通过模板模式或录制工具提供配置式和录制式编写HTTP测试脚本的功能。用户在页面上配置的HTTP脚本在执行前会被自动转换成Jython脚本,用户可以通过预览或者手工编辑查看自动转换后的脚本代码内容。通过模板模式和录制工具来编写HTTP脚本非常方便,但是如果遇到一些比较复杂的业务场景,比如定制化的用户登录,可配置的执行流程或者需要对请求进行动态关联等等,就需要用户通过手工编写脚本的方式来实现测试逻辑。

  本文围绕在Lite中使用手工编写方式这个主题,重点介绍性能测试脚本所用到的一些Jython语言基础、测试脚本框架、常用类和函数,最后介绍一些常用的脚本功能范例。用户通过阅读该指南能够在Lite中手工编写较为复杂的性能测试脚本。

2.Jython语法基础

2.1语言基础

  本章节重点介绍和编写性能测试脚本相关的Jython语言基础,内容非常有限。读者如果需要更全面、深入地解Jython语言可以首先参考Python的Tutorial,语言Reference和Jython的用户手册。

2.2Jython的词法

  Jython程序是由一系列语句组成的,语句组成了代码块,代码块组成了方法、函数,然后再通过类把数据、方法和函数封装起来。和其它高级语言一样,Jython的语句也是有一些最基本的词(token)组成的。Token可以是标识符(identifiers)、关键字(keywords)、字面值(literals)、操作符(operators)和分割符(delimiters)。这些token通过Jython的语言执行器进行词法分析产生,而词法分析器通过字符方式读入Jython脚本文件,这时就涉及到文件编码问题。

2.3文件编码

  Jython默认以ASCII码(Latin-1)的方式读入脚本文件,如果你的文件中存在非7位ASCII编码的字符就会发生错误。我们通过在脚本文件首行添加以下代码来让Jython分析器知道用那种编码来读入脚本文件:

# -*- coding: utf-8 -*-

以上指示表示该Jython脚本的编码为UTF-8,这样我们就可以在脚本中使用中文这样的Unicode字符。

2.4代码行

  Jython脚本由逻辑代码行组成,逻辑行可以是一个物理行,也可以是多个物理行通过显式或隐式连接起来的。而物理行就是文件中以NEWLINE结尾的行,NEWLINE在不通的平台上各有不一,比如UNIX系统中以LF(linefeed)作为换行,Windows中已(CRLF)作为换行而Mac中以CR作为换行,这些换行符在Jython中都支持。

if 1900 < year < 2100 and 1 <= month <= 12 
   and 1 <= day <= 31 and 0 <= hour < 24 
   and 0 <= minute < 60 and 0 <= second < 60:   # Looks like a valid date
        return 1

显示连接用在一行语句太长,需要分多行显示的情况,我们可以通过’’,反斜杠来将多个行连接成一条语句:

month_names = ['Januari', 'Februari', 'Maart',      # These are the
               'April',   'Mei',      'Juni',       # Dutch names
               'Juli',    'Augustus', 'September',  # for the months
               'Oktober', 'November', 'December']   # of the year

2.4代码块

  代码块可以是一个类,一个方法/函数,或者是一个if/while的控制单元。多数程序语言会用专门的开始结束标志来表示一个代码块,但是Jython/Python却通过缩进来表示一个代码块,这是初学Python的人最不适应的地方。缩进可以使用空格或者Tab制表符,但是最后Jython在解析的时候会将Tab转换成空格,而不通的机器Tab和空格的对应关系可能不一样,比如Windows通常使用4个空格表示一个Tab,而Unix可能用8个空格。

  所以非常重要的是我们在编辑器中只使用一种缩进方式:要不都用空格,要不都用缩进。我的通常做法是打开Eclipse的Tab自动转空格功能,在本地编辑就直接都用空格来缩进Jython脚本。开启一个代码块需要用‘:’来提示。

正确的示范:

def perm(l):
    # Compute the list of all permutations of l
    if len(l) <= 1:
        return [l]
    r = []
    for i in range(len(l)):
        s = l[:i] + l[i+1:]
        p = perm(s)
        for x in p:
            r.append(l[i:i+1] + x)
    return r

错误的示范:

 def perm(l):                       # error: first line indented
for i in range(len(l)):             # error: not indented
        s = l[:i] + l[i+1:]
            p = perm(l[:i] + l[i+1:])   # error: unexpected indent
            for x in p:
                r.append(l[i:i+1] + x)
            return r                # error: inconsistent dedent

2.5标识符

  ython的标识符(类名、变量名等)只能以下划线或者字母开头,后面跟字母、数字或者下划线,其他任何字符都是非法的标识符。标识符区分大小写、类和方法。

  用户可以定义一个类把数据和操作封装起来。比如下面这段代码定义了一个TestRunner的类,这个类有4个方法:init、call、action_20013805。Jython定义方法用到关键字def,一般”双划线”格式的方法都是语言内置的特殊方法,比如init方法是类的初始化方法,当一个类被实例化的时候该方法会被用调用一次,来初始化类的一些成员数据;call方法用来实现一个类的callable接口,这个一般会在多线程调用时用到。

  Lite用来测试的业务主体也是写在这个方法里面,这样就可以多次循环的调用业务逻辑。del方法类似Java的finalize方法,通常在类的对象消亡之前执行一些销毁操作,但是这个并不是实时的。

class TestRunner:

    # TestRunner对象的初始化方法,每个线程在创建TestRunner后执行一次该方法
    def __init__(self):
        self.threadContext = PTS.Context.getThreadContext()

    # 主体压测方法,每个线程在测试生命周期内会循环调用该方法
    def __call__(self):
        PTS.Data.delayReports = 1

        statusCode = self.action_20013805()
        PTS.Framework.setExtraData(statusCode)

        PTS.Data.report()
        PTS.Data.delayReports = 0

    def __del__(self):
        self.end = u'结束'

    def action_20013805(self):
        statusCode = [0L, 0L, 0L, 0L]

        headers = []
        result = HTTPRequest().GET(u'https://xx.xx.xx.xx:8080/examples/servlets/servlet/RequestInfoExample',[],headers)
        PTS.Framework.addHttpCode(result.getStatusCode(), statusCode)
        if(300 <= result.getStatusCode()):
            PTS.Data.forCurrentTest.success = False
        return statusCode

# 调用施压引擎施压。第一个参数是事务名,可以为中文;第二个参数是执行事务方法的方法名;第三个统一写TestRunner
PTS.Framework.instrumentMethod(u'requestInfo', 'action_20013805', TestRunner)

  看上面这段代码,我们还注意到Jython与Java或者C这些强类型语言的一些区别:变量的申明不需要指定类型,同样方法或者函数的入参和返回值也不需要制定类型,这就是Jython/Python语言的另一大特性——弱类型,Jython执行器只有在执行的时候才回去判断对象的类型,如果有不匹配的操作会抛出异常。

  细心的读者还会发现一个怪怪的self,其实self就类似Java语言中的this,通过self类中的方法/函数可以引用类的成员变量,或者调用类的成员方法,如果不加self,则默认使用全局空间的变量或方法,所谓全局变量/方法就是定义在类之外,脚本中顶格写的变量或者方法。定义类成员函数时,必须把self作为第一个参数传递给所定义的方法。

2.6字符串

  Jython中的字符串是被单引号、双引号引起来的字符集,单引号括起来的字符串中可以包含双引号而不需要用转义,同样,双引号重的单引号也不需要转义。

  比如:s = ‘This is a “BIG” surprise!’,这个是一个合法的字符串。字符串作为一种特殊的字符数组,支持下标操作,比如s[0]=’T’,s[0:5]=’This’。

  字符串还有很多其它的操作比如连接、比较、分割,可以参考: https://docs.python.org/2/tutorial/introduction.html#strings

2.7列表

  Jython中用[]来表示一个列表,列表是可变的,比如: a = [‘apple’, ‘orange’, ‘peach’]

  数组下标从0开始,a[0]=’apple’;a[1]=’orange’。获列表长度用Jython内置函数:len,比如len(a)=3。

  更多列表操作,请参考:https://docs.python.org/2/tutorial/introduction.html#lists

2.8脚本的执行

  Jython是解释执行语言,所以一个脚本被Jython引擎分析后,是按行执行的。脚本中顶格的代码行都是会被执行器执行的,而类或者全局方法则会被定义,定义之后代码中其他的地方可以使用这些类和方法。

1.一般脚本的前几行都是用来注释和作指引(比如字符编码):

#! /usr/bin/env python
# -*- coding: utf-8 -*-
# PTS Script Version 1.0
# Trunner auto-generated test script at Mon Jul 13 17:57:10 CST 2015

2.接下去回有一些import语句来导入脚本中所用的地其它类或者模块: Jython中的模块(Module)类似Java中的Package,用来把一组功能耦合的类封装在一起。由于Jython可以和Java会操作,所以这里的import语句也可以直接导入Java的package或者类。比如:

from java.lang import String
from java.util import Random
from java.util import Date

# PTS脚本SDK:框架API、常用HTTP请求/响应处理API
from util import PTS
from HTTPClient import NVPair
from HTTPClient import Cookie
from HTTPClient import HTTPRequest
from HTTPClient import CookieModule
from HTTPClient import ShutdownException

3.然后可以在脚本中定义一些类或者方法:

class TestRunner:

    # TestRunner对象的初始化方法,每个线程在创建TestRunner后执行一次该方法
    def __init__(self):

    # 主体压测方法,每个线程在测试生命周期内会循环调用该方法
    def __call__(self):

    def __del__(self):

    def action_20013805(self):

4.也可以执行一些语句:

# 脚本初始化段,可以设置压测引擎的常用HTTP属性ttt
PTS.HttpUtilities.setUrlEncoding('UTF-8')
PTS.HttpUtilities.setFollowRedirects(True)
PTS.HttpUtilities.setTimeout(120000)
# PTS.HttpUtilities.setKeepAlive(False)
# PTS.HttpUtilities.setUseCookieModule(False)
# PTS.HttpUtilities.setProxyServer('localhost', 8888)
# PTS.Context.setParamDirectory("/Users/fei/Work/trunner/data")

3脚本框架

  Lite的代码脚本是一个TestRunner类,这个类会被每一个并发线程初始化,类成员变量是线程安全的。测试进程首先加载脚本,并且执行脚本中顶格的语句,同时定义了TestRunner这个测试类;然后每个线程会实例化一个TestRunner类,调用类中的init方法一次,继而循环调用TestRunner类的call方法;最后线程结束时,会调用类中的del方法。init和del方法都是可选的,只有call方法是必需的。

所以一个Lite代码脚本的总体框架如下:

第一部分:执行器声明和脚本编码声明及脚本创建信息

#! /usr/bin/env python
# -*- coding: utf-8 -*-
# PTS Script Version 1.0
# Trunner auto-generated test script at Mon Jul 13 17:57:10 CST 2015

第二部分:Jython类库、Java类库和自定义类的导入

from java.lang import String
from java.util import Random
from java.util import Date

# PTS脚本SDK:框架API、常用HTTP请求/响应处理API
from util import PTS
from HTTPClient import NVPair
from HTTPClient import Cookie
from HTTPClient import HTTPRequest
from HTTPClient import CookieModule
from HTTPClient import ShutdownException
# 支持socket测试, 如TCPUDP等协议
# import socket

# 设置系统编码
import sys

第三部分:测试进程级别的脚本语句和初始化

# 脚本初始化段,可以设置压测引擎的常用HTTP属性ttt
PTS.HttpUtilities.setUrlEncoding('UTF-8')
PTS.HttpUtilities.setFollowRedirects(True)
PTS.HttpUtilities.setTimeout(120000)
# PTS.HttpUtilities.setKeepAlive(False)
# PTS.HttpUtilities.setUseCookieModule(False)
# PTS.HttpUtilities.setProxyServer('localhost', 8888)
# PTS.Context.setParamDirectory("/Users/fei/Work/trunner/data")

第四部分:TestRunner测试类

class TestRunner:

    # TestRunner对象的初始化方法,每个线程在创建TestRunner后执行一次该方法
    def __init__(self):
        self.threadContext = PTS.Context.getThreadContext()


    # 主体压测方法,每个线程在测试生命周期内会循环调用该方法
    def __call__(self):
        PTS.Data.delayReports = 1

        statusCode = self.action_20013805()
        PTS.Framework.setExtraData(statusCode)

        PTS.Data.report()
        PTS.Data.delayReports = 0

    def action_20013805(self):
        statusCode = [0L, 0L, 0L, 0L]

        headers = []
        result = HTTPRequest().GET(u'https://xx.xx.xx.xx:8080/examples/servlets/servlet/RequestInfoExample',[],headers)
        PTS.Framework.addHttpCode(result.getStatusCode(), statusCode)
        if(300 <= result.getStatusCode()):
            PTS.Data.forCurrentTest.success = False
        return statusCode

第五部分:Instrument语句

# 调用施压引擎施压。第一个参数是事务名,可以为中文;第二个参数是执行事务方法的方法名;第三个统一写TestRunner
PTS.Framework.instrumentMethod(u'requestInfo', 'action_20013805', TestRunner)

访问Lite控制台

最后更新:2016-05-06 10:44:42

  上一篇:go 录制测试脚本__脚本开发_Lite用户使用手册_性能测试-阿里云
  下一篇:go Lite脚本编写SDK__手工脚本编写_Lite用户使用手册_性能测试-阿里云