Giả một phần phpunit

Có một điều thú vị nữa mà tôi muốn cho bạn thấy với các bài kiểm tra tích hợp. Đó là một sự pha trộn kỳ lạ, không tự nhiên giữa các bài kiểm tra đơn vị và bài kiểm tra tích hợp. tôi thích nó. Tôi gọi đó là chế giễu một phần

Ngay bây giờ, chúng tôi đang lấy EnclosureBuilderService từ thùng chứa. Nhưng có một lựa chọn khác. Thay vào đó, hãy tạo nó theo cách thủ công - new EnclosureBuilderService() - và chuyển các phần phụ thuộc theo cách thủ công. Ví dụ: chuyển $this->getEntityManager() làm đối số đầu tiên

dòng 1 - 13 lớp EnclosureBuilderServiceIntegrationTest mở rộng KernelTestCase{. dòng 16 - 22kiểm tra hàm công khaiItBuildsEnclosureWithDefaultSpecifications(){. dòng 25 - 32$enclosureBuilderService = new EnclosureBuilderService($this->getEntityManager(),. dòng 35);. dòng 37 - 56}. dòng 58 - 73}

Tại sao chúng ta sẽ làm điều này? . Tại vì. nếu nó hữu ích, chúng ta có thể chế nhạo một số phụ thuộc nhất định. Chuẩn rồi. Thay vì tìm nạp DinosaurFactory từ vùng chứa, hãy giả lập nó. $dinoFactory = $this->createMock(DinosaurFactory::class)

dòng 1 - 13 lớp EnclosureBuilderServiceIntegrationTest mở rộng KernelTestCase{. dòng 16 - 22kiểm tra hàm công khaiItBuildsEnclosureWithDefaultSpecifications(){. dòng 25 - 27$dinoFactory = $this->createMock(DinosaurFactory. tầng lớp);. dòng 29 - 56}. dòng 58 - 73}

Sau đó, $dinoFactory->expects($this->any()) - chúng tôi không thực sự quan tâm - với ->method('growFromSpecification')->willReturn(new Dinosaur())

dòng 1 - 22kiểm tra chức năng công khaiItBuildsEnclosureWithDefaultSpecifications(){. dòng 25 - 28$dinoFactory->expects($this->any())->method('growFromSpecification')->willReturn(new Dinosaur());. dòng 32 - 56}. dòng 58 - 75

Vượt qua điều này như là đối số thứ hai. Điều này không đặc biệt hữu ích trong tình huống này. Nhưng đôi khi, có thể mô phỏng - và kiểm soát - một hoặc hai thành phần phụ thuộc trong thử nghiệm tích hợp thực sự tuyệt vời

dòng 1 - 22kiểm tra chức năng công khaiItBuildsEnclosureWithDefaultSpecifications(){. dòng 25 - 32$enclosureBuilderService = new EnclosureBuilderService($this->getEntityManager(),$dinoFactory);. dòng 37 - 56}. dòng 58 - 75

Ok bạn test thử

./vendor/bin/phpunit --filter testItBuildsEnclosureWithTheDefaultSpecification

trời ơi. Nó thất bại. Kỳ dị. Chỉ có 1 con khủng long. nhưng phải có 3. Chuyện gì đang xảy ra vậy? . PhpUnit đủ thông minh để lấy một đối tượng khủng long này và trả lại nó mỗi khi

./vendor/bin/phpunit --filter testItBuildsEnclosureWithTheDefaultSpecification

0 được gọi. Nhưng với Doctrine, có vẻ như chúng ta đang yêu cầu nó lưu cùng một đối tượng

./vendor/bin/phpunit --filter testItBuildsEnclosureWithTheDefaultSpecification

1. không phải ba con khủng long riêng biệt. Kết quả sẽ là một công viên giải trí ít ly kỳ hơn

Cách khắc phục là thay đổi cái này thành

./vendor/bin/phpunit --filter testItBuildsEnclosureWithTheDefaultSpecification

2 và truyền cho nó một hàm. Điều này sẽ được gọi mỗi khi

./vendor/bin/phpunit --filter testItBuildsEnclosureWithTheDefaultSpecification

