Kiểm tra móc gọi lại


34

Tôi đang phát triển một plugin sử dụng TDD và một điều mà tôi hoàn toàn không kiểm tra được là ... hook.

Ý tôi là OK, tôi có thể kiểm tra hook hook hook, nhưng làm thế nào tôi có thể kiểm tra nếu hook thực sự kích hoạt (cả hook tùy chỉnh và hook mặc định của WordPress)? Tôi cho rằng một số chế giễu sẽ giúp ích, nhưng tôi đơn giản là không thể tìm ra những gì tôi đang thiếu.

Tôi đã cài đặt bộ thử nghiệm với WP-CLI. Theo câu trả lời này , inithook sẽ kích hoạt, nhưng ... nó không; Ngoài ra, mã hoạt động trong WordPress.

Theo hiểu biết của tôi, bootstrap được tải sau cùng, vì vậy sẽ rất hợp lý khi không kích hoạt init, vì vậy câu hỏi còn lại là: làm thế nào tôi nên kiểm tra xem hook được kích hoạt như thế nào?

Cảm ơn!

Tệp bootstrap trông như thế này:

$_tests_dir = getenv('WP_TESTS_DIR');
if ( !$_tests_dir ) $_tests_dir = '/tmp/wordpress-tests-lib';

require_once $_tests_dir . '/includes/functions.php';

function _manually_load_plugin() {
  require dirname( __FILE__ ) . '/../includes/RegisterCustomPostType.php';
}
tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' );

require $_tests_dir . '/includes/bootstrap.php';

tập tin thử nghiệm trông như thế này:

class RegisterCustomPostType {
  function __construct()
  {
    add_action( 'init', array( $this, 'register_post_type' ) );
  }

  public function register_post_type()
  {
    register_post_type( 'foo' );
  }
}

Và bản thân bài kiểm tra:

class CustomPostTypes extends WP_UnitTestCase {
  function test_custom_post_type_creation()
  {
    $this->assertTrue( post_type_exists( 'foo' ) );
  }
}

Cảm ơn!


Nếu bạn đang chạy phpunit, bạn có thể thấy các bài kiểm tra thất bại hoặc vượt qua? Bạn đã cài đặt bin/install-wp-tests.shchưa?
Sven

Tôi nghĩ rằng một phần của vấn đề là có thể RegisterCustomPostType::__construct()không bao giờ được gọi khi plugin được tải cho các bài kiểm tra. Cũng có thể bạn đang bị ảnh hưởng bởi lỗi # 29827 ; có thể thử cập nhật phiên bản bộ kiểm tra đơn vị của WP.
JD

@Sven: có, các bài kiểm tra thất bại; i cài đặt bin/install-wp-tests.sh(kể từ khi tôi sử dụng wp-cli) @JD: RegisterCustomPostType :: __ construct được gọi là (chỉ cần thêm một die()tuyên bố và phpunit dừng lại ở đó)
Ionut Staicu

Tôi không quá chắc chắn về phía kiểm tra đơn vị (không phải sở trường của tôi), nhưng theo quan điểm nghĩa đen, bạn có thể sử dụng did_action()để kiểm tra xem các hành động đã bị bắn hay chưa.
Hết

@Rarst: cảm ơn vì lời đề nghị, nhưng nó vẫn không hoạt động. Vì một số lý do, tôi nghĩ rằng thời gian là sai (các bài kiểm tra được chạy trước khi inithook).
Irika Stomsu

Câu trả lời:


72

Kiểm tra trong sự cô lập

Khi phát triển một plugin, cách tốt nhất để kiểm tra nó là không tải môi trường WordPress.

Nếu bạn viết mã có thể dễ dàng kiểm tra mà không cần WordPress, mã của bạn sẽ trở nên tốt hơn .

Mọi thành phần được kiểm tra đơn vị, nên được kiểm tra riêng rẽ : khi bạn kiểm tra một lớp, bạn chỉ phải kiểm tra lớp cụ thể đó, giả sử tất cả các mã khác đang hoạt động hoàn hảo.

Bộ cách ly

Đây là lý do tại sao các bài kiểm tra đơn vị được gọi là "đơn vị".

Là một lợi ích bổ sung, không cần tải lõi, bài kiểm tra của bạn sẽ chạy nhanh hơn nhiều.

Tránh móc trong constructor

Một mẹo tôi có thể cung cấp cho bạn là tránh đặt móc trong các hàm tạo. Đó là một trong những điều sẽ làm cho mã của bạn có thể kiểm tra được một cách cô lập.

Hãy xem mã kiểm tra trong OP:

class CustomPostTypes extends WP_UnitTestCase {
  function test_custom_post_type_creation() {
    $this->assertTrue( post_type_exists( 'foo' ) );
  }
}

