如何使用PieceofScript“ bike”开发和测试API

PieceofScript是一种简单的语言,用于编写用于自动测试HTTP JSON API的脚本。

PieceofScript允许您执行以下操作:

  • 以几乎自然的语言描述YAML格式的API方法,并以该方法的名称来表示,这便于阅读测试
  • 足够灵活以YAML格式描述模型并从中生成随机数据
  • 用简单易懂的语言和简单的语法编写复杂的API调用脚本
  • 获得JUnit和HTML格式的测试结果

我之所以写这个“自行车”,是因为SoapUI界面使我失望了。 我想在没有特殊GUI的文本编辑器中简单清晰地描述测试。 另外,git不会消化SoapUI发出的庞大的xml文件,因此很难将特定任务的测试放在完成任务本身的同一分支中。 Postman界面要好得多,但是在开发时,要花很多时间在这里编写/修改请求并按正确的顺序重复它们。 我想自动化。 我还研究了其他测试工具,每个工具都有一个“ 致命缺陷 ”,因此为了适应NIH综合症,我打开了一个IDE。

这就是它的来历。



解释器是用PHP编写的,是phar存档;它需要PHP 7.2版本,尽管它也可以在7.1上运行。 源代码和文档https://github.com/maximw/PieceofScript 。 开发中的文档。 事实证明,这是最困难和乏味的部分。

测试项目,其结构和启动
测试脚本
API测试方法
API方法调用
模型和测试数据的生成
内建功能
测试用例
变量和范围
类型和操作
运行之间保存数据
输出到标准输出和报告
示例 -足够的单词,显示代码!
未来的评论和计划(如果有)

测试项目,其结构和启动


该项目是一个目录,其中包含一组脚本文件,API方法描述文件和测试数据生成器。

在最低版本中,项目如下所示:

./tests endpoints.yaml -  API generators.yaml -  start.pos -    

启动文件是开始测试过程的脚本。 它在启动时设置:

 pos.phar run ./start.pos --junit=junit_report.xml -vvv --config=config.yaml 

从包含启动文件的工作目录中读取所有相对路径。
可以在命令行中使用--config选项指定配置文件,或者将config.yaml放在工作目录中。 该配置是可选的,您需要根据需要进行爬升。 有关配置的更多信息

测试脚本


对于我自己,我决定在扩展名为.pos的文件中编写脚本,以便您可以在扩展名为IDE的代码中进行代码突出显示设置。 但是解释器对扩展完全不关心。

这是一个虚构论坛的简单脚本示例,其中执行了由不同用户创建和阅读帖子的测试。

 require "./globals.pos" //    ,  $domain include "./globals_local.pos" //     ,    include "./user/*.pos" //       ./user  ./post include "./post/*.pos" //       var $author = User() var $reader = User() var $banned = User() var $post = Post() Register $author //   API,     must $response.code == 201 //     Register $reader must $response.code == 201 Register $banned must $response.code == 201 Add $banned to blacklist of $author //$banned      $author Create post $post by $author //   API   must $response.code == 201 //    API   201 // ...,   ,       assert $response.body.post.content == $post.content var $postId = $response.body.post.id // Id     Read post $postId by $author //     must $response.code == 200 assert $response.body.post.content == $post.content Read post $postId by $reader //      must $response.code == 200 assert $response.body.post.content == $post.content Read post $postId by $banned //        assert $response.code == 404 

是的,如果没有背光,它看起来不会很好。

脚本的每一行都以运算符开头,或者是对API方法的调用。 如果突然,API方法的名称以与其中一个运算符匹配的单词开头,则可以使用“ > ”符号:

 >Include $user to group $userGroup 

运算符不区分大小写。 断言,ASSERT或aSsErT(但是为什么要这样写?)将起作用。
每个语句或API方法调用必须位于单独的行上。 但是,如果字符串的最后一个字符为\ (您好,Python),也可以换行。