0 được gọi. Và vì nó được thông qua một đối số

./vendor/bin/phpunit --filter testItBuildsEnclosureWithTheDefaultSpecification

4, cuộc gọi lại cũng nhận được điều này. Đó là cách tốt nhất để trả về các giá trị khác nhau dựa trên các đối số

dòng 1 - 22kiểm tra chức năng công khaiItBuildsEnclosureWithDefaultSpecifications(){. dòng 25 - 28$dinoFactory->expects($this->any()). dòng 30->willReturnCallback(function($spec) {. dòng 32});. dòng 34 - 58}. dòng 60 - 77

Chúng tôi không cần điều đó trong trường hợp này. Chúng tôi sẽ chỉ nói

./vendor/bin/phpunit --filter testItBuildsEnclosureWithTheDefaultSpecification

5. Và đó là nó. Thử lại các bài kiểm tra

dòng 1 - 22kiểm tra chức năng công khaiItBuildsEnclosureWithDefaultSpecifications(){. dòng 25 - 28$dinoFactory->expects($this->any()). dòng 30->willReturnCallback(function($spec) {return new Dinosaur();});. dòng 34 - 58}. dòng 60 - 77

./vendor/bin/phpunit --filter testItBuildsEnclosureWithTheDefaultSpecification

Chúng tôi hiểu rồi. Vì vậy, các bài kiểm tra tích hợp là một trong những điều yêu thích, yêu thích, yêu thích của tôi bởi vì chúng rất thực dụng. Khi tôi nghĩ về việc kiểm tra một lớp, đây là logic tôi tuân theo

Trong bài trước, chúng ta đã tìm hiểu về các khái niệm rất quan trọng đó là

$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->setMethods(null)
    ->getMock();
4 và
$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->setMethods(null)
    ->getMock();
5. Khái niệm này là tâm điểm của 1 bài kiểm tra đơn vị thành công và một khi nó đã đi sâu vào tâm trí của bạn, bạn sẽ bắt đầu nhận ra đơn vị có ích và đơn giản như thế nào

Có một thứ khác mà tôi muốn biết đó là. tạo bài kiểm tra đơn vị chỉ đơn giản là 1 trò chơi giải đố, bạn chỉ cần đi từng bước 1, và đảm bảo rằng tất cả các mảnh ghép được khớp đúng với nhau. Tôi hy vọng sẽ làm rõ điều này sau khi kết thúc bài viết này

phương pháp giả

You are known about

$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->setMethods(null)
    ->getMock();
6 and
$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->setMethods(null)
    ->getMock();
7. Có 1 khái niệm khác cũng khá quan trọng bạn cần biết đó là.
$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->setMethods(null)
    ->getMock();
8

đối tượng giả

$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->setMethods(null)
    ->getMock();
4 là một đối tượng giả mà chúng ta có kiểm tra toàn diện, đối tượng này kéo dài từ lớp đang liên quan đến kiểm tra đơn vị

Phương pháp sơ khai

$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->setMethods(null)
    ->getMock();
5 là 1 phương thức được bao gồm bên trong 1
$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->setMethods(null)
    ->getMock();
4, phương thức này trả về null theo mặc định, nhưng có thể thay đổi dễ dàng

Phương pháp giả

$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->setMethods(['authorizeAndCapture', 'foobar'])
    ->getMock();
2 cũng rất đơn giản, nó làm việc giống hoàn toàn với phương pháp ban đầu. Nói cách khác, mọi dòng mã bên trong phương thức mà bạn đang chế nhạo sẽ được chạy và sẽ không trả về giá trị rỗng theo mặc định (ngoại trừ trường hợp phương thức ban đầu được trả về như thế)

Mark Nichols đã đưa ra một lời giải thích rất hay về sự khác nhau giữa phương pháp giả và phương pháp sơ khai

Nói một cách đơn giản,

$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->setMethods(null)
    ->getMock();
8 rất hữu ích khi bạn muốn mã bên trong nó chạy, nhưng cũng muốn thực hiện một số xác nhận theo hành vi của phương thức. Ví dụ một số khẳng định như tham số cụ thể được truyền vào phương pháp hoặc phương pháp được gọi chính xác 3 lần hoặc không được chạy lần nào