Và hãy giả sử thử nghiệm này thất bại . Là Ai là thủ phạm ?

  • cái móc không được thêm vào hay không đúng cách?
  • phương pháp đăng ký loại bài đăng hoàn toàn không được gọi hoặc với các đối số sai?
  • Có lỗi gì trong WordPress?

Làm thế nào nó có thể được cải thiện?

Giả sử mã lớp của bạn là:

class RegisterCustomPostType {

  function init() {
    add_action( 'init', array( $this, 'register_post_type' ) );
  }

  public function register_post_type() {
    register_post_type( 'foo' );
  }
}

(Lưu ý: Tôi sẽ đề cập đến phiên bản này của lớp cho phần còn lại của câu trả lời)

Cách tôi viết lớp này cho phép bạn tạo các thể hiện của lớp mà không cần gọi add_action.

Trong lớp trên có 2 điều cần kiểm tra:

  • phương thức init thực sự gọi add_actiontruyền cho nó các đối số thích hợp
  • phương thức register_post_type thực sự gọi register_post_typehàm

Tôi không nói rằng bạn phải kiểm tra xem loại bài đăng có tồn tại hay không: nếu bạn thêm hành động phù hợp và nếu bạn gọi register_post_type, loại bài đăng tùy chỉnh phải tồn tại: nếu nó không tồn tại thì đó là vấn đề của WordPress.

Hãy nhớ rằng: khi bạn kiểm tra plugin của mình, bạn phải kiểm tra mã của mình chứ không phải mã WordPress. Trong các thử nghiệm của bạn, bạn phải cho rằng WordPress (giống như bất kỳ thư viện bên ngoài nào khác mà bạn sử dụng) hoạt động tốt. Đó là ý nghĩa của bài kiểm tra đơn vị .

Nhưng ... trong thực tế?

Nếu WordPress không được tải, nếu bạn cố gắng gọi các phương thức lớp ở trên, bạn sẽ gặp một lỗi nghiêm trọng, vì vậy bạn cần phải giả định các chức năng.

Phương pháp "thủ công"

Chắc chắn bạn có thể viết thư viện giả của mình hoặc "thủ công" giả mọi phương thức. Điều đó là có thể. Tôi sẽ cho bạn biết làm thế nào để làm điều đó, nhưng sau đó tôi sẽ chỉ cho bạn một phương pháp dễ dàng hơn.

Nếu WordPress không được tải trong khi các bài kiểm tra đang chạy, điều đó có nghĩa là bạn có thể xác định lại các chức năng của nó, ví dụ add_actionhoặc register_post_type.

Giả sử bạn có một tệp, được tải từ tệp bootstrap của bạn, nơi bạn có:

function add_action() {
  global $counter;
  if ( ! isset($counter['add_action']) ) {
    $counter['add_action'] = array();
  }
  $counter['add_action'][] = func_get_args();
}

function register_post_type() {
  global $counter;
  if ( ! isset($counter['register_post_type']) ) {
    $counter['register_post_type'] = array();
  }
  $counter['register_post_type'][] = func_get_args();
}

Tôi đã viết lại các hàm để chỉ cần thêm một phần tử vào một mảng toàn cục mỗi khi chúng được gọi.

Bây giờ bạn nên tạo (nếu bạn chưa có) lớp mở rộng trường hợp kiểm tra cơ sở của riêng bạn PHPUnit_Framework_TestCase: cho phép bạn dễ dàng định cấu hình các bài kiểm tra của mình.

Nó có thể là một cái gì đó như:

class Custom_TestCase extends \PHPUnit_Framework_TestCase {

    public function setUp() {
        $GLOBALS['counter'] = array();
    }

}

Theo cách này, trước mỗi bài kiểm tra, bộ đếm toàn cầu được đặt lại.

Và bây giờ mã kiểm tra của bạn (tôi tham khảo lớp viết lại mà tôi đã đăng ở trên):

class CustomPostTypes extends Custom_TestCase {

  function test_init() {
     global $counter;
     $r = new RegisterCustomPostType;
     $r->init();
     $this->assertSame(
       $counter['add_action'][0],
       array( 'init', array( $r, 'register_post_type' ) )
     );
  }

  function test_register_post_type() {
     global $counter;
     $r = new RegisterCustomPostType;
     $r->register_post_type();
     $this->assertSame( $counter['register_post_type'][0], array( 'foo' ) );
  }

}

