Как правильно тестировать код PHP7 с помощью PHPUnit 4.1 в Magento 2?

23

Когда я пишу свои модули, я пытаюсь снабдить их модульными тестами для наиболее важных частей приложения. Однако на данный момент (Magento 2.1.3) существует несколько способов написания модульных тестов:

Разные способы тестирования

  • Интегрируйте его bin/magento dev:tests:run unitи запустите поверх стандартных настроек phpunit, входящих в комплект Magento.
  • Запишите их отдельно, запустите их vendor/bin/phpunit app/code/Vendor/Module/Test/Unitи высмеивайте все, что есть Magento.
  • Напишите их отдельно, смоделируйте все и используйте глобально-системную версию PHPUnit.
  • Напишите их отдельно, запустите их vendor/bin/phpunit, но все же используйте \Magento\Framework\TestFramework\Unit\Helper\ObjectManager.

Magento 2 и PHPUnit

Кроме того, Magento 2 поставляется в комплекте с PHPUnit 4.1.0, который не совместим с PHP7. Нативные подсказки типов (вроде stringи `int) и объявление возвращаемых типов в ваших подписях приведут к ошибкам. Например, интерфейс / класс с сигнатурой метода, подобной этой:

public function foo(string $bar) : bool;

... не сможет быть высмеянным PHPUnit 4.1.0. :-(

Моя текущая ситуация

Это связано с тем, что сейчас я в основном пишу свои модульные тесты третьим способом (вызывая глобальную версию PHPUnit).

В моей установке PHPUnit 5.6 установлен глобально, поэтому я могу решить написать правильный PHP7-код, но я должен сделать некоторые изменения. Например:

phpunit.xml должен выглядеть так, чтобы я мог использовать автозагрузчик композитора:

<?xml version="1.0"?>
<phpunit bootstrap="../../../../../../vendor/autoload.php"
         colors="true">
    <testsuites>
        <testsuite name="Testsuite">
            <directory>.</directory>
        </testsuite>
    </testsuites>
</phpunit>

... и во всех моих setUp()-методах у меня есть следующая проверка, чтобы я мог писать свои тесты с прямой совместимостью:

// Only allow PHPUnit 5.x:
if (version_compare(\PHPUnit_Runner_Version::id(), '5', '<')) {
    $this->markTestSkipped();
}

Таким образом, когда мои тесты запускаются встроенным в Magentos PHPUnit, это не выдает ошибку.

Мой вопрос

Итак, вот мой вопрос: это «здоровый» способ написания юнит-тестов? Потому что мне кажется неправильным, что Magento поставляется с целым набором инструментов для помощи в тестировании, и я не могу их использовать, потому что я использую PHP7. Я знаю, что на GitHub есть билеты для решения этой проблемы, но мне интересно, как сообщество в настоящее время пишет свои тесты.

Есть ли способ написать модульные тесты в Magento 2, так что мне не нужно «понижать» мой код, и я все еще могу использовать встроенные в Magentos помощники для насмешки над всем, к чему прикасается менеджер объектов? Или даже плохая практика использовать диспетчер объектов даже в ваших модульных тестах?

Я упускаю множество рекомендаций / примеров того, как правильно проводить модульное тестирование ваших собственных пользовательских модулей.

Гиль Беркерс
источник
1
Какой замечательный вопрос.
Камдиксон

Ответы:

17

Использование встроенной версии PHPUnit, даже если она древняя, вероятно, является наилучшим способом, поскольку это позволит запускать тесты для всех модулей вместе во время CI.

Я думаю, что написание тестов способом, несовместимым с интегрированной средой тестирования, значительно снижает ценность тестов.
Конечно, вы можете настроить CI для запуска ваших тестов с другой версией PHPUnit, но это значительно усложняет систему сборки.

Тем не менее, я согласен с вами, что не стоит поддерживать PHP 5.6. Я использую подсказки скалярного типа PHP7 и подсказки возвращаемого типа в максимально возможной степени (плюс, мне нет дела до рынка).

Чтобы обойти ограничения насмешливой библиотеки PHPUnit 4.1, есть по крайней мере два довольно простых обходных пути, которые я использовал в прошлом:

  1. Используйте анонимные или обычные классы для создания ваших двойников теста, например

    $fooIsFalseStub = new class extends Foo implements BarInterface() {
        public function __construct(){};
        public function isSomethingTrue(string $something): bool
        {
            return false;
        }
    };
  2. Используйте прилагаемый PHPUnit, но стороннюю библиотеку-макет, которая может быть включена через composer require-dev, например, https://github.com/padraic/mockery . Все пробные библиотеки, которые я пробовал, очень легко можно использовать с любой средой тестирования, даже с очень старой версией PHPUnit, такой как 4.1.

Ни один из них не имеет каких-либо технических преимуществ перед другим. Вы можете реализовать любой необходимый тест двойной логики с любым из них.

Лично я предпочитаю использовать анонимные классы, потому что это не увеличивает количество внешних зависимостей, а также гораздо веселее писать их таким образом.

РЕДАКТИРОВАТЬ :
Чтобы ответить на ваши вопросы:

Решает ли Mockery проблему того, что PHPUnit 4.1.0 не может правильно обрабатывать подсказки типа PHP7?

Да, см. Пример ниже.

И каковы преимущества анонимных классов над издевательством?

Использование анонимных классов для создания тестовых двойников также является «насмешливым», на самом деле оно ничем не отличается от использования насмешливой библиотеки, такой как PHPUnits или Mockery, или другой.
Макет только на конкретный тип двойного теста , независимо от того, как он создается.
Небольшое различие между использованием анонимных классов или фиктивной библиотеки состоит в том, что анонимные классы не имеют зависимости от внешней библиотеки, поскольку это просто PHP. В противном случае нет никаких преимуществ или недостатков. Это просто вопрос предпочтений. Мне это нравится, потому что оно иллюстрирует, что тестирование не связано с какой-либо инфраструктурой тестирования или фиктивной библиотекой, тестирование - это просто написание кода, который выполняет тестируемую систему и автоматически проверяет ее работоспособность.

А как насчет обновления версии PHPUnit в основном файле composer.json до 5.3.5 (последняя версия, поддерживающая PHP7 и имеющая публичные методы mocking (требуется для собственных тестов Magento 2))?

Это может быть проблематично, поскольку тесты в других модулях и ядре тестируются только с помощью PHPUnit 4.1, и в этом случае вы можете столкнуться с ложными сбоями в CI. Я думаю, что по этой причине лучше придерживаться связанной версии PHPUnit. @maksek сказал, что они будут обновлять PHPUnit, но ETA для этого не существует.


Пример теста с двойным тестом класса, для которого требуется PHP7, работающий с PHPUnit 4.1, с использованием библиотеки Mockery:

<?php

declare(strict_types = 1);

namespace Example\Php7\Test\Unit;

// Foo is a class that will not work with the mocking library bundled with PHPUnit 4.1 
// The test below creates a mock of this class using mockery and uses it in a test run by PHPUnit 4.1
class Foo
{
    public function isSomethingTrue(string $baz): bool
    {
        return 'something' === $baz; 
    }
}

// This is another class that uses PHP7 scalar argument types and a return type.
// It is the system under test in the example test below.
class Bar
{
    private $foo;

    public function __construct(Foo $foo)
    {
        $this->foo = $foo;
    }

    public function useFooWith(string $s): bool
    {
        return $this->foo->isSomethingTrue($s);
    }
}

// This is an example test that runs with PHPUnit 4.1 and uses mockery to create a test double
// of a class that is only compatible with PHP7 and younger.
class MockWithReturnTypeTest extends \PHPUnit_Framework_TestCase
{
    protected function tearDown()
    {
        \Mockery::close();
    }

    public function testPHPUnitVersion()
    {
        // FYI to show this test runs with PHPUnit 4.1
        $this->assertSame('4.1.0', \PHPUnit_Runner_Version::id());
    }

    public function testPhpVersion()
    {
        // FYI this test runs with PHP7
        $this->assertSame('7.0.15', \PHP_VERSION);
    }

    // Some nonsensical example test using a mock that has methods with
    // scalar argument types and PHP7 return types.
    public function testBarUsesFoo()
    {
        $stubFoo = \Mockery::mock(Foo::class);
        $stubFoo->shouldReceive('isSomethingTrue')->with('faz')->andReturn(false);
        $this->assertFalse((new Bar($stubFoo))->useFooWith('faz'));
    }
}
Vinai
источник
Решает ли Mockery проблему того, что PHPUnit 4.1.0 не может правильно обрабатывать подсказки типа PHP7? И каковы преимущества анонимных классов над издевательством? А как насчет обновления версии PHPUnit в главном composer.jsonфайле до 5.3.5 (последняя версия, поддерживающая PHP7 и имеющая публичные методы mocking (требуется для собственных тестов Magento 2))? Так много вопросов сейчас ...
Гил
Обновлен мой ответ в ответ на ваш вопрос @GielBerkers
Vinai
Спасибо за ваш отличный ответ. Теперь это совершенно ясно! Я думаю, что я пойду и попробую Mockery тогда. Похоже, что анонимные классы должны заново изобретать то, что уже предлагает Mockery. Сначала я хотел изучить основы PHPUnit и двигаться дальше. Я думаю, что сейчас время.
Giel Berkers
Большой! Наслаждайтесь изучением Mockery, отличной библиотеки. Пока вы это делаете, возможно, проверьте Hamcrest, также библиотеку утверждений - она ​​будет установлена ​​вместе с Mockery автоматически.
Vinai
3

Сейчас Magento 2 поддерживает следующие версии PHP:

"php": "~5.6.5|7.0.2|7.0.4|~7.0.6"

Это означает, что весь код, написанный командой Magento, работает на каждой поддерживаемой версии.

Следовательно, Magento Team не использует только PHP 7. Возможности PHP 5.6 могут быть покрыты PHPUnit 4.1.0.

При написании собственного кода вы можете делать все, что хотите, и писать тесты любым удобным для вас способом. Но я считаю, что вы не сможете опубликовать свое расширение на Magento Marketplace из-за нарушения требований.

yaronish
источник
На самом деле, PHPUnit 5.7 поддерживается в PHP 5.6, PHP 7.0 и PHP 7.1. PHPUnit 4.8 был поддержан на PHP 5.3 - 5.6. Таким образом, хотя Magento 2 поддерживает PHP 5.6, он все же может перейти на PHPUnit 5.7.
Vinai