关于换行和缩进的无趣细节
如果注释中使用换行,则下一行也将被视为注释的一部分。 将行包装在块内( testcaseifwhileforeach )时,重要的是缩进 ,以使下一行落入同一块中。

 var $n = 20 var $i = 2 var $fib1 = 1; \ $fib2 = 1 while $i <= $n var $fib_sum = \ $fib2 + $fib1 print toString($i) + "  :" + \ toString($fib_sum) var $fib1 = $fib2 var $fib2 = $fib_sum var $i = $i + 1 

当执行块语句( testcaseifwhileforeach )时,块由其行的缩进确定。 缩进被计算为一行开头的空白数。 空格和制表符都视为一个字符,但是制表符通常在编辑器中显示为多个空格。 因此,为避免混淆,最好使用制表符或空格,但不能同时使用。

运营商完整列表


require fileName-将文件附加到调用运算符的位置。 附件将立即从第一行开始。 完成后,解释器将返回源文件的下一行。 如果请求的文件不可读,将产生错误。 相对路径是从工作目录中计算出来的。

include fileMask-与require相似,但是如果请求的文件不可读,则不会有错误。 例如,这对于为不同的测试环境创建设置非常方便。 另外,include可以一次通过掩码连接所有文件。 因此,例如,您可以下载包含测试用例的文件的整个目录。 但是同时,不能保证以任何顺序下载文件。

var $ variable1 = expression1 ; $ variable2 = expression2 ; ...; $ variableN = expressionN-为变量分配值。 如果该变量尚不存在,它将在当前上下文中创建。

$ variable1 = expression1 ; $ variable2 = expression2 ; ...; $ variableN = expressionN-为变量分配值。 如果该变量不在当前上下文中,则将尝试在全局上下文中创建或修改该变量。

const $ const1 = expression1 ; $ const2 = expression2 ; ...; $ constN = expressionN-设置当前上下文中的常量值。 常量和变量之间的区别仅在于它们无法更改;当您尝试为常量分配值时,在声明之后将发出警告。 如果已经有一个同名的变量,则在尝试将其声明为常量时将生成错误。 否则,变量所适用的所有内容对常量也适用。

导入$ variable1 ; $ variable2 ; ...; $ variableN-将变量从全局复制到当前上下文。 如果您需要对全局变量的值进行操作而不需要更改它,则它可能会很有用。

testcase testCaseName-宣布一个测试用例,然后可以使用run语句将其称为一个单元。 在本文的后面阅读更多关于测试用例的信息

断言表达式 -检查表达式是否为true ,否则打印有关失败测试的报告。

must 表达式assert相同,仅当测试失败时,当前测试用例才会停止。 在测试用例的上下文之外,脚本将完全终止。 如果发现错误而无法进行进一步的检查,则可以使用它。

运行testCaseName-运行指定的测试用例以执行。 不指定测试用例名称的情况下运行,将启动所有不需要按照声明顺序进行参数声明的测试用例。

while expression-一个循环,而expression为 true时,执行缩进行的语句比while更为有效。

foreach $数组 ; $ element-遍历数组,对数组的每个下一个元素执行循环体。 还可以获取密钥foreach $ array ; $键 ; $元素$ key$ element变量在当前上下文中创建/覆盖。

if expression-如果expression为 true,则执行缩进行的语句比if多

打印expression1 ; expression2 ; ... expressionN-将expressionM的值打印到stdout。 它可用于调试,仅在“健谈”级别--verbosity = 1-v及更高级别下起作用。

睡眠表达式 -停顿给定的数字(可以选择整数)秒。 有时您需要让经过测试的API休息一下。

暂停表达式 -不处于交互模式(命令行选项-n )类似于sleep表达式是可选的,在这种情况下不会暂停。 并且在交互模式下,在按Enter之前先暂停。

取消 -结束测试。 口译员完成工作,创建报告。

API测试方法


这实际上是您需要测试的内容-使用某些参数进行调用并检查答案是否符合期望。

API方法以YAML格式描述。 默认情况下,描述应在当前目录的endpoints.yaml文件中和/或在其./endpoints子目录的* .yaml文件中。 在测试之前,解释器将尝试一次读取所有这些文件。