Bạn cần lưu ý:

  • Tôi đã có thể gọi hai phương thức riêng biệt và WordPress hoàn toàn không được tải. Bằng cách này nếu một thử nghiệm thất bại, tôi biết chính xác thủ phạm là ai.
  • Như tôi đã nói, ở đây tôi kiểm tra rằng các lớp gọi các hàm WP với các đối số dự kiến. Không cần phải kiểm tra nếu CPT thực sự tồn tại. Nếu bạn đang kiểm tra sự tồn tại của CPT, thì bạn đang kiểm tra hành vi WordPress chứ không phải hành vi plugin của bạn ...

Hay đấy .. nhưng đó là Pita!

Có, nếu bạn phải tự chế nhạo tất cả các chức năng của WordPress, điều đó thực sự gây khó khăn. Một số lời khuyên chung tôi có thể đưa ra là sử dụng càng ít chức năng WP càng tốt: bạn không phải viết lại WordPress, nhưng các chức năng WP trừu tượng bạn sử dụng trong các lớp tùy chỉnh, để chúng có thể bị chế giễu và dễ dàng kiểm tra.

Ví dụ, liên quan đến ví dụ ở trên, bạn có thể viết một lớp đăng ký các loại bài đăng, gọi register_post_type'init' với các đối số đã cho. Với sự trừu tượng này, bạn vẫn cần kiểm tra lớp đó, nhưng ở những nơi khác trong mã của bạn đăng ký loại bài đăng, bạn có thể sử dụng lớp đó, chế nhạo nó trong các bài kiểm tra (vì vậy giả sử nó hoạt động).

Điều tuyệt vời là, nếu bạn viết một lớp tóm tắt đăng ký CPT, bạn có thể tạo một kho lưu trữ riêng cho nó và nhờ các công cụ hiện đại như Composer nhúng nó vào tất cả các dự án mà bạn cần: kiểm tra một lần, sử dụng ở mọi nơi . Và nếu bạn từng tìm thấy một lỗi trong đó, bạn có thể sửa nó ở một nơi và đơn giản là composer updatetất cả các dự án nơi nó được sử dụng cũng được sửa.

Lần thứ hai: viết mã có thể kiểm tra được trong sự cô lập có nghĩa là viết mã tốt hơn.

Nhưng sớm hay muộn tôi cũng cần sử dụng các chức năng WP ở đâu đó ...

Tất nhiên. Bạn không bao giờ nên hành động song song với cốt lõi, nó không có ý nghĩa. Bạn có thể viết các lớp bao bọc các hàm WP, nhưng các lớp đó cũng cần phải được kiểm tra. Phương thức "thủ công" được mô tả ở trên có thể được sử dụng cho các tác vụ rất đơn giản, nhưng khi một lớp chứa nhiều hàm WP thì đó có thể là một nỗi đau.

May mắn thay, ở đó có những người tốt viết những điều tốt. 10up , một trong những cơ quan WP lớn nhất, duy trì một thư viện rất tuyệt vời cho những người muốn thử nghiệm plugin đúng cách. Đó là WP_Mock.

Nó cho phép bạn giả định các chức năng WP một hook . Giả sử bạn đã tải trong các bài kiểm tra của mình (xem repo readme), bài kiểm tra tương tự tôi đã viết ở trên trở thành:

class CustomPostTypes extends Custom_TestCase {

  function test_init() {
     $r = new RegisterCustomPostType;
     // tests that the action was added with given arguments
     \WP_Mock::expectActionAdded( 'init', array( $r, 'register_post_type' ) );
     $r->init();
  }

  function test_register_post_type() {
     // tests that the function was called with given arguments and run once
     \WP_Mock::wpFunction( 'register_post_type', array(
        'times' => 1,
        'args' => array( 'foo' ),
     ) );
     $r = new RegisterCustomPostType;
     $r->register_post_type();
  }

}

Đơn giản phải không? Câu trả lời này không phải là hướng dẫn WP_Mock, vì vậy hãy đọc repo readme để biết thêm thông tin, nhưng ví dụ trên nên khá rõ ràng, tôi nghĩ vậy.

Hơn nữa, bạn không cần phải viết bất kỳ bản chế add_actionhoặc register_post_typetự chế giễu nào , hoặc duy trì bất kỳ biến toàn cục nào.

Còn lớp WP?

WP cũng có một số lớp và nếu WordPress không được tải khi bạn chạy thử nghiệm, bạn cần phải chế nhạo chúng.

Điều đó dễ dàng hơn nhiều so với các chức năng giả, PHPUnit có một hệ thống nhúng để giả lập các đối tượng, nhưng ở đây tôi muốn đề xuất Mockery cho bạn. Đây là một thư viện rất mạnh mẽ và rất dễ sử dụng. Hơn nữa, đó là một sự phụ thuộc của WP_Mock, vì vậy nếu bạn có nó, bạn cũng có Mockery.

Nhưng còn cái gì WP_UnitTestCase?

