Menguji kait panggilan balik

34

Saya sedang mengembangkan plugin menggunakan TDD dan satu hal yang saya benar-benar gagal untuk menguji adalah ... kait.

Maksudku OK, saya bisa menguji hook hookback, tetapi bagaimana saya bisa menguji apakah hook benar-benar memicu (baik hook kustom dan WordPress default hooks)? Saya berasumsi bahwa beberapa ejekan akan membantu, tetapi saya tidak tahu apa yang saya lewatkan.

Saya menginstal test suite dengan WP-CLI. Menurut jawaban ini , inithook seharusnya menjadi pemicu, namun ... tidak; juga, kode tersebut berfungsi di dalam WordPress.

Dari pemahaman saya, bootstrap dimuat terakhir, jadi masuk akal untuk tidak memicu init, jadi pertanyaan yang tersisa adalah: bagaimana sih saya harus menguji apakah kait dipicu?

Terima kasih!

File bootstrap terlihat seperti ini:

$_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';

File yang diuji terlihat seperti ini:

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

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

Dan tes itu sendiri:

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

Terima kasih!

Ionut Staicu
sumber
Jika Anda menjalankan phpunit, dapatkah Anda melihat tes yang gagal atau lulus? Apakah Anda menginstal bin/install-wp-tests.sh?
Sven
Saya pikir bagian dari masalahnya adalah bahwa mungkin RegisterCustomPostType::__construct()tidak pernah dipanggil ketika plugin dimuat untuk pengujian. Mungkin juga Anda dipengaruhi oleh bug # 29827 ; mungkin mencoba memperbarui versi unit test WP Anda.
JD
@Ven: ya, tes gagal; i diinstal bin/install-wp-tests.sh(karena saya menggunakan wp-cli) @JD: RegisterCustomPostType :: __ construct yang disebut (hanya menambahkan die()pernyataan dan phpunit berhenti di sana)
Ionut Staicu
Saya tidak terlalu yakin pada sisi pengujian unit (bukan keahlian saya), tetapi dari sudut pandang literal Anda dapat menggunakan did_action()untuk memeriksa apakah tindakan telah dipecat.
Rarst
@Rarst: terima kasih atas sarannya, tetapi masih tidak berhasil. Untuk beberapa alasan, saya pikir waktunya salah (tes dijalankan sebelum inithook).
Ionut Staicu

Jawaban:

72

Tes dalam isolasi

Saat mengembangkan sebuah plugin, cara terbaik untuk mengujinya adalah tanpa memuat lingkungan WordPress.

Jika Anda menulis kode yang dapat dengan mudah diuji tanpa WordPress, kode Anda menjadi lebih baik .

Setiap komponen yang diuji unit, harus diuji secara terpisah : ketika Anda menguji suatu kelas, Anda hanya perlu menguji kelas tersebut, dengan asumsi semua kode lain berfungsi dengan sempurna.

Isolator

Inilah alasan mengapa unit test disebut "unit".

Sebagai manfaat tambahan, tanpa memuat inti, pengujian Anda akan berjalan jauh lebih cepat.

Hindari kait di konstruktor

Kiat yang bisa saya berikan kepada Anda adalah menghindari memasukkan kait ke dalam konstruktor. Itulah salah satu hal yang akan membuat kode Anda dapat diuji secara terpisah.

Mari kita lihat kode tes di OP:

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

Dan mari kita asumsikan tes ini gagal . Siapa pelakunya ?

  • kait tidak ditambahkan sama sekali atau tidak benar?
  • metode yang mendaftarkan tipe posting tidak dipanggil sama sekali atau dengan argumen yang salah?
  • ada bug di WordPress?

Bagaimana itu bisa ditingkatkan?

Anggap kode kelas Anda adalah:

class RegisterCustomPostType {

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

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

(Catatan: Saya akan merujuk ke versi kelas ini untuk sisa jawabannya)

Cara saya menulis kelas ini memungkinkan Anda untuk membuat instance kelas tanpa memanggil add_action.

Di kelas di atas ada 2 hal yang harus diuji:

  • metode init sebenarnya memanggil add_actionlewat untuk argumen yang tepat
  • metode register_post_type sebenarnya memanggil register_post_typefungsi

Saya tidak mengatakan bahwa Anda harus memeriksa apakah jenis posting ada: jika Anda menambahkan tindakan yang tepat dan jika Anda menelepon register_post_type, jenis posting kustom harus ada: jika tidak ada itu adalah masalah WordPress.

Ingat: ketika Anda menguji plugin Anda, Anda harus menguji kode Anda , bukan kode WordPress. Dalam pengujian Anda, Anda harus menganggap bahwa WordPress (seperti perpustakaan eksternal lain yang Anda gunakan) berfungsi dengan baik. Itulah arti dari unit test.

Tapi ... dalam praktek?

Jika WordPress tidak dimuat, jika Anda mencoba memanggil metode kelas di atas, Anda mendapatkan kesalahan fatal, jadi Anda perlu mengejek fungsinya.

Metode "manual"

Tentu Anda dapat menulis perpustakaan mengejek Anda atau "secara manual" mengejek setiap metode. Itu mungkin. Saya akan memberi tahu Anda cara melakukan itu, tetapi kemudian saya akan menunjukkan kepada Anda metode yang lebih mudah.

Jika WordPress tidak dimuat saat tes sedang berjalan, itu berarti Anda dapat mendefinisikan kembali fungsinya, misalnya add_actionatau register_post_type.

Mari kita asumsikan Anda memiliki file, diambil dari file bootstrap Anda, di mana Anda memiliki:

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();
}