endpoints.yaml结构示例:

 Auth $user: method: "POST" url: $domain + "/login" headers: Content-Type: "application/json" format: "json" data: login: $user.login password: $user.password after: - assert $response.code == 200 - let $user.auth_token = $response.body.auth_token Create post $post by $user: method: "POST" url: $domain + "/posts" format: "json" data: $post headers: auth: "Bearer " + $user.auth_token content-type: "application/json" after: - assert $response.code == 201 Read post $postId by $user: method: "GET" url: $domain + "/posts/" + $postId headers: auth: "Bearer " + $user.auth_token content-type: "application/json" after: - assert ($response.code == 200) || ($response.code == 404) Create comment $comment on $post by $user: method: "POST" url: $domain + "/comments/create/" + $post.id format: "json" data: $comment headers: auth: "Bearer " + $user.auth_token content-type: "application/json" after: - assert $response.code == 201 

可以用来调用的API方法的名称(YAML结构的顶层)是几乎任意格式的字符串。

可以在名称中的任何位置指定参数。 它们之间应以空格隔开。 例如,最后一个方法中的$ comment$ post$ user

另外,在名称中的任何位置都可以在双花括号中指定可选的方法值。

 Get comments of $post {{$page=1; $perPage=$defaultGlobalPageSize}}: method: "GET" url: $domain + "/comments/" + $post.id query: page: $page per_page: $perPage headers: auth: "Bearer " + $user.auth_token content-type: "application/json" after: - assert $response.code == 200 

在指定可选值的表达式中,全局上下文变量可用。
可选值很有用,因此您不必在每次调用API方法时都指定它们。 如果只需要在一个地方更改页面大小,为什么要在所有其他地方指示它? 对该方法的调用示例:

 Get comments of $newPost //     $page  $perPage Get comments of $newPost {{$page=$currentPage+1}} Get comments of {$newPost} {{$perPage=10;$page=100}} 

其余的变量(上例中的$域 )将从全局上下文中获取。 稍后我会告诉您更多有关上下文的信息

在我看来,以自然语言为API方法提供人类可读的名称很方便,因此测试脚本更易于阅读。 名称不区分大小写,即Auth $ User方法可以称为auth $ UserAUTH $ User 。 但是,变量名称区分大小写,以下更多有关变量的信息

重要说明。 YAML格式允许您不要将字符串括在引号中。 但是对于解释器,不带引号的字符串是需要评估的表达式。 例如,声明url: http://example.com/login字段将在执行期间导致语法错误。 因此,它是正确的: url: "http://example.com/login"url: "http://"+$domain+"/login"

API方法描述字段


方法 -必需的HTTP方法

url-实际网址,必填

标头 -HTTP标列表,可选

cookie-可选的cookie列表

auth-用于HTTP身份验证的数据,可选

 auth: login: $login password: $password type: "basic" //  "digest"  "ntlm", - "basic" 

查询 -URL参数列表,可选

格式 -值之一:

  • -请求没有内文
  • json-发送到JSON
  • raw-按原样发送字符串
  • 表格 -应用程式/ x-www-form-urlencoded格式
  • 多部分 -多部分/表单数据格式

可选,默认

数据 -请求主体,将以format 格式 (可选)指定的格式发送

  • 如果没有 -可能缺少数据格式(如果存在),将被忽略
  • 对于json格式,任何值
  • 对于原始格式,任何标量值
  • 对于表单格式,其键为字段名称的数组:

     data: login: "Bob" password: $password remember_me: 1 
  • 对于多部分格式,具有以下结构的数组:

     data: user_id: value: 42 headers: X-Baz: "bar" avatar: file: "/path/to/file" photo: file: "http://url.to/file" filename: "custom_filename.jpg" 

文件字段中指定的文件必须可读。 如果指定了URL,则必须在php.ini中启用allow_url_fopen

before-将在HTTP请求之前执行的语句,可选

after-将在HTTP请求之后执行的语句,可选

在执行检查或处理每次执行HTTP请求之前或之后所需的任何数据时, beforeafter块的想法并不是由测试需求决定的,而由业务逻辑决定。 例如,将发出的授权令牌复制到$用户结构的字段中,以代表该用户调用所有后续API方法。 或者检查响应的HTTP状态,以免每次调用脚本后都检查响应。

API方法调用