Bộ kiểm tra WordPress đã được tạo để kiểm tra lõi WordPress và nếu bạn muốn đóng góp vào lõi thì đó là mấu chốt, nhưng sử dụng nó cho các plugin chỉ khiến bạn kiểm tra không bị cô lập.

Hãy để mắt đến thế giới WP: có rất nhiều khung công tác PHP và CMS hiện đại và không ai trong số họ đề xuất thử nghiệm plugin / mô-đun / tiện ích mở rộng (hoặc bất cứ thứ gì chúng được gọi) bằng cách sử dụng mã khung.

Nếu bạn bỏ lỡ các nhà máy, một tính năng hữu ích của bộ phần mềm, bạn phải biết rằng có những điều tuyệt vời ở đó.

Gotchas và nhược điểm

Có một trường hợp khi quy trình làm việc tôi đề nghị ở đây thiếu: kiểm tra cơ sở dữ liệu tùy chỉnh .

Trong thực tế, nếu bạn sử dụng các bảng và hàm WordPress tiêu chuẩn để viết ở đó (ở các $wpdbphương thức cấp thấp nhất ), bạn không bao giờ cần phải thực sự ghi dữ liệu hoặc kiểm tra nếu dữ liệu thực sự có trong cơ sở dữ liệu, chỉ cần chắc chắn rằng các phương thức thích hợp được gọi với các đối số phù hợp.

Tuy nhiên, bạn có thể viết các plugin với các bảng và hàm tùy chỉnh xây dựng các truy vấn để viết ở đó và kiểm tra xem các truy vấn đó có hoạt động không, đó là trách nhiệm của bạn.

Trong những trường hợp đó, bộ kiểm tra WordPress có thể giúp bạn rất nhiều và tải WordPress có thể cần thiết trong một số trường hợp để chạy các chức năng như dbDelta.

(Không cần phải nói sử dụng một db khác cho các thử nghiệm, phải không?)

May mắn thay, PHPUnit cho phép bạn tổ chức các bài kiểm tra của mình trong các "bộ" có thể chạy riêng, vì vậy bạn có thể viết một bộ cho các bài kiểm tra cơ sở dữ liệu tùy chỉnh nơi bạn tải môi trường WordPress (hoặc một phần của nó) để lại tất cả các bài kiểm tra còn lại của bạn không có WordPress .

Chỉ chắc chắn viết các lớp trừu tượng càng nhiều hoạt động cơ sở dữ liệu càng tốt, theo cách mà tất cả các lớp plugin khác sử dụng chúng, để sử dụng giả, bạn có thể kiểm tra chính xác phần lớn các lớp mà không phải xử lý cơ sở dữ liệu.

Lần thứ ba, viết mã dễ dàng kiểm tra trong sự cô lập có nghĩa là viết mã tốt hơn.


5
Holy crap, rất nhiều thông tin hữu ích! Cảm ơn bạn! Bằng cách nào đó tôi đã xoay sở để bỏ lỡ toàn bộ điểm kiểm thử đơn vị (cho đến bây giờ, tôi chỉ thực hành kiểm tra PHP bên trong Code Dojo). Tôi cũng đã tìm thấy về wp_mock sớm hôm nay, nhưng vì một số lý do tôi đã cố gắng bỏ qua nó. Điều khiến tôi bực mình là bất kỳ thử nghiệm nào, dù nhỏ đến đâu, nó thường mất ít nhất hai giây để chạy (tải WP env trước, thực hiện thử nghiệm thứ hai). Cảm ơn bạn một lần nữa vì đã mở mắt của tôi!
Irika Stomsu

4
Cảm ơn @I MuffStomsu Tôi đã quên đề cập rằng việc không tải WordPress giúp các bài kiểm tra của bạn nhanh hơn rất nhiều
gmazzap

6
Cũng đáng chỉ ra rằng khung kiểm tra đơn vị WP Core là một công cụ tuyệt vời để chạy thử nghiệm INTEGRATION, sẽ là các thử nghiệm tự động để đảm bảo rằng nó tích hợp tốt với chính WP (ví dụ: không có xung đột tên hàm ngẫu nhiên, v.v.).
John P Bloch

1
@JohnPBloch +1 cho điểm tốt. Ngay cả khi sử dụng một không gian tên là đủ để tránh bất kỳ xung đột tên hàm nào trong WordPress, trong đó mọi thứ là toàn cầu :) Nhưng, chắc chắn, tích hợp / kiểm tra chức năng là một điều. Hiện tại tôi đang chơi với Behat + Mink nhưng tôi vẫn đang luyện tập với nó.
gmazzap

1
Cảm ơn bạn đã "đi trực thăng" qua khu rừng UnitTest của WordPress - Tôi vẫn đang cười vì bức tranh hoành
tráng đó
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.