aferalabs / arachne
Behat 扩展,用于测试和验证基于 XML 和 JSON 的网络服务
Requires
- php: >=5.4.0
- behat/behat: ~3.3.0
- guzzlehttp/guzzle: ~6.0
- justinrainbow/json-schema: ~4.1|~5.0
- seromenho/xml-validator: ~v1.0
Requires (Dev)
- phpunit/phpunit: ~4.4
README
Arachne
Arachne 是 Behat 3 扩展,用于测试网络服务。类似于 Mink,它在上下文中公开了多种方法,以简化 RESTful API 的测试。
安装
安装 Arachne 的首选方式是通过 composer。只需将 Arachne 添加到项目的依赖项中,即可开始使用。
{ "require-dev": { "aferalabs/arachne": "0.1.*" } }
配置
示例项目中设置了示例配置。请参阅 examples/behat.yml
。
default: extensions: Arachne\ServiceContainer\ArachneExtension: base_url: http://echo.jsontest.com paths: schema_file_dir: %paths.base%/schemas request_file_dir: %paths.base%/requests response_file_dir: %paths.base%/responses auth: provider: Arachne\Auth\DummyProvider headers: Authorization: Token token=123456 suites: json: contexts: - Arachne\Context\ArachneContext - headers: X-Example-Header: Example Value
为了启用扩展,您需要将其添加到配置的扩展节点。
base_url
base_url
是唯一的必需配置值,并将添加到扩展发出的所有请求之前。
paths.schema_file_dir
在底层,Arachne 使用 JSON 架构验证器 或 XML 架构验证器 来验证响应的结构。为了使用 response should validate against "one_two[.json|.xsd]" schema
,扩展需要知道在哪里可以找到架构文件。 paths.schema_file_dir
告诉 Arachne 架构文件所在的文件夹。在这个特定示例中,Arachne 将在 examples/schemas/one_two.json
文件中查找架构。
Scenario: Given I use "GET" request method When I access the resource url "/one/two" And I send the request Then the status code should be 200 And response should be a valid JSON And response header "Server" should contain "Google Frontend" And response should validate against "one_two" schema # <---
提示:也可以使用 xsd 文件验证 xml 响应。您只需明确指定架构文件的文件类型(如果不输入文件类型,则使用 json 作为文件类型)
Scenario: Given I use "GET" request method When I access the resource url "/one/two" And I send the request Then the status code should be 200 And response should be a valid XML And response header "Server" should contain "Google Frontend" And response should validate against "one_two.xsd" schema # <---
paths.request_file_dir
有时请求相对较大,其内容会使功能不可读。因此,Arachne 支持使用文件内容设置请求体。 paths.request_file_dir
告诉 Arachne 请求文件所在的文件夹。在这种情况下,Arachne 将在 examples/requests/one_two[.json|.xml]
文件中查找请求体。
Scenario: Given I use "POST" request method When I access the resource url "/one/two" And I use the "one_two" file as request body # <--- And I send the request Then the status code should be 200 And response should be a valid JSON And response should be identical to "one_two" file
提示:也可以使用 xml 文件作为请求体。您只需明确指定 xml 文件的文件类型(如果不输入文件类型,则使用 json 作为文件类型)
Scenario: Given I use "POST" request method When I access the resource url "/one/two" And I use the "one_two.xml" file as request body # <--- And I send the request Then the status code should be 200 And response should be a valid XML And response should be identical to "one_two.xml" file
paths.response_file_dir
与请求文件类似,由网络服务提供的响应可能相对较大。为了验证不仅响应的架构,而且其内容,Arachne 支持将响应体的内容与文件内容进行比较。 paths.response_file_dir
告诉 Arachne 响应文件所在的文件夹。在这种情况下,Arachne 将在 examples/responses/one_two[.json|.xml]
文件中查找响应体。
Scenario: Given I use "POST" request method When I access the resource url "/one/two" And I use the "one_two" file as request body And I send the request Then the status code should be 200 And response should be a valid JSON And response should be identical to "one_two" file # <---
提示:也可以验证 xml 响应的内容。您只需明确指定 xml 文件的文件类型(如果不输入文件类型,则使用 json 作为文件类型)
Scenario: Given I use "POST" request method When I access the resource url "/one/two" And I use the "one_two.xml" file as request body And I send the request Then the status code should be 200 And response should be a valid XML And response should be identical to "one_two.xml" file # <---
auth.provider
某些网络服务需要在请求之间进行某种持久性操作。为了做到这一点,您可以使用身份验证提供程序在测试开始之前执行身份验证,并将结果提供给上下文。使用此配置变量,告诉 Arachne 在测试开始之前使用身份验证提供程序。您可以在下面了解更多有关身份验证提供程序的信息。
headers
并非所有网络服务都是开放的,其中许多需要授权。网络服务还使用资源的版本控制。这就是头部的用武之地。此配置允许您设置随每个请求发送的头信息。此配置变量支持发送每个请求时所需的所有头信息数量。要了解如何设置头部,请阅读下方的头部章节。
步骤
给定/当
我是一个匿名用户
如果Arachne被设置为使用身份验证提供者,您可以强制当前场景不将当前客户端传递给身份验证提供者的prepare
方法,从而省略身份验证。如果您正在测试未注册用户的错误响应或执行身份验证,这很有用。
我使用".*"请求方法
将请求方法设置为提供的HTTP动词。
我访问资源URL ".*"
设置请求的路径。
我使用".*"文件作为请求正文
使用文件的内容作为请求正文。
我将头部"."设置为".""
将头部设置为提供的值。有关依赖关系的更多信息,请参阅下方的头部章节。
我发送请求
必须显式调用以发送请求。
然后
状态码应该是\d+
验证,如果返回的状态码等于预期值。
响应应该是一个有效的JSON
验证,如果响应体可以反序列化为有效的JSON。
响应应该是一个有效的XML
验证,如果响应体可以反序列化为有效的XML。
响应头"."应该包含".""
验证,如果头部的内容等于预期值。
响应应该与".*"模式有效
验证,如果返回的响应再次验证模式。
响应应该与".*"文件相同
验证,如果响应体等于文件的内容。假设响应是一个JSON字符串。
身份验证提供者
为了执行身份验证,您可以创建一个身份验证提供者,让Arachne在测试开始之前对客户端进行身份验证。每个提供者都必须扩展Arachne\Auth\BaseProvider
并提供身份验证的逻辑。在每个请求之前,Arachne将执行prepare
方法,您可以使用它来修改客户端并例如添加身份验证头部。一个简单的身份验证提供者可能看起来像下面的类。
use Arachne\Auth\BaseProvider; class LoginProvider extends BaseProvider { private $sessionToken; public function authenticate() { $client = $this->getClient(); $client->setPath('/login'); $client->setRequestBody(json_encode(array('u' => 'user', 'p' => 'pa$$'))); $response = $client->send(); if ($response->getStatusCode() !== 200) { return false; } $body = json_decode($response->getBody()); $this->sessionToken = $body->sessionToken; return true; } public function prepare(ClientInterface $client) { $client->addHeader('X-Session-Token', $this->sessionToken); } }
头部
设置头部有多种方法。在第一步中,头部将在上下文初始化期间设置并传递给上下文构造函数。您可以通过测试套件的上下文配置传递头部。
default: suites: json: contexts: - Arachne\Context\ArachneContext: - headers: X-Example-Header: Example Value
如果您使用自定义上下文,请确保调用ArachneContext
的构造函数。
use Arachne\Context\ArachneContext class MyContext extends ArachneContext { public function __construct(array $params) { // ... process some custom params parent::__construct($params); } }
您还可以在扩展配置中设置头部。
default: extensions: Arachne\ServiceContainer\ArachneExtension: headers: Authorization: Token token=123456
在扩展配置中设置的任何头部将覆盖上下文初始化期间提供的头部。这是一个设置您的授权或接受头部的好地方,因为它们将被传递到每个请求。
头部也可以直接在功能中传递。在功能中提供的任何头部都将覆盖扩展配置或初始化参数中设置的头部。
# ... And I set the header "Accept" to "application/vnd.arachne.v1" # ...
如何运行示例
要运行存储库中提供的示例,请遵循以下步骤。
git clone git@github.com:theDisco/Arachne.git
cd Arachne
composer install
JSON示例
vendor/bin/behat -c examples/json/behat.yml
输出应该类似于下面。
Feature: Fake JSON API sample
In order for extension to work
As an API user
I need to be able to interact with Fake JSON API
Scenario: # features/example.feature:6
Given I am an anonymous user # Arachne\Context\ArachneContext::iAmAnAnonymousUser()
And I use "GET" request method # Arachne\Context\ArachneContext::iUseRequestMethod()
When I access the resource url "/key/value" # Arachne\Context\ArachneContext::iAccessTheResourceUrl()
And I send the request # Arachne\Context\ArachneContext::iSendTheRequest()
Then the status code should be 200 # Arachne\Context\ArachneContext::theStatusCodeShouldBe()
And response should be a valid JSON # Arachne\Context\ArachneContext::responseShouldBeAValidJson()
Scenario: # features/example.feature:14
Given I use "GET" request method # Arachne\Context\ArachneContext::iUseRequestMethod()
When I access the resource url "/one/two" # Arachne\Context\ArachneContext::iAccessTheResourceUrl()
And I send the request # Arachne\Context\ArachneContext::iSendTheRequest()
Then the status code should be 200 # Arachne\Context\ArachneContext::theStatusCodeShouldBe()
And response should be a valid JSON # Arachne\Context\ArachneContext::responseShouldBeAValidJson()
And response header "Server" should contain "Google Frontend" # Arachne\Context\ArachneContext::responseHeaderShouldContain()
And response should validate against "one_two.json" schema # Arachne\Context\ArachneContext::responseShouldValidateAgainstSchema()
Scenario: # features/example.feature:23
Given I use "POST" request method # Arachne\Context\ArachneContext::iUseRequestMethod()
When I access the resource url "/one/two" # Arachne\Context\ArachneContext::iAccessTheResourceUrl()
And I use the "one_two.json" file as request body # Arachne\Context\ArachneContext::iUseTheFileAsRequestBody()
And I set the header "Accept" to "application/vnd.arachne.v1" # Arachne\Context\ArachneContext::iSetTheHeaderTo()
And I send the request # Arachne\Context\ArachneContext::iSendTheRequest()
Then the status code should be 200 # Arachne\Context\ArachneContext::theStatusCodeShouldBe()
And response should be a valid JSON # Arachne\Context\ArachneContext::responseShouldBeAValidJson()
And response should be identical to "one_two.json" file # Arachne\Context\ArachneContext::responseShouldBeIdenticalToFile()
Scenario: # features/example.feature:33
Given I use "POST" request method # Arachne\Context\ArachneContext::iUseRequestMethod()
When I access the resource url "/one/two" # Arachne\Context\ArachneContext::iAccessTheResourceUrl()
And I use the "one_two" file as request body # Arachne\Context\ArachneContext::iUseTheFileAsRequestBody()
And I set the header "Accept" to "application/vnd.arachne.v1" # Arachne\Context\ArachneContext::iSetTheHeaderTo()
And I send the request # Arachne\Context\ArachneContext::iSendTheRequest()
Then the status code should be 200 # Arachne\Context\ArachneContext::theStatusCodeShouldBe()
And response should be a valid JSON # Arachne\Context\ArachneContext::responseShouldBeAValidJson()
And response should validate against "one_two" schema # Arachne\Context\ArachneContext::responseShouldValidateAgainstSchema()
And response should be identical to "one_two" file # Arachne\Context\ArachneContext::responseShouldBeIdenticalToFile()
4 scenarios (4 passed)
30 steps (30 passed)
0m1.05s (10.29Mb)
JSON示例
vendor/bin/behat -c examples/xml/behat.yml
输出应该类似于下面。
Feature: Fake XML API sample
In order for extension to work
As an API user
I need to be able to interact with Fake XML API
Scenario: # features/example.feature:6
Given I am an anonymous user # Arachne\Context\ArachneContext::iAmAnAnonymousUser()
And I use "GET" request method # Arachne\Context\ArachneContext::iUseRequestMethod()
When I access the resource url "/echo?status=200&Content-Type=application%2Fxml&body=%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%0A%3Cone%3Etwo%3C%2Fone%3E" # Arachne\Context\ArachneContext::iAccessTheResourceUrl()
And I send the request # Arachne\Context\ArachneContext::iSendTheRequest()
Then the status code should be 200 # Arachne\Context\ArachneContext::theStatusCodeShouldBe()
And response should be a valid XML # Arachne\Context\ArachneContext::responseShouldBeAValidXml()
And response should validate against "one_two.xsd" schema # Arachne\Context\ArachneContext::responseShouldValidateAgainstSchema()
And response should be identical to "one_two.xml" file # Arachne\Context\ArachneContext::responseShouldBeIdenticalToFile()
Scenario: # features/example.feature:16
Given I use "GET" request method # Arachne\Context\ArachneContext::iUseRequestMethod()
When I access the resource url "/echo?status=200&Content-Type=application%2Fxml&body=%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%0A%3Cone%3Etwo%3C%2Fone%3E" # Arachne\Context\ArachneContext::iAccessTheResourceUrl()
And I send the request # Arachne\Context\ArachneContext::iSendTheRequest()
Then the status code should be 200 # Arachne\Context\ArachneContext::theStatusCodeShouldBe()
And response should be a valid XML # Arachne\Context\ArachneContext::responseShouldBeAValidXml()
And response header "Server" should contain "Google Frontend" # Arachne\Context\ArachneContext::responseHeaderShouldContain()
And response should validate against "one_two.xsd" schema # Arachne\Context\ArachneContext::responseShouldValidateAgainstSchema()
Scenario: # features/example.feature:25
Given I use "POST" request method # Arachne\Context\ArachneContext::iUseRequestMethod()
When I access the resource url "/echo?status=200&Content-Type=application%2Fxml&body=%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%0A%3Cone%3Etwo%3C%2Fone%3E" # Arachne\Context\ArachneContext::iAccessTheResourceUrl()
And I use the "one_two.xml" file as request body # Arachne\Context\ArachneContext::iUseTheFileAsRequestBody()
And I set the header "Accept" to "application/vnd.arachne.v1" # Arachne\Context\ArachneContext::iSetTheHeaderTo()
And I send the request # Arachne\Context\ArachneContext::iSendTheRequest()
Then the status code should be 200 # Arachne\Context\ArachneContext::theStatusCodeShouldBe()
And response should be a valid XML # Arachne\Context\ArachneContext::responseShouldBeAValidXml()
And response should validate against "one_two.xsd" schema # Arachne\Context\ArachneContext::responseShouldValidateAgainstSchema()
And response should be identical to "one_two.xml" file # Arachne\Context\ArachneContext::responseShouldBeIdenticalToFile()
3 scenarios (3 passed)
24 steps (24 passed)
0m1.47s (10.30Mb)
待办事项
- 允许在钩子中修改HTTP客户端。当前钩子是静态的,它们没有访问上下文实例的权限。
许可证
The MIT License (MIT)
Copyright (c) 2015 Wojtek Gancarczyk <wojtek@aferalabs.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.