Đừng lo lắng nếu nó không được xác định rõ ràng ngay được

4 cách sử dụng getMockBuilder()

Chúng ta đã sử dụng PHPUnit API

$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->setMethods(['authorizeAndCapture', 'foobar'])
    ->getMock();
4 nhưng bạn biết đấy, có 4 cách khác nhau để tạo đối tượng?

/**
 * Specifies the subset of methods to mock. Default is to mock none of them.
 *
 * @return MockBuilder
 */
public function setMethods(array $methods = null)
{
    $this->methods = $methods;
    return $this;
}
TH1. Không gọi phương thức
$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->setMethods(['authorizeAndCapture', 'foobar'])
    ->getMock();
5

Đây là cách đơn giản nhất

$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->getMock();

Đoạn này sẽ tạo ra 1 đối tượng giả trong các phương thức của nó

  • Tất cả đều là sơ khai,
  • Tất cả trả về null theo mặc định,
  • Ghi đè nhanh
TH2. Truyền vào một mảng trống

You can transfer into an array empty for method

$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->setMethods(['authorizeAndCapture', 'foobar'])
    ->getMock();
5

$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->setMethods([])
    ->getMock();

Điều này sẽ tạo ra 1 đối tượng giả hoàn toàn giống với cách bạn không gọi phương thức

$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->setMethods(['authorizeAndCapture', 'foobar'])
    ->getMock();
5. Các phương thức trong đối tượng này

  • Tất cả đều là sơ khai,
  • Tất cả trả về null theo mặc định,
  • Ghi đè nhanh
TH3. Truyền vào null

You can also transfer to

$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->setMethods(['authorizeAndCapture', 'foobar'])
    ->getMock();
9

________số 8_______

Trường hợp này sẽ tạo ra 1 đối tượng giả, trong đó các phương thức

  • Tất cả đều là giả,
  • Chạy mã thực tế trong phương thức ban đầu khi được gọi,
  • Không cho phép ghi đè giá trị trả về
TH4. Truyền vào một mảng chứa tên các phương pháp
$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->setMethods(['authorizeAndCapture', 'foobar'])
    ->getMock();

Trong trường hợp này, đối tượng giả được tạo ra có các phương thức có đặc điểm của 3 trường hợp trước

  • With the method you given in array

    • Tất cả đều là sơ khai,
    • Tất cả trả về null theo mặc định,
    • Ghi đè nhanh
  • With the left method

    • Tất cả đều là giả,
    • Chạy mã thực tế trong phương thức ban đầu khi được gọi,
    • Không cho phép ghi đè giá trị trả về

Điều này có nghĩa là trong đối tượng giả mạo

<?php

namespace App;

class BadCode
{
    protected $user;

    public function __construct(array $user)
    {
        $this->user = $user;
    }

    public function authorize($password)
    {
        if ($this->checkPassword($password)) {
            return true;
        }

        return false;
    }

    protected function checkPassword($password)
    {
        if (empty($this->user['password']) || $this->user['password'] !== $password) {
            echo 'YOU SHALL NOT PASS';
            exit;
        }

        return true;
    }
}
0 thì
<?php

namespace App;

class BadCode
{
    protected $user;

    public function __construct(array $user)
    {
        $this->user = $user;
    }

    public function authorize($password)
    {
        if ($this->checkPassword($password)) {
            return true;
        }

        return false;
    }

    protected function checkPassword($password)
    {
        if (empty($this->user['password']) || $this->user['password'] !== $password) {
            echo 'YOU SHALL NOT PASS';
            exit;
        }

        return true;
    }
}
1 và
<?php

namespace App;

class BadCode
{
    protected $user;

    public function __construct(array $user)
    {
        $this->user = $user;
    }

    public function authorize($password)
    {
        if ($this->checkPassword($password)) {
            return true;
        }

        return false;
    }

