本节主要涉及PyRestTest的高级特征的详细使用,主要指:generators(生成器), variable binding(变量绑定), data extraction(数据提取), content validators(文本验证)

它们是如何组合在一起的?

模板和上下文

  • 测试和基准测试可以使用变量来模板化动态配置。
  • 使用基础的Python string templating进行模板化
  • 模板使用包含在上下文中的变量,而且每次测试运行或基准测试迭代都会对模板进行更新。
  • 上下文要么传递到测试中,要么在测试中创建(如果没有提供)
  • 上下文在TestSet是持久的。一旦在set中设置一个变量,该变量可以在当前set中的所有测试用例中使用。
  • 可以通过下面3个方法跳调整上下文变量:
    1. 在TestSet的config或Test中使用variable_binds进行申明并赋值
    2. 在test中使用generator binds设置变量来获取generator的值
    • generator必须在TestSet config中按名称声明才能使用它们
    • generator绑定的值在每次HTTP调用时更新
      • 一个测试用例只更新一次,一个Benchmark更新多次
    • generator绑定的变量每次在Test/Benchmar中声明后会被重新更新赋值。一旦生成器生成数值,该数值能在所有子测试用例中使用(除非这个变量与其他变量绑定)
      1. 在一个test中可以使用extract_binds对HTTP响应报文进行数据提取
    • 如果请求失败,变量中是没有设定数值的
    • 不支持在benchmark进行变量提取,因为benchmark应该是相互独立的,进行数据抽取是没有必要的

Templating(模板), Generators(生成器), Binding (绑定)示例

如果你想benchmark创建/更新一组用户,但是用户必须要有唯一的ID进行登录,你会怎么做呢?

可以使用数字序列的生成器(generator),然后把生成器生成的数值绑定到一个PUT请求benchmark的ID域中。

为了演示静态变量的绑定,如下示例对first、lastname进行了绑定,示例文本内容如下:

---
- config:
    - testset: "Benchmark tests using test app"
    # Variables to use in the test set
    - variable_binds: {firstname: 'Gaius-Test', lastname: 'Baltar-Test'}
    # Generators to use in the test set
    - generators:  
        # Generator named 'id' that counts up from 10
        - 'id': {type: 'number_sequence', start: 10}

- benchmark: # create new entities
    - generator_binds: {user_id: id}
    - name: "Create person"
    - url: {template: "/api/person/user_id/"}
    - warmup_runs: 0
    - method: 'PUT'
    - headers: {'Content-Type': 'application/json'}
    - body: {template: '{"first_name": "firstname","id": "user_id","last_name": "lastname","login": "test-login-$id"}'}
    - 'benchmark_runs': '1000'
    - output_format: csv
    - metrics:
        - total_time: total
        - total_time: mean

当前tempate只支持请求体、请求URL、请求头。

生成器概述

下表给出了所有 generator及它们的配置元素(必须、可选以及含义):