要在脚本中调用API方法,您需要根据需要指定其名称和参数。 这是上面描述中调用最后一个API方法的示例:

 Create comment $comments.1 on {$newPost} by {$postAuthor} 

如果参数用大括号括起来,它将按值传递-这样,您可以传递任何表达式。 如果指定不带花括号的参数,则该参数将通过引用传递-它只能是变量和对数组元素的静态访问(通过句点,但不能通过括号[])。

 Create comment {$comments[$i]} on $posts.0 by $users.1 Read post {123} by $user Get comments of $users.1.id {{$page = 2}} 

每次在调用本身的上下文中(在beforeafter语句的列表中)以及在调用它的上下文中调用API方法时,都会创建变量$ request$ response 。 这些是保留名称,我不建议将它们用于其他目的。 $ request 之前之后均可用,并且$ response仅在其值变为Null 之前的 after之后 。 在调用上下文中,这些变量在下一个API方法调用之前可用,在此之前它们将被重新初始化。

$请求结构


$ request.method-字符串-HTTP方法
$ request.url-字符串-请求的URL
$ request.query-数组-GET参数列表
$ request.headers-数组-请求标头列表
$ request.cookies-数组-cookie列表
$ reuqest.auth-数组或空-用于HTTP身份验证的数据
$ request.format-字符串-请求数据格式
$ request.data-输入any- 数据字段中计算出的值

$响应结构


$ response.network-布尔值-如果错误是在低于HTTP的网络级别上,则为false
$ response.code-数字或Null-响应码,例如200或404
$ response.status-字符串或Null-响应状态,例如“ 204 No Content”或“ 401未经授权”
$ response.headers-数组-响应标头列表,标头名称为小写
$ response.cookies-数组-Cookies列表
$ response.body-任何类型-响应主体都以JSON处理,如果在解析过程中发生错误,则根本没有body元素: @response.body == null关于检查变量是否存在
$ response.raw-字符串或Null-原始响应正文
$ response.duration-类型号-请求持续时间(以秒为单位)

模型和测试数据的生成


生成器用于描述模型并从中生成测试数据。 YAML格式的描述应该在工作目录中的generators.yaml文件中,和/或./generators子目录中的* .yaml文件中。

 User: body: login: Faker\login() name: Faker\name() email: Faker\email() password: Faker\text(16) child: Child() birthday: dateFormat(Faker\datetime(), "U") settings: notifications_enabled: Faker\boolean() Child: body: name: Faker\name() gender: Faker\integer(1, 2) age: Faker\integer(0, 18) Comment($user): body: content: "Hi! I'm " + $user.name tags: - "tag1" - "tag2" 

在上面的示例中,声明了三个生成器User()Child()Comment() 。 在这种情况下,后者的参数为$ user,并且可以在生成时使用此数据。 生成器的参数始终按值传递。 此外,该示例还使用了多个内置函数: Faker \ name()Faker \ email()dateFormat()等。 内置功能一节

从上面的示例调用User()生成器时,将生成一个在JSON中看起来像这样的结构:

 { "login": "fgadrkq", "name": "Lucy Cechtelar", "email": "tkshlerin@collins.com", "password": "gbnaueyaaf", "child": { "name": "Adaline Reichel", "gender": 2, "age": 12 }, "birthday": 318038400, "settings": { "notifications_enabled": true } } 

child字段的值是Child ()生成器的结果。

与API方法的描述一样,任何未用引号引起来的字符串都将被视为要评估的表达式。 这不仅可以是对另一个生成器的调用,还可以是任意表达式,例如,在Comment($ user)生成器中, content字段表示字符串Hi!的串联。 我和名字传递给$用户

生成器的名称不区分大小写,并且必须以拉丁字母开头;它们可以包含拉丁字母,数字,下划线和反斜杠。

因为调用生成器和内置函数的语法相同,所以它们共享一个公共的名称空间。 按照惯例,我建议使用反斜杠作为分隔符,以基于github.com/fzaninotto/Faker库指定“供应商”或内置函数库,例如Faker \ something()函数。