    protected function checkPassword($password)
    {
        if (empty($this->user['password']) || $this->user['password'] !== $password) {
            echo 'YOU SHALL NOT PASS';
            exit;
        }

        return true;
    }
}
2 sẽ trả về null theo mặc định hoặc bạn có thể ghi đè giá trị trả về, còn tất cả các phương thức khác trong đối tượng này sẽ chạy mã cấm đầu

in sao lại cần phương pháp giả?

Tôi sẽ bắt đầu với 1 ví dụ rất đơn giản mà bạn có thể gặp nhiều trong suốt cuộc đời lập trình

<?php

namespace App;

class BadCode
{
    protected $user;

    public function __construct(array $user)
    {
        $this->user = $user;
    }

    public function authorize($password)
    {
        if ($this->checkPassword($password)) {
            return true;
        }

        return false;
    }

    protected function checkPassword($password)
    {
        if (empty($this->user['password']) || $this->user['password'] !== $password) {
            echo 'YOU SHALL NOT PASS';
            exit;
        }

        return true;
    }
}

Một lớp đơn giản có thể hiển thị 1 vấn đề đơn giản. Nếu mật khẩu của người dùng không được đặt, sẽ báo lỗi cho người dùng và dừng tập lệnh

Vấn đề với lớp này đó là nó gọi là

<?php

namespace App;

class BadCode
{
    protected $user;

    public function __construct(array $user)
    {
        $this->user = $user;
    }

    public function authorize($password)
    {
        if ($this->checkPassword($password)) {
            return true;
        }

        return false;
    }

    protected function checkPassword($password)
    {
        if (empty($this->user['password']) || $this->user['password'] !== $password) {
            echo 'YOU SHALL NOT PASS';
            exit;
        }

        return true;
    }
}
3 làm cho mã PHP hiện đang chạy sẽ bị dừng, bao gồm tất cả các bài kiểm tra đơn vị bạn đang chạy. Có gì mà sai

Một giải pháp tối ưu đó là không gọi

<?php

namespace App;

class BadCode
{
    protected $user;

    public function __construct(array $user)
    {
        $this->user = $user;
    }

    public function authorize($password)
    {
        if ($this->checkPassword($password)) {
            return true;
        }

        return false;
    }

    protected function checkPassword($password)
    {
        if (empty($this->user['password']) || $this->user['password'] !== $password) {
            echo 'YOU SHALL NOT PASS';
            exit;
        }

        return true;
    }
}
3 trong đoạn mã đó nữa. Bạn nên cân nhắc trả lại giá trị thay vì gọi là
<?php

namespace App;

class BadCode
{
    protected $user;

    public function __construct(array $user)
    {
        $this->user = $user;
    }

    public function authorize($password)
    {
        if ($this->checkPassword($password)) {
            return true;
        }

        return false;
    }

    protected function checkPassword($password)
    {
        if (empty($this->user['password']) || $this->user['password'] !== $password) {
            echo 'YOU SHALL NOT PASS';
            exit;
        }

        return true;
    }
}
3. Nếu không thể làm điều này, có 1 giải pháp khác đó là bọc
<?php

namespace App;

class BadCode
{
    protected $user;

    public function __construct(array $user)
    {
        $this->user = $user;
    }

    public function authorize($password)
    {
        if ($this->checkPassword($password)) {
            return true;
        }

        return false;
    }

    protected function checkPassword($password)
    {
        if (empty($this->user['password']) || $this->user['password'] !== $password) {
            echo 'YOU SHALL NOT PASS';
            exit;
        }

        return true;
    }
}
3 trong 1 phương pháp khác và sơ khai nó

protected function checkPassword($password)
{
    if (empty($this->user['password']) || $this->user['password'] !== $password) {
        echo 'YOU SHALL NOT PASS';
        $this->callExit();
    }

    return true;
}

protected function callExit()
{
    exit;
}

Bây giờ lớp này đã có thể test được, bạn chỉ cần stub method

<?php

namespace App;

class BadCode
{
    protected $user;

    public function __construct(array $user)
    {
        $this->user = $user;
    }

    public function authorize($password)
    {
        if ($this->checkPassword($password)) {
            return true;
        }

        return false;
    }