含义 在YAML的名称 输出类型 参数
获取宿主机的环境变量的值 env_variable any required: ‘variable_name’, type: string (环境变量使用不需要添加前缀( $ 或 % )**
重置环境变量值 env_string string required: ‘string’, type: string(可以根据需要对环境变量中的值进行组合拼接获取需要的字符变量,获取系统环境变量需要前缀$)
类型为整形的序列 number_sequence integer optional: ‘start’, type: integer, default: 1 optional: ‘increment’, type: integer, default 1
随机整数(32位) random_int integer
随机字符串 random_text string optional: ‘character set’ OR ‘characters’, type: string, default: string.ascii_letters optional: ‘min_length’, type: integer, default: 8 optional: ‘max_length’, type: integer, default: 8 optional: ‘length’, (can either have length or min/min), type integer
随机获取给定list中的数值 choice any required: ‘values’, type: array of anything
给定一个有序列表,顺序获取给定列表的值 fixed_sequence any required: ‘values, type: array of anything

Generators简介

env_variable

该变量名是使用宿主机定义的环境变量名。例如:如果你在shell脚本中使用宿主机的host可以通过$HOST获取,但如果使用env_variable类型的生成器,只需要在variable_name值设置为‘HOST’就可以。

{type: 'env_variable', 'string': "HOST"}

env_string

可以根据业务需要把多个系统环境变量与其他元素组合成一个字符串值。例如:

在shell脚本中:

echo "USER logged intoHOSTNAME"

是这样,在env_string类型的生成器变成了这样:

{type: 'env_string', 'string': "USER logged intoHOSTNAME"}

random_text

生成随机的字符串,需要指定下列选项:

  • 合法的字符长度:
    • length:设置一个常量,指定长度
    • min_lengthmax_length:允许生成字符串的长度范围区间
  • 使用有效的字符,可以按下面两种方法定义:
    • characters:指定使用生成的有效字符,characters类型为字符型。示例:characters:’abceefj’
    • character_set: 指定使用的字符集。示例如下:character_set:string.ascii_letters.支持的字符集如下表:
描述 文本名称 来源
ASCII 码:大小写字母,不包括空格 ascii_letters Python internal
ASCII 码:小写字母,不包括空格 ascii_lowercase Python internal
ASCII码:大写字母,不包括空格 ascii_uppercase Python internal
数字: 0-9 digits Python internal
十六进制数字、大小写字母的组合 hexdigits Python internal
十六进制数字, 所有小写字母 hex_lower string.digits+abcdef,
十六进制数字, 所有大写写字母 hex_upper string.digits+ABCDEF,
字母 letters Python internal, locale-dependent
小写字母 lowercase Python internal, locale-dependent
八进制数字 (0-7) octdigits Python internal
标点符合, 管道符及 !”#$%&'()*+,-./:;?@[]^_`{}~ punctuation Python internal
所有可打印字符包括空格 printable Python internal, locale-dependent
大写字母 uppercase Python internal, locale-dependent
空格 whitespace Python internal, locale-dependent
URL字符(ASCII小写字母和破折号/) url.slug string.ascii_lowercase + string.digits + ‘-‘
URL Safe (RFC3986中的未保留字符) url.safe string.ascii_letters + string.digits + ‘-~_.’
字母数字 alphanumeric string.ascii_letters + string.digits
字母数字(只有小写字母) alphanumeric_lower string.ascii_lowercase + string.digits
字母数字(只有大写字母) alphanumeric_upper string.ascii_uppercase + string.digits

extractor

提取器是基于查询的方法,用于提取HTTP响应文本的某些部分以供使用。提取的值可以用作验证器的一部分,或者将提取的值用在上下文变量定义以供后续使用。

当前提取器存在一定的局限性,但是这些函数是插拔式-可以很容易增加regex matching, xpath, xquery等提取函数,然后在测试中使用这些函数。

提取器的定义样式如下:

extractor_name: extractor_configuration

提取器配置可以是简单的字符串查询、模板化( templated)字符串或更复杂的对象。如何处理这个问题完全取决于提取器解析函数

提取器类型: jsonpath_mini

基本的jsonpath_mini提取器提供了一个对 JsonPath简单提取,例如从JSON中抓取数据功能,该提取器不依赖外部扩展包。

该语法的元素是键或索引的列表,它们从树上降下来,由句点分隔。假定数字为数组索引。

如果你期望获取所有对象,你可以使用一个空字符“”查询或者使用“.”查询—-这对于返回对象数组的API是很有用,你可以随处对返回对象数目进行统计(使用countEq操作符)

示例:JSON

{
    "thing":{"foo":"bar"},
    "link_ids": [1, 2, 3, 4],
    "person":{
        "firstname": "Bob",
        "lastname": "Smith",
        "age": 17
    }
}
  • 查询条件: ‘person.lastname’
    查询返回结果: “Smith”

  • 查询条件: ‘person.is_a_ninja’
    查询返回结果: NOTHING (没有对象) — 因为在person中未定义is_a_ninja.

  • 查询条件: ‘link_ids.1’
    查询返回结果: 2

  • 查询条件: ‘thing’
    查询返回结果: {“foo”:”bar”}

如果你在templates中出现类似 {u’foo’: u’bar’} 样式元素,该元素将被转换成一个python字典。如果要在其他测试中使用它,则需要分别提取组成元素。

  • 查询条件: ‘.’
    查询返回结果: 整个响应文本 (作为一个python对象)。如果要对其进行包含或计数操作,这个查询是非常有用的。

  • 查询条件: ‘thing.0’
    查询返回结果: None — 技巧问题, ‘thing’ 不是一个数值

该提取器也支持模板template:

jsonpath_mini: {template: $keyname.age}
  • 如果变量keyname被设置为person,则返回17
  • 如果keyname被设置为‘thing’,将返回为Nnone(因为thing对象没有age这个key)

提取器类型: jmespath

jmespath提取器提供了全量的 JMESPath实现,通过该提取器从JSON获取数据,并且需要导入jmespath库。支持全部的JMESPath表达式

示例: JSON
{
   "test1" : {"a": "foo", "b": "bar", "c": "baz"},
   "test2" : {"a": {"b": {"c": {"d": "value"}}}},
   "test3" : ["a", "b", "c", "d", "e", "f"],
   "test4" : {
      "a": {
        "b": {
          "c": [
            {"d": [0, [1, 2]]},
            {"d": [3, 4]}
          ]
        }
      } },
   "test5" : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
   "test6" : {
      "people": [
         {"first": "James", "last": "d"},
         {"first": "Jacob", "last": "e"},
         {"first": "Jayden", "last": "f"},
         {"missing": "different"}
      ],
      "foo": {"bar": "baz"}
   },
   "test7" : {
      "ops": {
         "functionA": {"numArgs": 2},
         "functionB": {"numArgs": 3},
         "functionC": {"variadic": 4}
      }
   },
   "test8" : {
      "reservations": [
         { "instances": [ {"state": "running"}, {"state": "stopped"} ] },
         { "instances": [ {"state": "terminated"}, {"state": "runnning"} ] }
      ]
   },
   "test9" : [ [0, 1], 2, [3], 4, [5, [6, 7]] ],
   "test10" : { "machines": [ {"name": "a", "state": "running"}, {"name": "b", "state": "stopped"}, {"name": "c", "state": "running"} ] },
   "test11" : {
      "people": [
         { "name": "b", "age": 30, "state": {"hired": "ooo"} },
         { "name": "a", "age": 50, "state": {"fired": "ooo"} },
         { "name": "c", "age": 40, "state": {"hired": "atwork"} } ]
   } ,
   "test12" : { "myarray": [ "foo", "foobar", "barfoo", "bar", "baz", "barbaz", "barfoobaz" ] }
}

提取器类型: header

从HTTP响应提取HTTP请求头的值,该值可以通过比较或提取测试进行测试。HTTP请求头是大小写不敏感的,‘content-type’‘Content-Type’ 是一样的。如果header中定义多个值,将返回一个list。

提取示例1:

header: 'content-type'

比较示例2:

compare: {header: 'content-type', expected: 'application/json'}

提取器类型: raw_body

用于提取HTTP响应文本的raw数据。该值可以通过比较或提取测试进行测试。

验证

Validators主要验证响应文本的正确性。它们使用提供的上下文在响应文本上执行测试,并返回一个值,该值类型为布尔型,会返回True或False。可选择性,当验证结果为False的时候,让validator返回一个Failure并提供其他详细信息。

当前 Validators

提取并测试值:extract_test

  • 名称extract_test
  • 描述:提取器从响应文本中提取一个数值并使用一个函数对该值进行测试。
  • 参数:
    • 提取器: 定义一个提取,可以参考提取器, , 名称为提取器类型
    • test: 指定测试函数, 该函数返回 true 或 false

示例:

- validators:
    # Test key does not exist
    - extract_test: {jsonpath_mini: "key_should_not_exist",  test: "not_exists"}

测试函数

‘exists‘, ‘not_exists‘ – 检查值是否存在

抽取并比较:compare

  • 名称: comparator‘或compare‘或 assertEqual
  • 描述:提取并把提取值与预期结果进行比较
    • encodings特别提醒: 当测试raw二进制的body且预期结果值是string类型时,预期结果值会自动进行UTF-8的encoded之后再进行比较。,如果使用一个bytes的YAML出现这个问题,请查看编码,避免使用隐含式编码。
  • 参数:
    • 提取器: 定义一个提取,可以参考提取器, , 名称为提取器类型
    • comparator: 指定一个comparator函数,该函数返回值为 true或 false
    • expected:
    • 预期结果值(文字)
    • template: {template: ‘template_string’} – 如果要对数值进行模板化,则需要使用“ str_eq”比较器。
    • 定义一个提取器。可以对两个响应文本进行比较

示例:

- validators:
     # Check the user name matches
     - compare: {jsonpath_mini: "user_name", comparator: "eq", expected: 'neo'}

     # Check the total_count key has value over 10
     - compare: {jsonpath_mini: "total_count", comparator: "gt", expected: 10}

     # Check the user's login
     - compare: {jsonpath_mini: "total_count", comparator: "gt", expected: }

Comparator 函数:

Name(s) Description Details for comparator(A, B)
‘count_eq’,’length_eq’ 检查 body/str长度或对相等的数值进行计数 length(A) == B or -1 if cannot obtain length
‘lt’, ‘less_than’: 小于 A B
‘contains’ 包含 B in A
‘contained_by’ 被包含 A in B
type’ 变量类型判断 A instanceof (at least one of) B
‘regex’ 正则相等 A matches regex B

Types for type test:

类型匹配映射:

TYPES = {
    'null': type(None),  # JSON null type
    'none': type(None),  # JSON null type
    'number': (int, long, float),  # JSON number type
    'int': (int, long),  # JSON number type if not a float
    'float': float, # JSON number type if a float
    'boolean': bool,   # JSON boolean
    'string': basestring,  # JSON string
    'array': list,  # JSON array
    'list': list,  # JSON array
    'dict': dict,  # JSON Object
    'map': dict,  # JSON Object
    'scalar': (bool, int, long, float, basestring, None),  # JSON any type but object or array
    'collection': (list, dict, set)  # JSON array or object
}

JSONSchema Validator (可选)

该验证器需要python安装jsonschema,未安装该模块,将不能使用该验证器。

  • 名称:json_schema
  • 描述:该验证器使你可以针对 JSON Schema验证请求,该请求可以位于测试正文中,也可以位于外部文件中(根据请求正文)。
  • 参数:
    • schema – 用于验证请求正文的JSON模式

示例:
验证针对一个文件为: ‘miniapp-schema.json’示例

---
- test:
    - url: /api/person/1/
    - validators:
        - json_schema: {schema: {file: 'miniapp-schema.json'}}

不同操作的生命周期

TestSet 执行的生命周期

  1. 解析命令行参数
  2. 解析YAML,
  3. Parse YAML, 读取导入外部文件并构建 TestSets
  4. 执行TestSets:
    1. 每个 TestSets生成一个上下文,使用TestConfig中定义的generators和变量进行填充。
    2. 使用上下文运行测试集中的每个测试
    • 当出现输出failures ,结束退出
      1. 将来自该测试的统计信息添加到该测试组的信息中
      2. 在测试集中运行benchmarks ,将结果写入文件
  5. 打印出收集的测试结果,按测试组名称分组
  6. 退出,返回失败测试的响应代码

普通Test的生命周期

  1. 在测试之前更新上下文信息(在Test中使用update_context_before方法)
    1. 绑定在Test中定义的变量到上下文中
    2. 绑定在Test中定义的generator值的变量到上下文中
  2. 模板化(Templating):对测试的最终版本进行realize() ,读取延迟加载的文件
  3. 对一个Curl调用进行配置,配置信息来自test及configure_curl
  4. 如果是交互模式:输出信息并等待响应
  5. 执行curl调用
  6. 在测试(抽取)之后更新上下文
  7. 运行验证
  8. 构造并返回一个TestResponse()

一般Benchmark的生命周期

  1. 前置处理 (设置有效存储指标)
  2. 设置系统预热运行次数(warmup_runs)
    1. 测试之前更新上下文(绑定变量和generator)
    2. 实现测试模板
    3. 对一个Curl调用重新配置(如果可能,curl对象可以被重用)
    4. 运行Curl
  3. 设置Benchmark运行次数(benchmark_runs)
    1. 测试之前更新上下文(绑定变量和generator)
    2. 实现测试模板
    3. 对一个Curl调用重新配置(如果可能,curl对象可以被重用)
    4. 运行Curl
    5. 收集metrics (把metrics添加到一个数组)
  4. 后置处理:分析benchmark结果,对数组进行聚合,并生成BenchmarkResult对象

benchmarks关键点:

  • Benchmark尽可能少做:不做验证提取
  • 为获得最准确的结果,Benchmark不要对HTTP响应报文进行存储
  • 它们不对当前响应状态码进行检查(这在可能会长后续版本加上)
  • Benchmark跟踪静态故障计数,以解决网络问题
  • Benchmark将尝试使用尽可能安全的方法优化模板。

发表评论

电子邮件地址不会被公开。