Saya menulis ulang fungsi untuk hanya menambahkan elemen ke array global setiap kali mereka dipanggil.

Sekarang Anda harus membuat (jika Anda belum memilikinya) memperluas kelas kasus uji dasar Anda PHPUnit_Framework_TestCase: yang memungkinkan Anda untuk dengan mudah mengkonfigurasi tes Anda.

Itu bisa berupa:

class Custom_TestCase extends \PHPUnit_Framework_TestCase {

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

}

Dengan cara ini, sebelum setiap tes, penghitung global diatur ulang.

Dan sekarang kode pengujian Anda (saya merujuk pada kelas yang ditulis ulang yang saya posting di atas):

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' ) );
  }

}

Anda harus mencatat:

  • Saya dapat memanggil kedua metode secara terpisah dan WordPress tidak dimuat sama sekali. Dengan cara ini jika satu tes gagal, saya tahu persis siapa pelakunya.
  • Seperti yang saya katakan, di sini saya menguji bahwa kelas memanggil fungsi WP dengan argumen yang diharapkan. Tidak perlu menguji apakah CPT benar-benar ada. Jika Anda menguji keberadaan CPT, maka Anda menguji perilaku WordPress, bukan perilaku plugin Anda ...

Bagus .. tapi itu PITA!

Ya, jika Anda harus secara manual mengejek semua fungsi WordPress, itu benar-benar menyebalkan. Beberapa saran umum yang dapat saya berikan adalah menggunakan sesedikit mungkin fungsi WP: Anda tidak perlu menulis ulang WordPress, tetapi fungsi WP abstrak yang Anda gunakan di kelas khusus, sehingga dapat diejek dan diuji dengan mudah.

Misalnya mengenai contoh di atas, Anda dapat menulis kelas yang mendaftarkan jenis posting, memanggil register_post_type'init' dengan argumen yang diberikan. Dengan abstraksi ini Anda masih perlu menguji kelas itu, tetapi di tempat lain dari kode Anda yang mendaftarkan jenis posting Anda dapat menggunakan kelas itu, mengejeknya dalam tes (jadi dengan asumsi itu berfungsi).

Yang luar biasa adalah, jika Anda menulis kelas yang mengabstraksi pendaftaran CPT, Anda dapat membuat repositori terpisah untuknya, dan terima kasih kepada alat-alat modern seperti Composer menanamkannya dalam semua proyek di mana Anda membutuhkannya: uji sekali, gunakan di mana-mana . Dan jika Anda pernah menemukan bug di dalamnya, Anda dapat memperbaikinya di satu tempat dan dengan sederhana composer updatesemua proyek di mana ia digunakan juga diperbaiki.

Untuk kedua kalinya: menulis kode yang dapat diuji secara terpisah berarti menulis kode yang lebih baik.

Tapi cepat atau lambat saya harus menggunakan fungsi WP di suatu tempat ...

Tentu saja. Anda tidak boleh bertindak sejajar dengan inti, itu tidak masuk akal. Anda bisa menulis kelas yang membungkus fungsi WP, tetapi kelas-kelas itu perlu diuji juga. Metode "manual" yang diuraikan di atas dapat digunakan untuk tugas-tugas yang sangat sederhana, tetapi ketika sebuah kelas berisi banyak fungsi WP, itu bisa menyebalkan.

Untungnya, di sana ada orang baik yang menulis hal-hal baik. 10up , salah satu agen WP terbesar, memiliki perpustakaan yang sangat bagus untuk orang yang ingin menguji plugin dengan cara yang benar. Itu WP_Mock.

Hal ini memungkinkan Anda untuk mengejek fungsi WP sebuah kait . Dengan asumsi Anda telah dimuat dalam tes Anda (lihat repo readme) tes yang sama yang saya tulis di atas menjadi:

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();
  }

}

Sederhana bukan? Jawaban ini bukan tutorial untuk WP_Mock, jadi baca repo readme untuk info lebih lanjut, tetapi contoh di atas harus cukup jelas, saya pikir.

Selain itu, Anda tidak perlu menulis apa pun yang dipermainkan add_actionatau sendirian register_post_type, atau mempertahankan variabel global apa pun.