    protected function checkPassword($password)
    {
        if (empty($this->user['password']) || $this->user['password'] !== $password) {
            echo 'YOU SHALL NOT PASS';
            exit;
        }

        return true;
    }
}
7

This is unit test of them ta

<?php

namespace Tests;

class BadCodeTest extends TestCase
{
    public function testAuthorizeExitsWhenPasswordNotSet()
    {
        $user = ['username' => 'jtreminio'];
        $password = 'foo';

        $badCode = $this->getMockBuilder(App\BadCode::class)
            ->setConstructorArgs([$user])
            ->setMethods(['callExit'])
            ->getMock();

        $badCode->expects($this->once())
            ->method('callExit');

        $this->expectOutputString('YOU SHALL NOT PASS');

        $badCode->authorize($password);
    }
}

Bằng cách truyền vào mảng

<?php

namespace App;

class BadCode
{
    protected $user;

    public function __construct(array $user)
    {
        $this->user = $user;
    }

    public function authorize($password)
    {
        if ($this->checkPassword($password)) {
            return true;
        }

        return false;
    }

    protected function checkPassword($password)
    {
        if (empty($this->user['password']) || $this->user['password'] !== $password) {
            echo 'YOU SHALL NOT PASS';
            exit;
        }

        return true;
    }
}
8 vào phương thức
$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->setMethods(['authorizeAndCapture', 'foobar'])
    ->getMock();
5, bạn đã tạo ra 1 đối tượng giả với cả sơ khai và phương pháp giả. Trong ví dụ này,
<?php

namespace App;

class BadCode
{
    protected $user;

    public function __construct(array $user)
    {
        $this->user = $user;
    }

    public function authorize($password)
    {
        if ($this->checkPassword($password)) {
            return true;
        }

        return false;
    }

    protected function checkPassword($password)
    {
        if (empty($this->user['password']) || $this->user['password'] !== $password) {
            echo 'YOU SHALL NOT PASS';
            exit;
        }

        return true;
    }
}
7 là phương thức duy nhất còn sơ khai, còn tất cả các phương thức khác đều là giả

Khi mã chạy đến đoạn

protected function checkPassword($password)
{
    if (empty($this->user['password']) || $this->user['password'] !== $password) {
        echo 'YOU SHALL NOT PASS';
        $this->callExit();
    }

    return true;
}

protected function callExit()
{
    exit;
}
1 và gọi là
<?php

namespace App;

class BadCode
{
    protected $user;

    public function __construct(array $user)
    {
        $this->user = $user;
    }

    public function authorize($password)
    {
        if ($this->checkPassword($password)) {
            return true;
        }

        return false;
    }

    protected function checkPassword($password)
    {
        if (empty($this->user['password']) || $this->user['password'] !== $password) {
            echo 'YOU SHALL NOT PASS';
            exit;
        }

        return true;
    }
}
7, bài kiểm tra đơn vị của bạn sẽ không bị dừng vì
<?php

namespace App;

class BadCode
{
    protected $user;

    public function __construct(array $user)
    {
        $this->user = $user;
    }

    public function authorize($password)
    {
        if ($this->checkPassword($password)) {
            return true;
        }

        return false;
    }

    protected function checkPassword($password)
    {
        if (empty($this->user['password']) || $this->user['password'] !== $password) {
            echo 'YOU SHALL NOT PASS';
            exit;
        }

        return true;
    }
}
7 đã bị khai báo thay vì chạy mã thực bên trong nó

Nếu bạn khai thác nhiều phương thức trong 1 đối tượng giả, bạn có thể gọi

protected function checkPassword($password)
{
    if (empty($this->user['password']) || $this->user['password'] !== $password) {
        echo 'YOU SHALL NOT PASS';
        $this->callExit();
    }

    return true;
}

protected function callExit()
{
    exit;
}
4 nhiều lần. Những lần gọi đó được xem là
protected function checkPassword($password)
{
    if (empty($this->user['password']) || $this->user['password'] !== $password) {
        echo 'YOU SHALL NOT PASS';
        $this->callExit();
    }

    return true;
}

protected function callExit()
{
    exit;
}
5 và nó không vi phạm mục tiêu chỉ 1 xác nhận trong 1 bài kiểm tra đơn vị. If you gone line