使用发电机的细微差别,您无法阅读
使用生成器,您可以组成数据结构:

 # Userredentials     $user Userredentials($user): body: login: $user.email password: $user.password #    .     ,    GlobalSearchResult($posts, $comments, $users): body: posts: title: " " list: $posts comments: title: " " list: $comments users: title: " " list: $users 

GlobalSearchResult不是在请求中发送给API方法的测试数据,而是一种响应模型,可以使用API​​将发送的内容进行验证,例如,使用相似()相同()函数。

生成器可以使用在replaceremove字段中计算出的结构来更改在主体中获得的结构。 我给你看一个例子。

假设您已经有一个User()生成器,可以为用户创建正确的数据结构。 现在,您需要检查如果提供的数据不正确,API将如何响应。 您可以通过两种方式进行:

  • 从头开始创建“错误的”用户生成器。 但是随后我们将获得代码重复,例如,稍后,根据业务逻辑的需要向用户添加新字段时,您将不得不在两个地方进行更改。 干!
  • 您可以通过将其设置在主体中 ,从现有的User()结构“继承”它。 在替换删除中,设置将要添加/更改和删除的字段。

 #      ,    , #    InvalidUser($user): body: $user replace: email: Faker\String(6, 15) #   password: Faker\String(1, 5) #    new_field: "      ,  " remove: name: size($user.name) < 10 #  ,    10  #      , #        InvalidNewUser: body: User() replace: login: "!@#$%^&*" #   remove: about: true settings: notifications: 100500 #       , #      true 

当生成器工作时,首先计算主体中的数据结构,然后覆盖它,并使用replace元素进行补充然后如果remove中指定的字段值等于true,则删除它们。 如果bodyreplaceremove的计算结果不是数组,则不会有错误,但是也没有意义,因为不会有任何字段可以替换和删除。

内建功能


内置功能的完整列表 。 例如,我只给出其中一些。
如果已定义返回值的类型,则在函数名称和参数列表之后显示。

变量操作:


相似 ($ var,$ sample,$ checkTypes) 布尔值 -如果参数的类型相同,则返回true ;如果$ var是数组,则$ sample中的所有字符串键都应位于$ var中 ;如果$ checkTypes为 true,则相应元素的类型必须匹配。 换句话说, $ var数组的元素是$ sample元素的子集。
相同($变种,样品,$ checkTypes)布尔 -类似物相似的() 在阵列的情况下的附加的反向状态,在串中的所有的键是$ var应该是在$样品。换句话说,$ var数组的元素等于$ sample数组的元素,直到元素类型。
max($ var1,$ var2,... $ varN)-传递值的最大值(如果可以比较)。
min($ var1,$ var2,... $ varN)-传递值的最小值。
if($ condition,$ var1,$ var2)-如果$ condition == true,则它将返回$ var1,否则返回$ var2。替换教练操作员(您好,MySQL)。
选择 ($ condition1,$ var1,$ condition2,$ var2,...,$ conditionN,$ varN)-如果$ conditionK == true,将返回第一个遇到的$ varK。

使用字符串:


大小($字符串)数量 -以UTF-8编码的线路长度。
regex($字符串,$ regex)布尔值 -检查字符串是否包含正则表达式
regexMatch($ string,$ regex)数组 -将返回一个字符串数组-与常规组$ regex匹配

数组处理:


阵列的($ VAR1,VAR2 $ ... $ VARN)的阵列 -创建的输入和输出元件的阵列。
size($数组)Number-数组中元素的数量。
keys($ array)Array-数组中键的列表。
slice($ array,$ offset,$ length)数组 -长度为$ length的$ offset的数组的一部分,(更多)。
append($数组,$值)Array-在数组末尾添加一个元素。
prepend($数组,$值)Array-在数组的开头添加一个元素。

日期处理:


dateFormat($ date,$ format)字符串 -日期格式,(有关格式的更多信息)。
dateModify($ date,$ format)日期 -更改日期,便于与相对格式一起使用

随机测试数据生成:


Faker \整数($ min,$ max)数字 -从$ min到$ max包括在内的随机整数
Faker \ ipv4()字符串 -随机IPv4
Faker \ arrayElement($ array)字符串 - 数组中的随机元素
Faker \ name()字符串 -随机名称
Faker \ email()字符串 -随机电子邮件

