Thực tiễn tốt nhất để kiểm tra các phương thức được bảo vệ bằng PHPUnit


287

Tôi tìm thấy các cuộc thảo luận về Bạn có kiểm tra thông tin phương pháp riêng tư .

Tôi đã quyết định, trong một số lớp, tôi muốn có các phương thức được bảo vệ, nhưng kiểm tra chúng. Một số phương pháp này là tĩnh và ngắn. Bởi vì hầu hết các phương pháp công khai sử dụng chúng, tôi có thể sẽ có thể loại bỏ các bài kiểm tra một cách an toàn sau này. Nhưng để bắt đầu với cách tiếp cận TDD và tránh gỡ lỗi, tôi thực sự muốn thử nghiệm chúng.

Tôi nghĩ về những điều sau đây:

  • Phương pháp Đối tượng như được khuyên trong một câu trả lời dường như là quá mức cần thiết cho việc này.
  • Bắt đầu với các phương thức công khai và khi phạm vi bảo hiểm được đưa ra bởi các thử nghiệm cấp cao hơn, hãy chuyển chúng được bảo vệ và loại bỏ các thử nghiệm.
  • Kế thừa một lớp với giao diện có thể kiểm tra làm cho các phương thức được bảo vệ trở nên công khai

Đó là thực hành tốt nhất? Có gì khác?

Dường như, JUnit tự động thay đổi các phương thức được bảo vệ thành công khai, nhưng tôi không có cái nhìn sâu hơn về nó. PHP không cho phép điều này thông qua sự phản ánh .


Hai câu hỏi: 1. tại sao bạn phải bận tâm kiểm tra chức năng mà lớp của bạn không phơi bày? 2. Nếu bạn nên kiểm tra nó, tại sao nó là riêng tư?
nad2000

2
Có thể anh ta muốn kiểm tra xem một tài sản riêng có được đặt chính xác hay không và cách kiểm tra duy nhất chỉ sử dụng chức năng setter là làm cho tài sản riêng công khai và kiểm tra dữ liệu
AntonioCS

4
Và đây là kiểu thảo luận và do đó không mang tính xây dựng. Một lần nữa :)
mlvljr

72
Bạn có thể gọi nó là trái với các quy tắc của trang web, nhưng chỉ gọi nó là "không mang tính xây dựng" là ... thật xúc phạm.
Andy V

1
@Visser, Nó tự xúc phạm mình;)
Pacerier

Câu trả lời:


417

Nếu bạn đang sử dụng PHP5 (> = 5.3.2) với PHPUnit, bạn có thể kiểm tra các phương thức riêng tư và được bảo vệ của mình bằng cách sử dụng sự phản chiếu để đặt chúng ở chế độ công khai trước khi chạy thử nghiệm của bạn:

protected static function getMethod($name) {
  $class = new ReflectionClass('MyClass');
  $method = $class->getMethod($name);
  $method->setAccessible(true);
  return $method;
}

public function testFoo() {
  $foo = self::getMethod('foo');
  $obj = new MyClass();
  $foo->invokeArgs($obj, array(...));
  ...
}

27
Để trích dẫn liên kết đến blog của người sebastians: "Vì vậy: Chỉ vì việc kiểm tra các thuộc tính và phương thức được bảo vệ và riêng tư không có nghĩa là đây là" điều tốt "." - Chỉ cần ghi nhớ điều đó
edorian

10
Tôi sẽ thi mà. Nếu bạn không cần các phương pháp được bảo vệ hoặc riêng tư của mình để hoạt động, đừng thử chúng.
uckelman

10
Chỉ cần làm rõ, bạn không cần phải sử dụng PHPUnit để làm việc này. Nó cũng sẽ hoạt động với SimpleTest hoặc bất cứ điều gì. Không có gì về câu trả lời phụ thuộc vào PHPUnit.
Ian Dunn

84
Bạn không nên kiểm tra trực tiếp các thành viên được bảo vệ / tư nhân. Chúng thuộc về việc thực hiện nội bộ của lớp, và không nên kết hợp với thử nghiệm. Điều này làm cho việc tái cấu trúc là không thể và cuối cùng bạn không kiểm tra những gì cần kiểm tra. Bạn cần kiểm tra chúng một cách gián tiếp bằng các phương pháp công khai. Nếu bạn thấy điều này khó khăn, gần như chắc chắn rằng có một vấn đề với thành phần của lớp và bạn cần tách nó thành các lớp nhỏ hơn. Hãy nhớ rằng lớp học của bạn phải là một hộp đen cho bài kiểm tra của bạn - bạn ném vào một cái gì đó và bạn nhận lại một cái gì đó, và đó là tất cả!
gphilip