$badCode->expects($this->once())
    ->method('callExit');

mã của chúng ta vẫn sẽ vượt qua. Tuy nhiên, ý nghĩa của

protected function checkPassword($password)
{
    if (empty($this->user['password']) || $this->user['password'] !== $password) {
        echo 'YOU SHALL NOT PASS';
        $this->callExit();
    }

    return true;
}

protected function callExit()
{
    exit;
}
4 ở đây là chúng ta muốn chắc chắn rằng phương thức
<?php

namespace App;

class BadCode
{
    protected $user;

    public function __construct(array $user)
    {
        $this->user = $user;
    }

    public function authorize($password)
    {
        if ($this->checkPassword($password)) {
            return true;
        }

        return false;
    }

    protected function checkPassword($password)
    {
        if (empty($this->user['password']) || $this->user['password'] !== $password) {
            echo 'YOU SHALL NOT PASS';
            exit;
        }

        return true;
    }
}
7 sẽ được gọi 1 lần bên trong phương thức
protected function checkPassword($password)
{
    if (empty($this->user['password']) || $this->user['password'] !== $password) {
        echo 'YOU SHALL NOT PASS';
        $this->callExit();
    }

    return true;
}

protected function callExit()
{
    exit;
}
8, nếu trong mã tương ứng không được gọi là phương thức
<?php

namespace App;

class BadCode
{
    protected $user;

    public function __construct(array $user)
    {
        $this->user = $user;
    }

    public function authorize($password)
    {
        if ($this->checkPassword($password)) {
            return true;
        }

        return false;
    }

    protected function checkPassword($password)
    {
        if (empty($this->user['password']) || $this->user['password'] !== $password) {
            echo 'YOU SHALL NOT PASS';
            exit;
        }

        return true;
    }
}
7 thì bài kiểm tra đơn vị của chúng ta sẽ bị lỗi ngay lập tức và chúng ta

Nếu bạn cố gắng định nghĩa lại giá trị trả về của

protected function checkPassword($password)
{
    if (empty($this->user['password']) || $this->user['password'] !== $password) {
        echo 'YOU SHALL NOT PASS';
        $this->callExit();
    }

    return true;
}

protected function callExit()
{
    exit;
}
8 với
<?php

namespace Tests;

class BadCodeTest extends TestCase
{
    public function testAuthorizeExitsWhenPasswordNotSet()
    {
        $user = ['username' => 'jtreminio'];
        $password = 'foo';

        $badCode = $this->getMockBuilder(App\BadCode::class)
            ->setConstructorArgs([$user])
            ->setMethods(['callExit'])
            ->getMock();

        $badCode->expects($this->once())
            ->method('callExit');

        $this->expectOutputString('YOU SHALL NOT PASS');

        $badCode->authorize($password);
    }
}
1 thì PHPUnit sẽ bỏ qua nó và tiếp tục với bài kiểm tra. Hãy nhớ rằng, các phương thức giả định không cho phép ghi đè giá trị trả về

Xử lý Bad Constructor

Đôi khi bạn đọc qua các đoạn mã cũ và có những cấu trúc mà nó rất phức tạp không phải chỉ là việc khởi động tạo giá trị cho các thuộc tính

Miško Hevery đã đưa ra là một quy tắc giải thích cho công việc tại sao nhà xây dựng chỉ nên làm một công việc đơn giản là khởi tạo thuộc tính cho các đối tượng theo các tham số truyền vào

Một ví dụ về nhà xây dựng có thiết kế không tốt

<?php

namespace Tests;

class BadCodeTest extends TestCase
{
    public function testAuthorizeExitsWhenPasswordNotSet()
    {
        $user = ['username' => 'jtreminio'];
        $password = 'foo';

        $badCode = $this->getMockBuilder(App\BadCode::class)
            ->setConstructorArgs([$user])
            ->setMethods(['callExit'])
            ->getMock();

        $badCode->expects($this->once())
            ->method('callExit');

        $this->expectOutputString('YOU SHALL NOT PASS');

        $badCode->authorize($password);
    }
}
2

<?php

namespace App;