现在没有太多内置函数。在测试中,我仅添加了我认为必要的内容。您可以根据需要在新版本中添加新功能。将来,如果需要,我将添加创建动态连接功能的功能,这些功能在PHP中作为特殊类实现。

测试用例


测试用例是可以称为一个单元的一系列语句。用编程语言对过程进行某种模拟。

测试用例由testcase语句创建,后跟测试用例的名称,其语法类似于API方法 names 禁止嵌套测试用例。

 testcase Registration $device //     var $user = User() Register $user on $device assert $response.code == 201 //        var $user = User() //       InvalidUser() var $user.email = "some_bad_email" Register $user on $device assert $response.code == 400 

run语句可以调用单独的测试用例,也可以调用不需要参数的所有测试用例。

 run Get all users //   -,    run Get user $user_id //   -   run //   -,     

这种启动的想法是,测试用例可以用作业务逻辑一部分的独立独立测试,并且可以用作避免在复杂测试场景中重复代码的过程。

参数是通过引用或值传递给测试用例的,完全类似于将参数传递给API方法。

变量和范围


变量名称区分大小写,并以$符号开头(是的,是的,我是pshpshnik)。

如果变量类型的阵列,然后访问各个字段或值的元素是通过点制备:$users.12.password在点之间,只能使用数字或拉丁字母,下划线和带有第一个拉丁字母的数字。字段名称也区分大小写。

可以动态访问数组元素:$post.comments[$i + 1].content

上下文有四种类型-变量的范围。

全局上下文 -首先创建,包含在执行测试用例和API方法调用之外的语句时声明的所有变量。

测试用例上下文 -每次使用run语句执行测试用例时,都会创建一个新的上下文

API方法上下文 -在执行API方法,执行beforeafter部分中指定的运算符时创建

生成器上下文-生成器中不可能创建新的或更改现有的变量,因此全局上下文变量和参数是只读的。变量总是按值传递给生成器上下文。

重要说明。在所有上下文中,如果未在当前上下文中创建它们的名称,则全局上下文变量可用。

使用变量的运算符示例:

 const $a = 1 let $a = 2 //    var $b = 1 const $b = 3 //    const $c = 3; $c = 4 //   , $c      

 let $a = 1; $a = $a + 1; $a = $a + 2 print $a // 4 

 Testcase Context example changes $argument1, $argument2 and $argument3 var $a = "changed" let $b = "changed" const $c = "changed" import $i let $i = "changed" var $argument1 = "changed" var $argument2 = "changed" var $argument3 = "changed" // ,     var $a = "original" var $b = "original" var $i = "original" const $c = "original"; var $paramByRef = "original" var $paramByVal = "original" const $paramConst = "original" run Context example changes $paramByRef, {$paramByVal} and $paramConst //  $a     - print $a // "original" // $b     -,  let    $b print $b // "changed" // $i      print $i // "original" //      ,  var   print $c // "original" //     print $paramByRef // "changed" //     print $paramByVal // "original" //     ,     print $paramConst // "original" 

类型系统和操作


使用宽松的动态类型。

值存储在对应的PHP类型的包装器中。为了更好的理解,请参见 PHP类型系统。动态类型转换的自由度有所降低。例如,当添加一个字符串和一个数字时"2" + 2,将产生一个错误,PHP将安静地执行添加。也许将来我将需要修改动态类型的规则,但是到目前为止,我已经尝试在可靠测试所需的便利性和严格性之间找到平衡。

PieceofScript中可用的数据类型:

数字是一个数字。为简单起见,我没有为Integer和Float分别创建类型。 PieceofScript中整数和实数之间唯一的显着差异是使用数组作为键:实数将四舍五入为整数。
7 -42 3.14159

字符串 -用双引号括起来的字符串,可以用斜杠转义
"I say \"Hello world!\""

Null-由不区分大小写的常量设置
null

Boolean-是布尔运算和比较操作的结果,由不区分大小写的常量
true false

Date-日期和时间设置。 “内幕”是DateTime。常数以单引号指定,格式之一
'now', '2008-08-07 18:11:31', 'last day of next month'