Dan kelas WP?

WP juga memiliki beberapa kelas, dan jika WordPress tidak dimuat saat Anda menjalankan tes, Anda perlu mengejeknya.

Itu jauh lebih mudah daripada fungsi mengejek, PHPUnit memiliki sistem tertanam untuk mengejek objek, tetapi di sini saya ingin menyarankan Mockery kepada Anda. Ini adalah perpustakaan yang sangat kuat dan sangat mudah digunakan. Selain itu, ini adalah ketergantungan WP_Mock, jadi jika Anda memilikinya, Anda juga memiliki ejekan.

Tapi bagaimana dengan itu WP_UnitTestCase?

Suite pengujian WordPress dibuat untuk menguji inti WordPress , dan jika Anda ingin berkontribusi pada inti itu sangat penting, tetapi menggunakannya untuk plugin hanya membuat Anda menguji tidak secara terpisah.

Letakkan mata Anda di dunia WP: ada banyak kerangka kerja PHP dan CMS modern di luar sana dan tidak ada yang menyarankan pengujian plugin / modul / ekstensi (atau apa pun namanya) menggunakan kode kerangka kerja.

Jika Anda melewatkan pabrik, fitur berguna dari suite, Anda harus tahu bahwa ada hal-hal luar biasa di sana.

Gotchas dan kerugiannya

Ada kasus ketika alur kerja yang saya sarankan di sini kurang: pengujian basis data khusus .

Bahkan, jika Anda menggunakan tabel dan fungsi WordPress standar untuk menulis di sana (pada $wpdbmetode level terendah ) Anda tidak perlu benar - benar menulis data atau menguji apakah data benar - benar dalam database, pastikan saja metode yang tepat dipanggil dengan argumen yang tepat.

Namun, Anda dapat menulis plugin dengan tabel kustom dan fungsi yang membuat kueri untuk ditulis di sana, dan menguji apakah kueri itu berfungsi, itu adalah tanggung jawab Anda.

Dalam kasus-kasus itu, WordPress test suite dapat banyak membantu Anda, dan memuat WordPress mungkin diperlukan dalam beberapa kasus untuk menjalankan fungsi-fungsi seperti dbDelta.

(Tidak perlu dikatakan untuk menggunakan db yang berbeda untuk tes, bukan?)

Untungnya PHPUnit memungkinkan Anda untuk mengatur tes Anda di "suite" yang dapat dijalankan secara terpisah, sehingga Anda dapat menulis suite untuk tes basis data khusus tempat Anda memuat lingkungan WordPress (atau sebagian darinya) meninggalkan semua tes Anda bebas WordPress .

Pastikan untuk menulis kelas yang abstrak sebanyak mungkin operasi basis data, dengan cara semua kelas plugin lainnya memanfaatkannya, sehingga dengan mengolok-olok Anda dapat dengan benar menguji mayoritas kelas tanpa berurusan dengan basis data.

Untuk ketiga kalinya, menulis kode mudah diuji dalam isolasi berarti menulis kode yang lebih baik.

gmazzap
sumber
5
Omong kosong, banyak info berguna! Terima kasih! Entah bagaimana saya berhasil melewatkan seluruh poin pengujian unit (sampai sekarang, saya sedang berlatih pengujian PHP hanya di dalam Kode Dojo). Saya juga menemukan tentang wp_mock sebelumnya hari ini, tetapi untuk beberapa alasan saya berhasil mengabaikannya. Apa yang membuatku kesal adalah bahwa tes apa pun, tidak peduli seberapa kecil itu, dulu butuh setidaknya dua detik untuk menjalankan (memuat WP env pertama, jalankan tes kedua). Sekali lagi terima kasih telah membuka mata saya!
Ionut Staicu
4
Terima kasih @IonutStaicu saya lupa menyebutkan bahwa tidak memuat WordPress membuat pengujian Anda jauh lebih cepat
gmazzap
6
Juga patut menunjukkan bahwa kerangka uji unit WP Core adalah alat yang luar biasa untuk menjalankan tes INTEGRASI, yang akan menjadi tes otomatis untuk memastikan bahwa itu terintegrasi dengan baik dengan WP itu sendiri (misalnya tidak ada tabrakan nama fungsi yang tidak disengaja, dll.).
John P Bloch
1
@JohnPBloch +1 untuk poin yang bagus. Bahkan jika menggunakan namespace sudah cukup untuk menghindari tabrakan nama fungsi di WordPress, di mana semuanya bersifat global :) Tapi, tentu saja, tes integrasi / fungsional adalah sesuatu. Saya bermain dengan Behat + Mink saat ini tetapi saya masih berlatih dengan itu.
gmazzap
1
Terima kasih untuk "naik helikopter" di atas hutan UnitTest WordPress - Saya masih menertawakan gambar epik itu ;-)
birgire