24
@gphilip Đối với tôi, protectedphương thức cũng là một phần của api công khai bởi vì bất kỳ lớp bên thứ ba nào cũng có thể mở rộng nó và sử dụng nó mà không cần bất kỳ phép thuật nào. Vì vậy, tôi nghĩ rằng chỉ có privatecác phương pháp rơi vào loại phương pháp không được kiểm tra trực tiếp. protectedpublicnên được kiểm tra trực tiếp.
Filip Halaxa

48

Bạn dường như đã nhận thức được, nhưng dù sao tôi cũng sẽ nói lại; Đó là một dấu hiệu xấu, nếu bạn cần thử nghiệm các phương pháp được bảo vệ. Mục đích của một bài kiểm tra đơn vị, là để kiểm tra giao diện của một lớp và các phương thức được bảo vệ là các chi tiết triển khai. Điều đó nói rằng, có những trường hợp nó có ý nghĩa. Nếu bạn sử dụng tính kế thừa, bạn có thể thấy một siêu lớp khi cung cấp giao diện cho lớp con. Vì vậy, ở đây, bạn sẽ phải kiểm tra phương thức được bảo vệ (Nhưng không bao giờ là phương thức riêng tư ). Giải pháp cho vấn đề này là tạo ra một lớp con cho mục đích thử nghiệm và sử dụng phương thức này để trưng ra các phương thức. Ví dụ.:

class Foo {
  protected function stuff() {
    // secret stuff, you want to test
  }
}

class SubFoo extends Foo {
  public function exposedStuff() {
    return $this->stuff();
  }
}

Lưu ý rằng bạn luôn có thể thay thế sự kế thừa bằng thành phần. Khi kiểm tra mã, việc xử lý mã sử dụng mẫu này thường dễ dàng hơn rất nhiều, vì vậy bạn có thể muốn xem xét tùy chọn đó.


2
Bạn chỉ có thể trực tiếp triển khai công cụ () dưới dạng công khai và trả về cha mẹ :: Stuff (). Xem phản ứng của tôi. Có vẻ như tôi đang đọc mọi thứ quá nhanh ngày hôm nay.
Michael Johnson

Bạn đúng; Nó có giá trị để thay đổi một phương thức được bảo vệ thành một phương thức công khai.
troelskn

Vì vậy, mã gợi ý tùy chọn thứ ba của tôi và "Lưu ý rằng bạn luôn có thể thay thế kế thừa bằng thành phần." đi theo hướng tùy chọn đầu tiên của tôi hoặc refactoring.com/catalog/replaceInherributionWithDelegation.html
GrGr

34
Tôi không đồng ý rằng đó là một dấu hiệu xấu. Hãy tạo sự khác biệt giữa TDD và Kiểm tra đơn vị. Kiểm thử đơn vị nên kiểm tra các phương thức riêng tư, vì đây là các đơn vị và sẽ có lợi giống như cách kiểm tra đơn vị được hưởng lợi từ kiểm thử đơn vị.
koen

36
Các phương thức được bảo vệ một phần của giao diện của một lớp, chúng không chỉ đơn giản là chi tiết triển khai. Toàn bộ quan điểm của các thành viên được bảo vệ là để các lớp con (người dùng theo quyền riêng của họ) có thể sử dụng các phương thức được bảo vệ đó bên trong các biểu thức của lớp. Những người rõ ràng cần phải được kiểm tra.
BT

40

teastburn có cách tiếp cận đúng. Thậm chí đơn giản hơn là gọi phương thức trực tiếp và trả lời câu trả lời:

class PHPUnitUtil
{
  public static function callMethod($obj, $name, array $args) {
        $class = new \ReflectionClass($obj);
        $method = $class->getMethod($name);
        $method->setAccessible(true);
        return $method->invokeArgs($obj, $args);
    }
}

Bạn có thể gọi điều này đơn giản trong các thử nghiệm của bạn bằng cách:

$returnVal = PHPUnitUtil::callMethod(
                $this->object,
                '_nameOfProtectedMethod', 
                array($arg1, $arg2)
             );

1
Đây là một ví dụ tuyệt vời, cảm ơn. Phương pháp nên được công khai thay vì được bảo vệ, không nên?
valk