class NaughtyConstructor
{
    public $html;

    public function __construct($url)
    {
        $this->html = file_get_contents($url);
    }

    public function getMetaTags()
    {
        $mime = 'text/plain';
        $filename = "data://{$mime};base64," . base64_encode($this->html);

        return get_meta_tags($filename);
    }

    public function getTitle()
    {
        preg_match("#<title>(.+)</title>#siU", $this->html, $matches);

        return !empty($matches[1]) ? $matches[1] : false;
    }
}

Cấu trúc của lớp này bắt chước nhiều lớp khác mà bạn có thể gặp. To use it, you can call as after

$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->getMock();
0

Nếu không cuộn xuống bên dưới, bạn có thể chỉ ra vấn đề lớn nhất khi kiểm tra đoạn mã này không?

Câu trả lời. Vì bạn đã tạo ra một phụ thuộc vào

<?php

namespace Tests;

class BadCodeTest extends TestCase
{
    public function testAuthorizeExitsWhenPasswordNotSet()
    {
        $user = ['username' => 'jtreminio'];
        $password = 'foo';

        $badCode = $this->getMockBuilder(App\BadCode::class)
            ->setConstructorArgs([$user])
            ->setMethods(['callExit'])
            ->getMock();

        $badCode->expects($this->once())
            ->method('callExit');

        $this->expectOutputString('YOU SHALL NOT PASS');

        $badCode->authorize($password);
    }
}
3 trong hàm tạo, nên bạn phải trực tuyến mới kiểm tra được lớp này. Các bài kiểm tra không nên phụ thuộc vào bất kỳ thứ gì bên ngoài

Tạo một đơn vị kiểm tra đơn vị cho mã hiện tại

<?php

namespace Tests;

class BadCodeTest extends TestCase
{
    public function testAuthorizeExitsWhenPasswordNotSet()
    {
        $user = ['username' => 'jtreminio'];
        $password = 'foo';

        $badCode = $this->getMockBuilder(App\BadCode::class)
            ->setConstructorArgs([$user])
            ->setMethods(['callExit'])
            ->getMock();

        $badCode->expects($this->once())
            ->method('callExit');

        $this->expectOutputString('YOU SHALL NOT PASS');

        $badCode->authorize($password);
    }
}
4

$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->getMock();
1

Trước khi chạy test case đơn giản này, tôi bật chế độ máy bay trên laptop để ngắt kết nối Internet. And after that run test

$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->getMock();
2

Sau một khoảng thời gian chờ đợi khá dài, tôi nhận được kết quả đã được báo trước. Thất bại

phụ thuộc Internet

Có nhiều cách để làm cho lớp học này tốt hơn, nhưng hiện tại, với mục đích của bài viết này, tôi muốn giả sử rằng lớp học đặc biệt này không thể bị thay đổi, chúng tôi chỉ viết bài kiểm tra đơn vị cho nó mà không thể thay đổi

If want to change class, some way could be

  • Truyền HTML as as number of constructor (vd.
    <?php
    
    namespace Tests;
    
    class BadCodeTest extends TestCase
    {
        public function testAuthorizeExitsWhenPasswordNotSet()
        {
            $user = ['username' => 'jtreminio'];
            $password = 'foo';
    
            $badCode = $this->getMockBuilder(App\BadCode::class)
                ->setConstructorArgs([$user])
                ->setMethods(['callExit'])
                ->getMock();
    
            $badCode->expects($this->once())
                ->method('callExit');
    
            $this->expectOutputString('YOU SHALL NOT PASS');
    
            $badCode->authorize($password);
        }
    }
    
    5)
  • Di chuyển lời gọi
    <?php
    
    namespace Tests;
    
    class BadCodeTest extends TestCase
    {
        public function testAuthorizeExitsWhenPasswordNotSet()
        {
            $user = ['username' => 'jtreminio'];
            $password = 'foo';
    
            $badCode = $this->getMockBuilder(App\BadCode::class)
                ->setConstructorArgs([$user])
                ->setMethods(['callExit'])
                ->getMock();
    
            $badCode->expects($this->once())
                ->method('callExit');
    
            $this->expectOutputString('YOU SHALL NOT PASS');
    
            $badCode->authorize($password);
        }
    }
    
    3 ra bên ngoài hàm tạo và sử dụng phương thức sơ khai

