symfony3
Hi! I wrote this post to make it easier for you to test your PHP application. The first two tricks are related to the Symfony web framework and functional/integration testing in this framework, but the second one is a pattern that every PHP engineer should know. You can treat this article as a note created for me from the past. It’s also time-capsule for me from the future if I forget how to work with Symfony testing services. Mr. Google please — index this article perfectly that I can find it in the future.
嗨! 我写这篇文章是为了使您更轻松地测试PHP应用程序。 前两个技巧与Symfony Web框架以及该框架中的功能/集成测试有关,但第二个技巧是每个PHP工程师都应该知道的一种模式。 您可以将本文视为过去为我创建的注释。 如果我忘记了如何使用Symfony测试服务,这对我来说也是将来的时间。 Google先生-请完美索引这篇文章,以备将来使用。
功能测试和其他环境的单独实现 (Separated implementations for functional testing and the other environments)
While I was working on my side-project I needed something that enables me to use different implementations of some interfaces in the testing environment and the others. I couldn’t find it in the framework guide so maybe it’s helpful not only for me. In the example, we have an interface like:
在进行外部项目时,我需要一些东西来使我能够在测试环境和其他环境中使用某些接口的不同实现。 我在框架指南中找不到它,因此也许不仅对我有帮助。 在示例中,我们有一个类似的接口:
<?php
interface Customers
{
/** @throws CustomerNotFound **/
public function ofId(CustomerId $id): Customer;
}
And we have also two implementations. First one is a DoctrineORM implementation:
我们还有两个实现。 第一个是DoctrineORM实现:
The second one implementation is just InMemory version that we need in the tests (for example units). Yes — that’s just a fake test double implementation. ;)
第二种实现只是测试中我们需要的InMemory版本(例如单元)。 是的-这只是一个假的双重测试实现。 ;)
<?php
final class InMemoryCustomers implements Customers
{
public function ofId(CustomerId $id): Customer
{
// Blabla using in memory
}
}
Okay, at this moment we have two implementations. One is using external things (in this example Doctrine and database) and the second one is working in memory so it’s using state of the object.
好的,此刻我们有两个实现。 一种是使用外部事物(在本例中为教义和数据库),第二种是在内存中工作,因此它正在使用对象的状态。
As you may know or now — functional tests are not using third party adapters. They should just check the flow and the final result of a request. For example — check the status code of a response.
如您所知或现在—功能测试未使用第三方适配器。 他们应该只检查请求的流程和最终结果。 例如-检查响应的状态码。
Coming to the point — we want to use it in different situations — other implementations. As a default, we’re registering services in something like /config/services.yaml
. But how to change implementation for the tests? I’ve moved definitions to the /config/packages/services.yaml
and created /config/packages/test/services.yaml
with the same keys but different implementation classes. Don’t forget about the packages
namespace. That’s easy and allows us to make the functional tests as fast as we can do them. Definitions should looks like:
说到重点-我们想在不同的情况下使用它-其他实现。 默认情况下,我们在/config/services.yaml
类的位置注册服务。 但是,如何更改测试的实现? 我已经将定义移至/config/packages/services.yaml
并使用相同的键但不同的实现类创建了/config/packages/test/services.yaml
。 不要忘记packages
名称空间。 这很容易,使我们能够尽快进行功能测试。 定义应类似于:
# /config/packages/services.yaml
services:
App\Domain\Customers: '@App\Infrastructure\Doctrine\ORM\DoctrineORMCustomers'
# /config/packages/test/services.yaml
services:
App\Domain\Customers: '@App\Infrastructure\InMemory\InMemoryCustomers'
Thanks to this you can make some fast functional tests of your application. Remember: tests must be quick because slow tests destroying your developer experience.
因此,您可以对应用程序进行一些快速的功能测试。 请记住:测试必须快速,因为缓慢的测试会破坏您的开发人员体验。
认真测试依赖注入容器 (Testing dependency injection container is close to you, seriously)
This information is not that hard to find but you can use your testing container (that marks all the services as a public) using a magic static test attribute static::$container
. Thanks to this you can easier test some services that are registered as a private for the container. AND! Remember to mark them as final/private.
这些信息并不是很难找到,但是您可以使用神奇的静态测试属性static::$container
使用测试容器(将所有服务标记为公共)。 因此,您可以更轻松地测试一些已注册为容器私有的服务。 和! 记住将它们标记为final / private 。
ObjectMother是你的朋友 (ObjectMother is your friend)
This tip is not related to the Symfony framework but still, it’s really useful. ObjectMother is a great testing pattern that could avoid code duplication and simplify tests. Sometimes your testing code — especially given section may look not that cool as you want it to be. The situation could look like the following example:
该技巧与Symfony框架无关,但确实有用。 ObjectMother是一种很好的测试模式,可以避免代码重复并简化测试。 有时,您的测试代码(尤其是给定的部分)可能看起来并不像您想要的那样酷。 情况可能类似于以下示例:
<?php
public function testPremiumCustomerDidSomethingBlabla(): void
{
// Given
$email = new Email('patryk.wozinski@example.com', 'verified');
$address = new Address('Warsaw', 'Ursynów');
$customer = new Customer($email, $address);
// When
// Blurred :)
}
Let’s suppose that premium customer is just a customer with a verified e-mail address. Now image that — you need the same state of the customer in… hmm, seven test cases. It’s boring and painful to copy&paste code pieces. And… after your change, the $address
needs the third parameter so you need to change all the seven test cases. That’s terrifying! To avoid such sad situations you can introduce ObjectMother pattern in your test code. You can create few objects and then compose them into some detailed object. Let’s look at the bigger example:
让我们假设高级客户只是具有经过验证的电子邮件地址的客户 。 现在想象一下-您需要在……嗯,七个测试用例中保持客户的相同状态。 复制并粘贴代码段很无聊且痛苦。 并且…更改之后, $address
需要第三个参数,因此您需要更改所有七个测试用例。 太恐怖了! 为了避免这种情况,您可以在测试代码中引入ObjectMother模式。 您可以创建几个对象,然后将它们组合成一些详细的对象。 让我们看一个更大的例子:
<?php
final class EmailMother
{
private const EMAIL = 'patryk.wozinski@example.com';
private const STATUS_VERIFIED = 'verified'; // I hate "statuses" but that's just an example xd
public static function anyVerified(): Email
{
return new Email(self::EMAIL, self::STATUS_VERIFIED);
}
}
final class AddressMother
{
private const CITY = 'Warsaw';
private const DISTRICT = 'Ursynów';
public static function any(): Address
{
return new Address(self::CITY, self::DISTRICT);
}
}
// And finally the most interesting for us
final class CustomerMother
{
public static function anyPremium(): Customer
{
return new Customer(EmailMother::anyVerified(), AddressMother::any());
}
}
So… right now your testing method could looks like:
所以...现在,您的测试方法可能类似于:
<?php
public function testPremiumCustomerDidSomethingBlabla(): void
{
// Given
$customer = CustomerMother::anyPremium();
// When
// Blurred :)
}
At this moment any change of the customer’s interiors doesn’t affect your tests. It’s much CHEAPER and EASIER to maintain. Two wins with just one simple pattern.
目前,客户内饰的任何变化都不会影响您的测试。 它更便宜和更容易维护。 仅用一种简单模式就能获得两场胜利。
Happy coding! 👋
编码愉快! 👋
Go ahead, let’s chat!
来吧,让我们聊天!
Twitter [ https://twitter.com/patrykwozinski ]
Twitter [ https://twitter.com/patrykwozinski ]
LinkedIn [ https://www.linkedin.com/in/patrykwozinski ]
翻译自: https://medium.com/@patrykwozinski/three-simple-testing-tricks-using-php-and-symfony-e4983efad8b5
symfony3