Điểm tốt. Tôi thực sự sử dụng phương thức này trong lớp cơ sở mà tôi mở rộng các lớp kiểm tra của mình, trong trường hợp này có ý nghĩa. Tên của lớp sẽ sai ở đây mặc dù.
robert.egginton

Tôi đã cùng một mảnh chính xác mã dựa trên teastburn xD
Nebulosar

23

Tôi muốn đề xuất một biến thể nhỏ cho getMethod () được xác định trong câu trả lời của uckelman .

Phiên bản này thay đổi getMethod () bằng cách xóa các giá trị được mã hóa cứng và đơn giản hóa việc sử dụng một chút. Tôi khuyên bạn nên thêm nó vào lớp PHPUnitUtil của bạn như trong ví dụ bên dưới hoặc vào lớp mở rộng PHPUnit_Framework_TestCase của bạn (hoặc, tôi cho rằng, trên toàn cầu vào tệp PHPUnitUtil của bạn).

Vì MyClass đang được khởi tạo bằng mọi cách và ReflectionClass có thể lấy một chuỗi hoặc một đối tượng ...

class PHPUnitUtil {
    /**
     * Get a private or protected method for testing/documentation purposes.
     * How to use for MyClass->foo():
     *      $cls = new MyClass();
     *      $foo = PHPUnitUtil::getPrivateMethod($cls, 'foo');
     *      $foo->invoke($cls, $...);
     * @param object $obj The instantiated instance of your class
     * @param string $name The name of your private/protected method
     * @return ReflectionMethod The method you asked for
     */
    public static function getPrivateMethod($obj, $name) {
      $class = new ReflectionClass($obj);
      $method = $class->getMethod($name);
      $method->setAccessible(true);
      return $method;
    }
    // ... some other functions
}

Tôi cũng đã tạo một hàm bí danh getProtectedMethod () để rõ ràng những gì được mong đợi, nhưng đó là tùy thuộc vào bạn.

Chúc mừng!


+1 để sử dụng API Lớp phản chiếu.
Bill Ortell

10

Tôi nghĩ troelskn là gần. Tôi sẽ làm điều này thay vào đó:

class ClassToTest
{
   protected function testThisMethod()
   {
     // Implement stuff here
   }
}

Sau đó, thực hiện một cái gì đó như thế này:

class TestClassToTest extends ClassToTest
{
  public function testThisMethod()
  {
    return parent::testThisMethod();
  }
}

Sau đó, bạn chạy thử nghiệm của mình chống lại TestClassToTest.

Có thể tự động tạo các lớp mở rộng như vậy bằng cách phân tích mã. Tôi sẽ không ngạc nhiên nếu PHPUnit đã cung cấp một cơ chế như vậy (mặc dù tôi chưa kiểm tra).


Heh ... có vẻ như tôi đang nói, hãy sử dụng tùy chọn thứ ba của bạn :)
Michael Johnson

2
Vâng, đó chính xác là lựa chọn thứ ba của tôi. Tôi khá chắc chắn rằng PHPUnit không cung cấp một cơ chế như vậy.
GrGr

Điều này sẽ không hoạt động, bạn không thể ghi đè chức năng được bảo vệ bằng chức năng công khai có cùng tên.
Koen.

Tôi có thể sai, nhưng tôi không nghĩ cách tiếp cận này có thể hiệu quả. PHPUnit (theo như tôi đã từng sử dụng) yêu cầu lớp thử nghiệm của bạn mở rộng một lớp khác cung cấp chức năng thử nghiệm thực tế. Trừ khi có một cách xung quanh mà tôi không chắc tôi có thể thấy câu trả lời này có thể được sử dụng như thế nào. phpunit.de/manual/c Hiện / en / từ
Cypher

FYI này onl hoạt động cho các phương thức được bảo vệ , không phải cho tư nhân
Sliq

5

Tôi sẽ ném mũ vào vòng ở đây:

Tôi đã sử dụng hack __call với nhiều mức độ thành công khác nhau. Cách khác tôi nghĩ ra là sử dụng mẫu Khách truy cập:

1: tạo lớp stdClass hoặc lớp tùy chỉnh (để thực thi kiểu)

2: nguyên tố với phương thức và đối số cần thiết

3: đảm bảo rằng SUT của bạn có phương thức acceptVisitor sẽ thực thi phương thức với các đối số được chỉ định trong lớp truy cập

4: tiêm nó vào lớp bạn muốn kiểm tra

5: SUT tiêm kết quả hoạt động vào khách truy cập

6: áp dụng điều kiện kiểm tra của bạn cho thuộc tính kết quả của Khách truy cập


