问题描述
假设有以下简单的网络应用:
<?PHP
// src/App/App.PHP
namespace Practice\Sources\App;
use Closure;
use Laminas\Diactoros\ServerRequestFactory;
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Server\RequestHandlerInterface;
class App implements RequestHandlerInterface
{
private Closure $requestProvider;
private ResponseFactoryInterface $responseFactory;
private SapiEmitter $responseEmitter;
public function __construct(ResponseFactoryInterface $responseFactory)
{
$this->requestProvider = fn() => ServerRequestFactory::fromGlobals();
$this->responseFactory = $responseFactory;
$this->responseEmitter = new SapiEmitter();
}
public function run(): void
{
$request = ($this->requestProvider)();
$response = $this->handle($request);
$this->responseEmitter->emit($response);
}
public function handle(RequestInterface $request): ResponseInterface
{
$response = $this->responseFactory->createResponse();
$response->getBody()->write('hello world');
return $response;
}
}
可以通过将以下代码放在他们的 Web 前端控制器(例如 public_html/index.PHP
)中来轻松运行它:
<?PHP
// web-root/front-controller.PHP
use Laminas\Diactoros\ResponseFactory;
use Practice\Sources\App\App;
require_once __DIR__.'/../vendor/autoload.PHP';
$responseFactory = new ResponseFactory();
$app = new App($responseFactory);
$app->run();
现在,我想对它运行 Codeception 功能测试,用 Gherkin 编写。考虑以下简单测试:
# tests/Feature/run.feature
# (also symlink'ed from tests/Codeception/tests/acceptance/ )
Feature: run app
Scenario: I run the app
When I am on page '/'
Then I see 'hello world'
要针对它运行验收测试,我必须提供我的步骤实现。为此,我将重用 Codeception 提供的标准步骤:
<?PHP
// tests/Codeception/tests/_support/FeatureTester.PHP
namespace Practice\Tests\Codeception;
use Codeception\Actor;
abstract class FeatureTester extends Actor
{
// use _generated\AcceptanceTesteractions;
// use _generated\FunctionalTesteractions;
/**
* @When /^I am on page \'([^\']*)\'$/
*/
public function iAmOnPage($page)
{
$this->amOnPage($page);
}
/**
* @Then /^I see \'([^\']*)\'$/
*/
public function iSee($what)
{
$this->see($what);
}
}
<?PHP
// tests/Codeception/tests/_support/AcceptanceTester.PHP
namespace Practice\Tests\Codeception;
class AcceptanceTester extends FeatureTester
{
use _generated\AcceptanceTesteractions;
}
# tests/Codeception/codeception.yml
namespace: Practice\Tests\Codeception
paths:
tests: tests
output: tests/_output
data: tests/_data
support: tests/_support
envs: tests/_envs
actor_suffix: Tester
extensions:
enabled:
- Codeception\Extension\RunFailed
# tests/Codeception/tests/acceptance.suite.yml
actor: AcceptanceTester
modules:
enabled:
- PHPbrowser:
url: http://practice.local
gherkin:
contexts:
default:
- \Practice\Tests\Codeception\AcceptanceTester
但是现在,我想使用相同的测试代码来运行与使用 Codeception 的功能测试相同的测试。为此,我必须enable the module 以功能方式实现这些相同的步骤。我使用哪一种? Codeception 提供了几个,但它们是用于 3rd 方框架的,例如Laravel、Yii2、Symphony 等。对于这样一个不使用任何第三方框架的简单应用,我该怎么做?
这是我设法做到的。我创建了自己的 \Codeception\Lib\Innerbrowser
实现,它继承自 Codeception 提供的 \Codeception\Module\PHPbrowser
,其中我用我自己的实现(也继承自 Guzzle)替换了 Codeception 使用的 Web 客户端(它使用 Guzzle)客户端),它不执行任何网络请求,而是请求我的应用程序:
# tests/Codeception/tests/functional.suite.yml
actor: FunctionalTester
modules:
enabled:
- \Practice\Tests\Codeception\Helper\CustomInnerbrowser:
url: http://practice.local
gherkin:
contexts:
default:
- \Practice\Tests\Codeception\FunctionalTester
<?PHP
// tests/Codeception/tests/_support/FunctionalTester.PHP
namespace Practice\Tests\Codeception;
class FunctionalTester extends FeatureTester
{
use _generated\FunctionalTesteractions;
}
为了让它起作用,我必须让我的应用返回 Guzzle Response
s(它也实现了 PSR 的 ResponseInterface
)——因为 PHPbrowser 希望它的网络客户端返回它们 - 这就是为什么我必须将 ResponseFactory
设为构造函数参数才能在测试中替换它。
<?PHP
// tests/Codeception/tests/_support/Helper/CustomInnerbrowser.PHP
namespace Practice\Tests\Codeception\Helper;
use Codeception\Module\PHPbrowser;
use Http\Factory\Guzzle\ResponseFactory;
use Practice\Sources\App\App;
class CustomInnerbrowser extends PHPbrowser
{
private App $app;
public function __construct(...$args)
{
parent::__construct(...$args);
$responseFactory = new ResponseFactory();
$this->app = new App($responseFactory);
}
public function _prepareSession(): void
{
parent::_prepareSession();
$this->guzzle = new CustomInnerbrowserClient($this->guzzle->getConfig(),$this->app);
$this->client->setClient($this->guzzle);
}
}
<?PHP
// tests/Codeception/tests/_support/Helper/CustomInnerbrowserClient.PHP
namespace Practice\Tests\Codeception\Helper;
use GuzzleHttp\Client as GuzzleClient;
use Practice\Sources\App\App;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
class CustomInnerbrowserClient extends GuzzleClient
{
private App $app;
public function __construct(array $config,App $app)
{
parent::__construct($config);
$this->app = $app;
}
public function send(RequestInterface $request,array $options = []): ResponseInterface
{
return $this->app->handle($request);
}
}
在这样的配置中,一切似乎都正常。
但是有一个问题。注意 App::handle()
签名:
public function handle(RequestInterface $request): ResponseInterface
- 它与它实现的不同,它在 RequestHandlerInterface
中声明:
public function handle(ServerRequestInterface $request): ResponseInterface;
从技术上讲,它是完全合法的,因为它没有违反 Liskov 替换原则要求的 parameter contravariance。
我面临的问题是 PHPbrowser
假定它发送一个(客户端)RequestInterface
s(通过网络)但 my 应用程序需要一个(服务器-端) ServerRequestInterface
代替,以便能够访问在服务器端设置的参数,例如 ServerRequestInterface::getParsedBody()
、会话等。
我该如何解决这个问题? Codeception 提供的框架模块已经以某种方式做到了这一点......顺便说一句,Codeception(或其他人)提供了一种针对自定义代码运行功能测试的简单方法吗?
顺便说一句:composer.json
:
{
"require": {
"PHP": "~7.4","laminas/laminas-diactoros": "^2.5","laminas/laminas-httphandlerrunner": "^1.3"
},"require-dev": {
"codeception/codeception": "^4.1","codeception/module-PHPbrowser": "^1.0.0","http-interop/http-factory-guzzle": "^1.0"
},"autoload": {
"psr-4": {
"Practice\\Sources\\": "src"
}
},"autoload-dev": {
"psr-4": {
"Practice\\Tests\\Unit\\": "tests/Unit/","Practice\\Tests\\Support\\": "tests/Support/","Practice\\Tests\\Codeception\\": "tests/Codeception/tests/_support/","Practice\\Tests\\Codeception\\_generated\\": "tests/Codeception/tests/_support/_generated/","Practice\\Tests\\Codeception\\Helper\\": "tests/Codeception/tests/_support/Helper/"
}
},"scripts": {
"test-feature": "codecept run --config tests/Codeception/codeception.yml"
}
}
解决方法
暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!
如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。
小编邮箱:dio#foxmail.com (将#修改为@)