Array是一个数组,唯一的非标量类型。结束该阵列没有这种类型的文字,但是数组可以是生成器,内置函数(例如array() -hello PHP 5.3及以下版本)的工作结果,或者您可以简单地访问在赋值后将动态创建的变量键。

 let $a.1 = 100 let $i = 1 let $a[$i + 1] = 200 let $a.sum = $a.1 + $a.2 print " "; $a.sum //  300 var $b = array(true, 3, $a, "Hi") // [true, 3, {1: 100, 2: 200, "sum":300}, "Hi"] 

当访问不存在的变量或数组元素时,测试脚本将停止并生成错误。但是,当执行assertmust 语句时,如果访问了不存在的变量,则不会有错误,但是检查将被视为失败。

检查变量的存在和类型


我们必须单独提及检查变量@的存在和类型的构造

如果在变量名中指定@而不是$,则此构造的结果将是以下之一:

  • 一个字符串,其名称为变量类型或数组元素的类型(如果使用键);
  • 如果在可访问的上下文中找不到变量,或者数组中具有指定键的元素不存在,则返回null

当检查HTTP响应的结构时,此设计可能很有用。

 var $a.string_field = "Hello World" var $a.number_field = 3.14 var $a.bool_field = true var $a.date_field = '+1 day' var $a.null_field = null var $a.array_field = array(1, "2") assert @a.string_field == "String" assert @a.number_field == "Number" assert @a.bool_field == "Boolean" assert @a.date_field == "Date" assert @a.null_field == "Null" assert @a.array_field == "Array" assert @a.array_field.0 == "Number" assert @a.array_field.1 == "String" assert @a.array_field.2 == null assert @notExistedVar == null 

或在类似的结构中:

 assert @comment.optional_field && $comment.optional_field > 20 

在第一个操作数上优化了布尔运算。如果第一个操作数为false,则&&操作甚至不会尝试计算第二个操作数。||类似

运行之间保存数据


我不仅在任务完成后用于测试,还在开发期间使用了单独的脚本。实现该功能时,我对脚本进行了补充和更改。后来,该脚本成为编写测试用例的基础,但是在开发过程中,必须一次又一次地进行相同的API调用。同时,每次在脚本中都要花很长时间从头开始创建新实体(例如,用户注册),在数据库中创建垃圾,并且以各种方式干扰开发。因此,我决定增加在键值存储中两次启动之间保存和恢复变量值的功能。

通过--storage命令行选项启用保存,该选项设置存储文件的名称:

 pos.phar run ./start.pos --storage=storage.yaml 

数据以YAML格式保存,从而易于读取和编辑。

storage \ get(字符串$键,$ defaultValue,布尔值$ saveValue = true)-如果键$键不存在或未指定存储文件,则返回$ defaultValue。否则,它将返回存储的值。如果参数$ saveValue为true并且未找到键$ key,则将$ defaultValue写入那里。

storage \ set(字符串$键,$值)-用键$键保存$值,并返回$值。如果尚未设置存储文件,则仅返回$值。

存储\键(字符串$ regexp = null)数组-返回所有可用键的数组。如果参数$ regexp不为null,则将返回与此正则表达式相对应的键。如果尚未设置存储文件,它将返回一个空数组。

输出到标准输出


PieceofScript可以生成JUnit和HTML格式的报告。首先需要与CI / CD系统集成,例如Jenkins。第二个是方便自己查看测试结果,例如在本地测试时。可以在启动时设置报告文件:
 pos.phar run ./start.pos --junit=junit_report.xml --html=report.html 
HTML报告的示例

在stdout中显示有关解释器工作的各种信息。有5个标准级别的信息输出。在同一级别显示的所有内容也会在其他“健谈”的内容上显示。

安静 -最“安静”的级别是通过-q命令行选项设置的
在此级别上,没有任何东西输出到标准输出,甚至是关键的解释器错误。但是通过非零返回码,您可以了解出了问题。默认级别是“

普通 ”,不指定选项。
在此级别上,解释器中会生成错误。对API方法的错误请求和断言失败必须进行检查

详细 -由选项设置-v
在此级别上,显示print语句的结果

