Apa cara yang tepat untuk pengujian unit kode PHP7 dengan PHPUnit 4.1 di Magento 2?

23

Ketika saya menulis modul saya, saya mencoba untuk memasok mereka dengan tes unit untuk bagian paling kritis dari aplikasi. Namun, ada saat ini (Magento 2.1.3) beberapa cara tentang cara menulis unit test:

Berbagai cara pengujian

  • Integrasikan dengan bin/magento dev:tests:run unitdan jalankan di atas pengaturan phpunit default yang dibundel dengan Magento.
  • Tulis mereka secara terpisah, jalankan dengan vendor/bin/phpunit app/code/Vendor/Module/Test/Unitdan mengejek semua yang Magento.
  • Menulisnya secara terpisah, mengejek semuanya dan menggunakan versi global sistem-PHPUnit.
  • Tuliskan secara terpisah, jalankan dengan vendor/bin/phpunit, tetapi tetap gunakan \Magento\Framework\TestFramework\Unit\Helper\ObjectManager.

Magento 2 dan PHPUnit

Selain itu, Magento 2 dibundel dengan PHPUnit 4.1.0, yang tidak kompatibel dengan PHP7. Mengetik petunjuk asli (seperti stringdan `int) dan mendeklarasikan tipe pengembalian di tanda tangan Anda akan menimbulkan kesalahan. Misalnya, antarmuka / kelas dengan tanda tangan metode seperti ini:

public function foo(string $bar) : bool;

... tidak akan bisa dipermainkan oleh PHPUnit 4.1.0. :-(

Situasi saya saat ini

Itu karena ini bahwa saya sekarang sebagian besar menulis tes unit saya dengan cara ketiga (dengan memanggil versi sistem-global PHPUnit).

Dalam pengaturan saya, saya telah menginstal PHPUnit 5.6 secara global, sehingga saya dapat menyelesaikan penulisan kode PHP7 yang tepat, tetapi saya harus melakukan beberapa penyesuaian. Sebagai contoh:

phpunit.xml harus terlihat seperti ini sehingga saya dapat menggunakan komposer autoloader:

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

... dan dalam semua setUp()-metode saya , saya memiliki pemeriksaan berikut sehingga saya dapat menulis tes saya dengan kompatibilitas ke depan:

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

Dengan cara ini, ketika tes saya dijalankan oleh built-in PHPUnit Magentos, itu tidak menimbulkan kesalahan.

Pertanyaan saya

Jadi, inilah pertanyaan saya: apakah ini cara menulis unit test yang 'sehat'? Karena sepertinya tidak tepat bagi saya bahwa Magento dibundel dengan sejumlah alat untuk membantu pengujian dan saya tidak dapat menggunakannya karena saya menggunakan PHP7. Saya tahu ada tiket di GitHub yang membahas masalah ini, tapi saya bertanya-tanya bagaimana komunitas saat ini sedang menulis tesnya.

Apakah ada cara untuk menulis unit test di Magento 2 jadi saya tidak perlu 'menurunkan peringkat' kode saya dan masih dapat menggunakan pembantu bawaan Magentos untuk mengejek semua yang disentuh oleh manajer objek? Atau bahkan praktik yang buruk untuk menggunakan manajer objek bahkan dalam pengujian unit Anda?

Saya kehilangan banyak panduan / contoh tentang cara yang tepat tentang cara menguji modul kustom Anda sendiri.

Giel Berkers
sumber
1
Pertanyaan yang bagus.
camdixon

Jawaban:

17

Menggunakan versi PHPUnit yang dibundel, bahkan jika itu kuno, mungkin merupakan cara terbaik untuk pergi karena itu akan memungkinkan menjalankan tes untuk semua modul bersama selama CI.

Saya pikir menulis tes dengan cara yang tidak sesuai dengan kerangka kerja tes yang dibundel sangat mengurangi nilai tes.
Tentu saja Anda dapat mengatur CI untuk menjalankan tes Anda dengan versi PHPUnit yang berbeda, tetapi itu menambah banyak kerumitan pada sistem build.

Yang mengatakan, saya setuju dengan Anda bahwa itu tidak layak mendukung PHP 5.6. Saya menggunakan petunjuk jenis skalar PHP7 dan mengembalikan isyarat jenis sebanyak mungkin (ditambah, saya tidak peduli dengan pasar).

Untuk mengatasi keterbatasan dari perpustakaan ejekan PHPUnit 4.1, setidaknya ada dua solusi sederhana yang saya gunakan di masa lalu:

  1. Misalnya, gunakan kelas anonim atau reguler untuk membuat tes ganda Anda

    $fooIsFalseStub = new class extends Foo implements BarInterface() {
        public function __construct(){};
        public function isSomethingTrue(string $something): bool
        {
            return false;
        }
    };
    
  2. Gunakan PHPUnit yang dibundel tetapi perpustakaan mengejek pihak ketiga yang dapat dimasukkan melalui komposer dengan require-dev, misalnya https://github.com/padraic/mockery . Semua pustaka mengejek yang saya coba dapat dengan mudah digunakan dengan kerangka pengujian apa pun, bahkan versi PHPUnit yang sangat lama seperti 4.1.

Tak satu pun dari mereka memiliki keunggulan teknis di atas yang lain. Anda dapat menerapkan logika uji ganda apa pun yang diperlukan dengan salah satunya.

Secara pribadi saya lebih suka menggunakan kelas anonim karena itu tidak menambah jumlah ketergantungan eksternal, dan juga lebih menyenangkan untuk menulisnya seperti itu.

EDIT :
Untuk menjawab pertanyaan Anda:

Apakah Mockery 'memecahkan' masalah PHPUnit 4.1.0 tidak dapat menangani petunjuk jenis PHP7 dengan benar?

Ya, lihat contoh di bawah ini.

Dan apa manfaat dari kelas anonim dari mengejek?

Menggunakan kelas anonim untuk membuat tes ganda juga "mengejek", itu tidak benar-benar berbeda dari menggunakan perpustakaan mengejek seperti PHPUnits atau Mockery atau lainnya.
Mock hanya pada tipe tes ganda tertentu , terlepas dari bagaimana itu dibuat.
Satu perbedaan kecil antara menggunakan kelas anonim atau perpustakaan mengejek adalah bahwa kelas anonim tidak memiliki ketergantungan perpustakaan eksternal, karena itu hanya PHP biasa. Kalau tidak, tidak ada manfaat atau kekurangan. Ini hanya masalah preferensi. Saya suka karena ini menggambarkan bahwa pengujian bukan tentang kerangka kerja pengujian atau mocking library, pengujian hanya menulis kode yang mengeksekusi sistem yang diuji dan secara otomatis memvalidasi kerjanya.

Dan bagaimana dengan memperbarui versi PHPUnit di file composer.json utama ke 5.3.5 (versi terbaru yang mendukung PHP7 dan memiliki metode mengejek publik (diperlukan oleh tes Magento 2 sendiri))?

Ini mungkin bermasalah karena tes dalam modul lain dan inti hanya diuji dengan PHPUnit 4.1, dan dengan demikian Anda mungkin mengalami kegagalan palsu di CI. Saya pikir yang terbaik untuk tetap dengan versi PHPUnit yang dibundel karena alasan itu. @aksaks mengatakan mereka akan memperbarui PHPUnit, tetapi tidak ada ETA untuk itu.


Contoh tes dengan tes ganda dari kelas yang membutuhkan PHP7 berjalan dengan PHPUnit 4.1, menggunakan perpustakaan 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
sumber
Apakah Mockery 'memecahkan' masalah PHPUnit 4.1.0 tidak dapat menangani petunjuk jenis PHP7 dengan benar? Dan apa manfaat dari kelas anonim dari mengejek? Dan bagaimana dengan memperbarui versi PHPUnit di composer.jsonfile utama ke 5.3.5 (versi terbaru yang mendukung PHP7 dan memiliki metode ejekan publik (diperlukan oleh tes Magento 2 sendiri))? Begitu banyak pertanyaan lagi sekarang ...
Giel Berkers
Memperbarui jawaban saya sebagai jawaban atas pertanyaan Anda @GielBerkers
Vinai
Terima kasih atas jawaban Anda. Benar-benar jelas sekarang! Saya pikir saya akan pergi dan mencoba Mockery kemudian. Kelas anonim sepertinya saya harus menemukan kembali banyak yang sudah ditawarkan Mockery. Saya pertama-tama ingin mempelajari dasar-dasar PHPUnit dan beralih dari sana. Saya pikir sekarang waktunya sudah tiba.
Giel Berkers
Besar! Nikmati menjelajahi Mockery, perpustakaan yang hebat. Saat Anda melakukannya, mungkin periksa juga hamcrest, pustaka pernyataan - itu akan diinstal dengan Mockery secara otomatis.
Vinai
3

Saat ini Magento 2 mendukung versi PHP berikutnya:

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

Ini berarti bahwa semua kode yang ditulis oleh Tim Magento bekerja pada setiap versi yang didukung.

Karenanya Tim Magento tidak menggunakan fitur hanya PHP 7. Fitur PHP 5.6 dapat dicakup dengan PHPUnit 4.1.0.

Menulis kode Anda sendiri, Anda dapat melakukan semua yang Anda inginkan dan menulis tes dengan cara apa pun yang Anda suka. Tetapi saya percaya bahwa Anda tidak akan dapat mempublikasikan ekstensi Anda di Magento Marketplace karena pelanggaran persyaratan.

yaron
sumber
Sebenarnya, PHPUnit 5.7 didukung pada PHP 5.6, PHP 7.0, dan PHP 7.1. PHPUnit 4.8 didukung di PHP 5.3 - 5.6. Jadi meskipun Magento 2 mendukung PHP 5.6, Magento 2 masih dapat ditingkatkan ke PHPUnit 5.7.
Vinai