Ở đây chúng ta cần có mock constructor

Replace the first line in unit test,

<?php

namespace Tests;

class BadCodeTest extends TestCase
{
    public function testAuthorizeExitsWhenPasswordNotSet()
    {
        $user = ['username' => 'jtreminio'];
        $password = 'foo';

        $badCode = $this->getMockBuilder(App\BadCode::class)
            ->setConstructorArgs([$user])
            ->setMethods(['callExit'])
            ->getMock();

        $badCode->expects($this->once())
            ->method('callExit');

        $this->expectOutputString('YOU SHALL NOT PASS');

        $badCode->authorize($password);
    }
}
7, with PHPUnit
$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->setMethods(['authorizeAndCapture', 'foobar'])
    ->getMock();
4

$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->getMock();
3

Nếu bạn còn nhớ, bất kỳ phương thức nào bạn khai báo trong

$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->setMethods(['authorizeAndCapture', 'foobar'])
    ->getMock();
5, nó sẽ trở thành sơ khai, trả về null theo mặc định

Nhưng trường hợp này thì không. tại sao?

You could not stub constructor. Một phương thức sơ khai trả về null theo mặc định. Khi bạn khởi tạo một đối tượng với

$badCode->expects($this->once())
    ->method('callExit');
0 PHP trả về một thể hiện của lớp. Vì vậy, nó sẽ không có ý nghĩa gì nếu bạn thay đổi và trả về null thay vì một đối tượng mới không đúng?

PHPUnit has a solution is

$badCode->expects($this->once())
    ->method('callExit');
1

$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->getMock();
4

Lưu ý, truyền

$badCode->expects($this->once())
    ->method('callExit');
2 vào phương thức
$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->setMethods(['authorizeAndCapture', 'foobar'])
    ->getMock();
5 nhìn có vẻ không cần thiết lắm, nhưng hãy nhớ lại nếu bạn không gọi
$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->setMethods(['authorizeAndCapture', 'foobar'])
    ->getMock();
5 hoặc truyền một mảng trống
$badCode->expects($this->once())
    ->method('callExit');
5 và
$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->setMethods(['authorizeAndCapture', 'foobar'])
    ->getMock();
5 thì tất cả các phương thức trong đối tượng sẽ trở thành sơ khai và trả về null. Điều đó không phải là điều chúng ta mong muốn

Run back PHPUnit and. vẫn thất bại

Dĩ nhiên là nó bị lỗi bởi

$badCode->expects($this->once())
    ->method('callExit');
7 đang trống do chúng ta đã vô hiệu hóa hàm tạo

Điều này dẫn đến một điểm thú vị khác. Có chuyện gì xảy ra nên chúng tôi cố gắng thử nghiệm 1 trang web mà chúng tôi không có quyền điều khiển? . Đây là một điểm nữa trong công việc tránh có các bên phụ thuộc bên ngoài trong các bài kiểm tra đơn vị

Giải pháp ở đây đơn giản là sử dụng một đoạn HTML mẫu trong thử nghiệm

Bây giờ, bài kiểm tra của bạn như sau

$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->getMock();
5

Chúng ta đã hoàn thành một mục tiêu quan trọng trong bài kiểm tra đơn vị. loại bỏ các phụ thuộc bên ngoài

Run back phpunit and pass

Chúng ta còn có thể thêm một bài kiểm tra khác

$authorizeNet = $this->getMockBuilder(AuthorizeNetAIM::class)
    ->getMock();
6

thanh màu xanh lá cây

Tổng kết

hôm nay bạn đã học về mảnh ghép cuối cùng trong vấn đề mock and stub. phương pháp giả

Các định nghĩa khó hiểu về đối tượng giả, phương pháp sơ khai và phương pháp giả có thể làm cho bạn chán chí lúc đầu, nhưng tôi tự tin rằng khi bạn tìm thấy sự khác biệt giữa ba khái niệm này, và khi bạn cần các phương pháp giả thay thế