非常详细 -由-vv选项设置
在此级别,显示解释器警告。

调试 -通过-vvv选项设置
在此级别,将显示所有已执行的脚本行。API方法的所有请求和答案,所有assert的结果必须检查

例子


谚语“看一次比听一百次更好”是正确的,在解释中,“看一次代码比读一百次代码更好”。我准备了示例并将其放在https://github.com/maximw/PosExamples存储库中

病毒总数


Virustotal.com-用于检查恶意文件和链接的服务。API文档除了注释方法外,针对API的公共部分进行了测试,因为 我不想乱扔带有测试数据的真正“战斗” API。

要访问API,您需要注册,获取密钥并将其添加到Virustotal / globals.pos文件

运行测试:

 pos.phar run ./Virustotal/start.pos 

车上有一个exe-shnik存放在存储库中吗?
为了进行测试,我从Symfony存储库的Console组件复制了hiddeninput.exe。可以删除此文件,并且为了进行测试,请使用其他最大32 mb的文件。

糕点店


Pasebin类似物。API文档
要访问API,您需要注册,获取密钥并将其添加到Pastery / globals.pos文件中

运行测试:

 pos.phar run ./Pastery/start.pos 

值得注意的是,通过这些测试,在视图数量的限制中发现了一个错误。Pastery 开发人员已经修复了该问题。

里克与弗洛里


我认为这个动画系列广为人知,并为许多人所喜爱。API文档API由三个几乎相同的部分组成,分别是角色,位置和情节。因此,方案几乎是相同的,仅查看测试用例只是这些部分之一。

运行测试:

 pos.phar run ./RickAndMorty/20MinutesTest.pos 

如果您知道以这种方式进行测试很有趣的公共API,请写一封个人电子邮件。

未来的评论和计划(如果有)


0)我列出了一些我不需要的小改进和大改进,但它们很有用。

查看清单
  • 添加有关Json格式的工作的报告,可能会在运行多个脚本后覆盖
  • body replace remove
  • , YAML
  • HTTP- ,
  • , run . .
  • HTML- stdout, -vvv
  • https
  • application/x-www-form-urlencoded CURLFile . Guzzle 6,
  • « »,
  • API,
  • HTML-, bootstrap- « », .

1)为了使用生成器在API响应中验证模型,到目前为止只有两个函数- 相似()相同()。与他们的验证太“笨拙”。当然,已经可以“手动”验证答案了,在某些情况下,这是不可能的,但是我想使其更方便,并且在可能的情况下,避免手动检查答案。关于如何使用相同的模型描述来生成和验证模型,有一些想法,以避免重复。但是到目前为止,这些想法还不够成熟,因此您无法在代码中实现它们。

2)我认为基于OpenAPI(Swagger),RAML和集合中的描述的API方法的脚手架将非常有用邮差。但是,如果PieofofScript值得,那么很多工作值得坐下来。

3)最好为某些IDE制作插件,并突出显示代码并自动完成。自动完成测试用例名称,API方法,运算符和变量将非常方便。但是他还没有朝这个方向“挖”。了解Sublime Text和Language Server协议的创建突出显示。如果已经有志趣相投的人精通此类事情,我将感到高兴。

4)我不知道将创建动态连接功能的能力放在什么优先级用PHP实现。一方面,那里的一切都很简单,足以应付自动加载并为所使用的类和名称空间指定规格。另一方面,复杂函数及其依赖关系将不可避免地导致依赖关系之间的命名空间冲突(在最坏的情况下,是不同的版本)。还有一些事情要考虑。

5)好的测试系统可以并行运行独立的测试。现在,可以通过使用不同的开始文件(连接了不同的测试用例)多次启动解释器来完成此操作。但是我认为我们需要将此功能嵌入到解释器中,并自动检测可以并行启动的功能。

PS一方面,由于这是我的“手工艺品”,因此在中心“我是PR”中张贴帖子是合乎逻辑的。另一方面,我不公关,不追求任何商业利益,只是我自己为自己创造的一种工具,我决定“梳理”并公开发布。

Source: https://habr.com/ru/post/zh-CN430392/


All Articles