1
+1 cho một giải pháp thú vị
2014

5

Bạn thực sự có thể sử dụng __call () theo cách chung để truy cập các phương thức được bảo vệ. Để có thể kiểm tra lớp này

class Example {
    protected function getMessage() {
        return 'hello';
    }
}

bạn tạo một lớp con trong exampleTest.php:

class ExampleExposed extends Example {
    public function __call($method, array $args = array()) {
        if (!method_exists($this, $method))
            throw new BadMethodCallException("method '$method' does not exist");
        return call_user_func_array(array($this, $method), $args);
    }
}

Lưu ý rằng phương thức __call () không tham chiếu lớp theo bất kỳ cách nào để bạn có thể sao chép phần trên cho mỗi lớp bằng các phương thức được bảo vệ mà bạn muốn kiểm tra và chỉ cần thay đổi khai báo lớp. Bạn có thể đặt chức năng này trong một lớp cơ sở chung, nhưng tôi chưa thử.

Bây giờ, trường hợp thử nghiệm chỉ khác ở chỗ bạn xây dựng đối tượng cần kiểm tra, hoán đổi trong Ví dụExposed cho ví dụ.

class ExampleTest extends PHPUnit_Framework_TestCase {
    function testGetMessage() {
        $fixture = new ExampleExposed();
        self::assertEquals('hello', $fixture->getMessage());
    }
}

Tôi tin rằng PHP 5.3 cho phép bạn sử dụng sự phản chiếu để thay đổi khả năng truy cập trực tiếp của các phương thức, nhưng tôi cho rằng bạn phải làm như vậy cho từng phương thức riêng lẻ.


1
Việc triển khai __call () hoạt động rất tốt! Tôi đã cố gắng bỏ phiếu, nhưng tôi đã bỏ phiếu cho đến khi tôi thử phương pháp này và bây giờ tôi không được phép bỏ phiếu do giới hạn thời gian trong SO.
Adam Franco

Các call_user_method_array()chức năng bị phản đối như của PHP 4.1.0 ... sử dụng call_user_func_array(array($this, $method), $args)để thay thế. Lưu ý rằng nếu bạn đang sử dụng PHP 5.3.2+, bạn có thể sử dụng Reflection để có quyền truy cập vào các phương thức và thuộc tính được bảo vệ / riêng tư
nuqqsa

@nuqqsa - Cảm ơn, tôi đã cập nhật câu trả lời của mình. Kể từ đó, tôi đã viết một Accessiblegói chung sử dụng sự phản chiếu để cho phép các bài kiểm tra truy cập các thuộc tính và phương thức riêng tư / được bảo vệ của các lớp và các đối tượng.
David Harkness

Mã này không hoạt động với tôi trên PHP 5.2.7 - phương thức __call không được gọi cho các phương thức mà lớp cơ sở định nghĩa. Tôi không thể tìm thấy tài liệu này, nhưng tôi đoán hành vi này đã được thay đổi trong PHP 5.3 (nơi tôi đã xác nhận nó hoạt động).
Russell Davis

@Russell - __call()chỉ được gọi nếu người gọi không có quyền truy cập vào phương thức. Vì lớp và các lớp con của nó có quyền truy cập vào các phương thức được bảo vệ, các cuộc gọi đến chúng sẽ không được thực hiện __call(). Bạn có thể gửi mã của bạn không hoạt động trong 5.2.7 trong một câu hỏi mới không? Tôi đã sử dụng ở trên trong 5.2 và chỉ chuyển sang sử dụng sự phản chiếu với 5.3.2.
David Harkness

2

Tôi đề nghị cách giải quyết sau cho cách giải quyết / ý tưởng của "Henrik Paul" :)

Bạn biết tên của các phương thức riêng tư của lớp bạn. Ví dụ: chúng giống như _add (), _edit (), _delete (), v.v.

Do đó, khi bạn muốn kiểm tra nó từ khía cạnh của kiểm thử đơn vị, chỉ cần gọi các phương thức riêng tư bằng cách thêm tiền tố và / hoặc thêm một số từ phổ biến (ví dụ _addPhastait) để khi phương thức __call () được gọi (vì phương thức _addPhastait () không tồn tại) của lớp chủ sở hữu, bạn chỉ cần đặt mã cần thiết vào phương thức __call () để loại bỏ từ / s có hậu tố (Phastait) và sau đó gọi phương thức riêng được suy ra từ đó. Đây là một cách sử dụng tốt các phương pháp ma thuật.

Hãy thử nó.

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.