Jaki jest właściwy sposób testowania kodu PHP7 za pomocą PHPUnit 4.1 w Magento 2?

23

Pisząc moje moduły, próbuję dostarczyć im testy jednostkowe dla najbardziej krytycznych części aplikacji. Istnieje jednak obecnie (Magento 2.1.3) kilka sposobów pisania testów jednostkowych:

Różne sposoby testowania

  • Zintegruj go bin/magento dev:tests:run uniti uruchom na domyślnych ustawieniach phpunit dołączonych do Magento.
  • Napisz je osobno, uruchom je vendor/bin/phpunit app/code/Vendor/Module/Test/Uniti kpij z wszystkiego, co jest Magento.
  • Napisz je osobno, wyśmiewaj wszystko i użyj systemowej wersji PHPUnit.
  • Napisz je osobno, uruchom je vendor/bin/phpunit, ale nadal korzystaj z \Magento\Framework\TestFramework\Unit\Helper\ObjectManager.

Magento 2 i PHPUnit

Poza tym Magento 2 jest dostarczany w pakiecie z PHPUnit 4.1.0, który nie jest kompatybilny z PHP7. Podpisywanie typów tubylców (jak stringi `int) i deklarowanie typów zwrotów w twoich podpisach spowoduje zgłaszanie błędów. Na przykład interfejs / klasa z takim podpisem metody:

public function foo(string $bar) : bool;

... nie będzie można kpić z PHPUnit 4.1.0. :-(

Moja obecna sytuacja

Wynika to z tego, że teraz głównie piszę testy jednostkowe w trzeci sposób (wywołując globalną wersję PHPUnit).

W moim ustawieniu mam PHPUnit 5.6 zainstalowany globalnie, więc mogę rozwiązać pisanie właściwego kodu PHP7, ale muszę wprowadzić kilka poprawek. Na przykład:

phpunit.xml musi wyglądać tak, aby móc skorzystać z autoloadera kompozytora:

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

... i we wszystkich moich setUp()metodach mam następującą kontrolę, aby móc pisać testy z kompatybilnością do przodu:

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

W ten sposób, gdy moje testy są uruchamiane przez PHPUnit wbudowanego w Magentos, nie generuje to błędu.

Moje pytanie

Oto moje pytanie: czy to „zdrowy” sposób pisania testów jednostkowych? Ponieważ nie wydaje mi się właściwe, aby Magento zawierało całą gamę narzędzi pomocnych w testowaniu i nie mogę ich używać, ponieważ używam PHP7. Wiem, że na GitHub są bilety, które rozwiązują ten problem, ale zastanawiam się, jak społeczność obecnie pisze testy.

Czy istnieje sposób na pisanie testów jednostkowych w Magento 2, więc nie muszę „obniżać” kodu i nadal mogę korzystać z wbudowanych pomocników Magentos do kpienia z wszystkiego, czego dotknie menedżer obiektów? Czy może nawet złą praktyką jest używanie menedżera obiektów nawet w testach jednostkowych?

Brakuje mi wielu wskazówek / przykładów, w jaki sposób testować jednostki własnych modułów niestandardowych.

Giel Berkers
źródło
1
Co za świetne pytanie.
camdixon,

Odpowiedzi:

17

Korzystanie z dołączonej wersji PHPUnit, nawet jeśli jest starodawne, jest prawdopodobnie najlepszym rozwiązaniem, ponieważ pozwoli na uruchomienie testów dla wszystkich modułów razem podczas CI.

Myślę, że pisanie testów w sposób niezgodny z dołączoną platformą testową znacznie obniża wartość testów.
Oczywiście możesz skonfigurować CI do uruchamiania testów z inną wersją PHPUnit, ale to dodaje wiele komplikacji do systemu kompilacji.

To powiedziawszy, zgadzam się z tobą, że nie warto wspierać PHP 5.6. Używam podpowiedzi typu skalarnego PHP7 i podpowiedzi typu zwrotnego w jak największym stopniu (plus, nie dbam o rynek).

Aby obejść ograniczenia biblioteki próbnej PHPUnit 4.1, istnieją co najmniej dwa dość proste obejścia, z których korzystałem w przeszłości:

  1. Używaj anonimowych lub regularnych klas, aby na przykład budować swoje podwójne testowe

    $fooIsFalseStub = new class extends Foo implements BarInterface() {
        public function __construct(){};
        public function isSomethingTrue(string $something): bool
        {
            return false;
        }
    };
  2. Skorzystaj z dołączonej biblioteki PHPUnit, ale ze strony zewnętrznej kpiącej biblioteki, którą można włączyć za pomocą kompozytora require-dev, na przykład https://github.com/padraic/mockery . Ze wszystkich wypróbowanych bibliotek, których wypróbowałem, można bardzo łatwo używać dowolnego frameworka testowego, nawet bardzo starej wersji PHPUnit, takiej jak 4.1.

Żadna z nich nie ma przewagi technicznej nad drugą. Możesz zaimplementować dowolną wymaganą podwójną logikę testową.

Osobiście wolę używać anonimowych klas, ponieważ nie zwiększa to liczby zewnętrznych zależności, a także fajniej jest je pisać w ten sposób.

EDYCJA :
Aby odpowiedzieć na twoje pytania:

Czy Mockery „rozwiązuje” problem polegający na tym, że PHPUnit 4.1.0 nie jest w stanie poprawnie obsługiwać wskazówek typu PHP7?

Tak, patrz przykład poniżej.

A jakie są zalety anonimowych zajęć nad kpiną?

Używanie anonimowych klas do tworzenia podwójnych testów jest również „kpieniem”, nie różni się tak naprawdę od używania kpiącej biblioteki, takiej jak PHPUnits lub Mockery, lub innej.
Makieta dotyczy tylko określonego typu podwójnego testu , niezależnie od tego, jak jest tworzona.
Jedną niewielką różnicą między używaniem anonimowych klas lub fałszywej biblioteki jest to, że anonimowe klasy nie są zależne od biblioteki zewnętrznej, ponieważ są to zwykłe PHP. W przeciwnym razie nie ma żadnych zalet ani wad. To po prostu kwestia preferencji. Podoba mi się, ponieważ pokazuje, że w testowaniu nie chodzi o żadną platformę testową ani bibliotekę kpiącą, testowanie to tylko pisanie kodu, który wykonuje testowany system i automatycznie sprawdza, czy działa.

A co powiesz na aktualizację wersji PHPUnit w głównym pliku composer.json do 5.3.5 (najnowsza wersja obsługująca PHP7 i posiadająca publiczne metody kpienia (wymagane przez własne testy Magento 2))?

Może to być problematyczne, ponieważ testy w innych modułach i rdzeniu są testowane tylko z PHPUnit 4.1 i jako takie mogą wystąpić fałszywe niepowodzenia w CI. Myślę, że z tego powodu najlepiej trzymać się dołączonej wersji PHPUnit. @maksek powiedział, że będą aktualizować PHPUnit, ale nie ma na to ETA.


Przykład testu z podwójnym testem klasy, który wymaga PHP7 działającego z PHPUnit 4.1, przy użyciu biblioteki 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
źródło
Czy Mockery „rozwiązuje” problem polegający na tym, że PHPUnit 4.1.0 nie jest w stanie poprawnie obsługiwać wskazówek typu PHP7? A jakie są zalety anonimowych zajęć nad kpiną? A co powiesz na aktualizację wersji PHPUnit w głównym composer.jsonpliku do 5.3.5 (najnowsza wersja obsługująca PHP7 i posiadająca publiczne metody kpiące (wymagane przez własne testy Magento 2))? Tyle pytań teraz ...
Giel Berkers
Zaktualizowałem moją odpowiedź w odpowiedzi na twoje pytanie @GielBerkers
Vinai
Dziękuję za wspaniałą odpowiedź. Teraz jest całkowicie jasne! Myślę, że wtedy pójdę i spróbuję. Anonimowe zajęcia wydają się, że muszę na nowo wymyślić wiele, co już oferuje Kpina. Najpierw chciałem nauczyć się podstaw PHPUnit i od tego momentu zacząć. Myślę, że teraz nadszedł czas.
Giel Berkers
Świetny! Ciesz się odkrywaniem kpiny, świetnej biblioteki. Podczas gdy jesteś przy tym, może sprawdź też hamcrest, bibliotekę asercji - zostanie automatycznie zainstalowana z kpiną.
Vinai
3

Obecnie Magento 2 obsługuje kolejne wersje PHP:

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

Oznacza to, że cały kod napisany przez Magento Team działa na każdej obsługiwanej wersji.

Dlatego Magento Team nie korzysta tylko z funkcji PHP 7. Funkcje PHP 5.6 mogą być objęte PHPUnit 4.1.0.

Pisząc własny kod, możesz robić wszystko, co chcesz i pisać testy w dowolny sposób. Uważam jednak, że ze względu na naruszenie wymagań nie będzie można opublikować swojego rozszerzenia w Magento Marketplace.

yaronish
źródło
W rzeczywistości PHPUnit 5.7 jest obsługiwany w PHP 5.6, PHP 7.0 i PHP 7.1. PHPUnit 4.8 był obsługiwany w PHP 5.3 - 5.6. Więc nawet jeśli Magento 2 obsługuje PHP 5.6, nadal można go zaktualizować do PHPUnit 5.7.
Vinai