Cách thích hợp để kiểm tra đơn vị mã PHP7 với PHPUnit 4.1 trong Magento 2 là gì?


23

Khi tôi viết các mô-đun của mình, tôi đang cố gắng cung cấp cho họ các bài kiểm tra đơn vị cho các phần quan trọng nhất của ứng dụng. Tuy nhiên, hiện tại có một số cách (Magento 2.1.3) về cách viết bài kiểm tra đơn vị:

Những cách kiểm tra khác nhau

  • Tích hợp nó với bin/magento dev:tests:run unitvà chạy nó trên đầu cài đặt phpunit mặc định đi kèm với Magento.
  • Viết chúng riêng biệt, chạy chúng với vendor/bin/phpunit app/code/Vendor/Module/Test/Unit và chế nhạo mọi thứ là Magento.
  • Viết chúng một cách riêng biệt, mô phỏng mọi thứ và sử dụng phiên bản toàn cầu của hệ thống PHPUnit.
  • Viết chúng riêng biệt, chạy chúng với vendor/bin/phpunit, nhưng vẫn sử dụng \Magento\Framework\TestFramework\Unit\Helper\ObjectManager.

Magento 2 và PHPUnit

Bên cạnh đó, Magento 2 đi kèm với PHPUnit 4.1.0, không tương thích với PHP7. Kiểu bản địa gợi ý kiểu (như stringvà `int) và khai báo kiểu trả về trong chữ ký của bạn sẽ đưa ra lỗi. Ví dụ: một giao diện / lớp có chữ ký phương thức như thế này:

public function foo(string $bar) : bool;

... sẽ không thể bị chế giễu bởi PHPUnit 4.1.0. :-(

Tình hình hiện tại của tôi

Chính vì điều này mà giờ đây tôi chủ yếu viết các bài kiểm tra đơn vị của mình theo cách thứ ba (bằng cách gọi một phiên bản PHPUnit toàn hệ thống).

Trong thiết lập của tôi, tôi đã cài đặt PHPUnit 5.6 trên toàn cầu, vì vậy tôi có thể giải quyết việc viết mã PHP7 phù hợp, nhưng tôi phải thực hiện một số điều chỉnh. Ví dụ:

phpunit.xml phải trông như thế này để tôi có thể sử dụng trình tải tự động của nhà soạn nhạc:

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

... Và trong tất cả các phương pháp của tôi setUp(), tôi có kiểm tra sau để tôi có thể viết các bài kiểm tra của mình với khả năng tương thích về phía trước:

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

Theo cách này, khi các bài kiểm tra của tôi được chạy bởi PHPUnit tích hợp của Magentos, nó không gây ra lỗi.

Câu hỏi của tôi

Vì vậy, đây là câu hỏi của tôi: đây có phải là cách viết bài kiểm tra đơn vị 'lành mạnh' không? Bởi vì có vẻ không đúng với tôi khi Magento đi kèm với cả đống công cụ để hỗ trợ kiểm tra và tôi không thể sử dụng chúng vì tôi đang sử dụng PHP7. Tôi biết có vé trên GitHub giải quyết vấn đề này, nhưng tôi tự hỏi làm thế nào cộng đồng hiện đang viết bài kiểm tra.

Có cách nào để viết bài kiểm tra đơn vị trong Magento 2 để tôi không phải 'hạ cấp' mã của mình và vẫn có thể sử dụng trình trợ giúp tích hợp của Magentos để chế nhạo mọi thứ mà trình quản lý đối tượng chạm vào không? Hoặc thậm chí là thực hành xấu khi sử dụng trình quản lý đối tượng ngay cả trong các bài kiểm tra đơn vị của bạn?

Tôi đang thiếu rất nhiều hướng dẫn / ví dụ về cách thức phù hợp để kiểm tra các mô-đun tùy chỉnh của riêng bạn.


1
Thật là một câu hỏi tuyệt vời.
camdixon

Câu trả lời:


17

Sử dụng phiên bản PHPUnit đi kèm, ngay cả khi nó cổ xưa, có lẽ là cách tốt nhất vì điều đó sẽ cho phép chạy các bài kiểm tra cho tất cả các mô-đun cùng nhau trong CI.

Tôi nghĩ rằng viết bài kiểm tra theo cách không tương thích với khung kiểm tra đi kèm làm giảm đáng kể giá trị của các bài kiểm tra.
Tất nhiên, bạn có thể thiết lập CI để chạy thử nghiệm của mình với một phiên bản PHPUnit khác, nhưng điều đó làm tăng thêm sự phức tạp cho hệ thống xây dựng.

Điều đó nói rằng, tôi đồng ý với bạn rằng nó không đáng để hỗ trợ PHP 5.6. Tôi sử dụng gợi ý kiểu vô hướng PHP7 và gợi ý kiểu trả về càng nhiều càng tốt (cộng với, tôi không quan tâm đến thị trường).

Để khắc phục những hạn chế của thư viện mô phỏng PHPUnit 4.1, có ít nhất hai cách giải quyết khá đơn giản mà tôi đã sử dụng trong quá khứ:

  1. Sử dụng các lớp ẩn danh hoặc thông thường để xây dựng đôi bài kiểm tra của bạn, ví dụ

    $fooIsFalseStub = new class extends Foo implements BarInterface() {
        public function __construct(){};
        public function isSomethingTrue(string $something): bool
        {
            return false;
        }
    };
  2. Sử dụng PHPUnit kèm theo nhưng thư viện giả định của bên thứ ba có thể được bao gồm thông qua nhà soạn nhạc với require-dev, ví dụ https://github.com/padraic/mockery . Tất cả các thư viện giả mà tôi đã thử có thể dễ dàng được sử dụng với bất kỳ khung thử nghiệm nào, ngay cả một phiên bản PHPUnit rất cũ như 4.1.

Không ai trong số họ có bất kỳ lợi thế kỹ thuật so với người khác. Bạn có thể thực hiện bất kỳ logic kiểm tra kép cần thiết với một trong hai.

Cá nhân tôi thích sử dụng các lớp ẩn danh vì điều đó không thêm vào số lượng phụ thuộc bên ngoài, và cũng vui hơn khi viết chúng theo cách đó.

EDIT :
Để trả lời câu hỏi của bạn:

Mockery có 'giải quyết' được vấn đề của PHPUnit 4.1.0 không thể xử lý đúng gợi ý kiểu PHP7 không?

Vâng, xem ví dụ dưới đây.

Và những lợi ích của các lớp ẩn danh so với chế nhạo là gì?

Sử dụng các lớp ẩn danh để tạo các bài kiểm tra nhân đôi cũng là "chế nhạo", nó không thực sự khác với việc sử dụng một thư viện giả, chẳng hạn như PHPUnits hoặc Mockery hoặc khác.
Một giả chỉ là trên loại thử nghiệm cụ thể , bất kể nó được tạo ra như thế nào.
Một điểm khác biệt nhỏ giữa việc sử dụng các lớp ẩn danh hoặc thư viện giả là các lớp ẩn danh không có sự phụ thuộc thư viện bên ngoài, vì đó chỉ là PHP đơn giản. Nếu không thì không có lợi ích hay hạn chế. Nó chỉ đơn giản là một vấn đề ưu tiên. Tôi thích nó bởi vì nó minh họa rằng thử nghiệm không phải là về bất kỳ khung thử nghiệm hoặc thư viện giả định nào, thử nghiệm chỉ là viết mã thực thi hệ thống đang thử nghiệm và tự động xác nhận nó hoạt động.

Và làm thế nào về việc cập nhật phiên bản PHPUnit trong tệp composer.json chính lên 5.3.5 (phiên bản mới nhất hỗ trợ PHP7 và có các phương thức giả định công khai (được yêu cầu bởi các thử nghiệm riêng của Magento 2))?

Điều này có thể có vấn đề vì các thử nghiệm trong các mô-đun khác và lõi chỉ được thử nghiệm với PHPUnit 4.1 và do đó bạn có thể gặp phải các lỗi sai trong CI. Tôi nghĩ rằng tốt nhất là gắn bó với phiên bản PHPUnit đi kèm vì lý do đó. @maksek cho biết họ sẽ cập nhật PHPUnit, nhưng không có ETA cho điều đó.


Ví dụ về một bài kiểm tra với một bài kiểm tra gấp đôi của một lớp yêu cầu PHP7 chạy với PHPUnit 4.1, sử dụng thư viện 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'));
    }
}

Mockery có 'giải quyết' được vấn đề của PHPUnit 4.1.0 không thể xử lý đúng gợi ý kiểu PHP7 không? Và những lợi ích của các lớp ẩn danh so với chế nhạo là gì? Và làm thế nào về việc cập nhật phiên bản PHPUnit trong composer.jsontệp chính lên 5.3.5 (phiên bản mới nhất hỗ trợ PHP7 và có các phương thức giả định công khai (được yêu cầu bởi các thử nghiệm riêng của Magento 2))? Vì vậy, nhiều câu hỏi hơn bây giờ ...
Giel Berkers

Đã cập nhật câu trả lời của tôi để trả lời câu hỏi của bạn @GielBerkers
Vinai

Cảm ơn câu trả lời tuyệt vời của bạn. Bây giờ thì hoàn toàn rõ ràng! Tôi nghĩ rằng tôi sẽ đi và thử Mockery sau đó. Các lớp ẩn danh có vẻ như tôi phải phát minh lại rất nhiều thứ mà Mockery đã cung cấp. Trước tiên tôi muốn tìm hiểu những điều cơ bản về PHPUnit và tiếp tục từ đó. Tôi nghĩ rằng bây giờ thời gian là ở đó.
Giel Berkers

Tuyệt quá! Thích khám phá Mockery, một thư viện tuyệt vời. Trong khi bạn đang ở đó, cũng có thể kiểm tra hamcrest, một thư viện xác nhận - nó sẽ được cài đặt tự động với Mockery.
Vinai

3

Ngay bây giờ Magento 2 hỗ trợ các phiên bản PHP tiếp theo:

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

Điều đó có nghĩa là tất cả các mã được viết bởi Magento Team đều hoạt động trên mọi phiên bản được hỗ trợ.

Do đó Magento Team không sử dụng các tính năng chỉ dành cho PHP 7. Các tính năng PHP 5.6 có thể được trình bày bằng PHPUnit 4.1.0.

Viết mã của riêng bạn, bạn có thể làm tất cả những gì bạn muốn và viết bài kiểm tra theo bất kỳ cách nào bạn muốn. Nhưng tôi tin rằng bạn sẽ không thể xuất bản tiện ích mở rộng của mình trên Magento Marketplace vì vi phạm yêu cầu.


Trên thực tế, PHPUnit 5.7 được hỗ trợ trên PHP 5.6, PHP 7.0 và PHP 7.1. PHPUnit 4.8 đã được hỗ trợ trên PHP 5.3 - 5.6. Vì vậy, mặc dù Magento 2 hỗ trợ PHP 5.6, nó vẫn có thể nâng cấp lên PHPUnit 5.7.
Vinai
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.