Advertisement
  1. Code
  2. BDD

Memahami PhpSpec

Scroll to top
Read Time: 13 min

Indonesian (Bahasa Indonesia) translation by Kaustina Nurul Ilmi (you can also view the original English article)

Jika anda membandingkan PhpSpec dengan kerangka pengujian lainnya, anda akan menemukan bahwa ini adalah alat yang sangat canggih dan berpendirian. Salah satu alasan untuk ini, adalah bahwa PhpSpec bukanlah kerangka pengujian seperti yang sudah anda ketahui.  

Sebaliknya, ini adalah alat perancangan yang membantu menggambarkan perilaku perangkat lunak. Efek samping dari mendeskripsikan perilaku perangkat lunak dengan PhpSpec, adalah anda akan berakhir dengan spesifikasi yang juga akan berfungsi sebagai tes setelahnya.

Pada artikel ini, kita akan melihat di kap PhpSpec dan mencoba untuk mendapatkan pemahaman yang lebih dalam tentang cara kerjanya dan cara menggunakannya.

Jika anda ingin memoles phpspec, lihatlah  getting started tutorial saya.

Dalam artikel ini ...

  • Sebuah Tour Cepat Dari PhpSpec Internal
  • Perbedaan Antara TDD dan BDD
  • Bagaimana PhpSpec berbeda (dengan PHPUnit)
  • PhpSpec: Alat Desain

Sebuah Tour Cepat Dari PhpSpec Internal

Mari kita mulai dengan melihat beberapa konsep dan kelas kunci yang membentuk PhpSpec.

Memahami $this

Memahami $this mengacu pada apada adalah kunci untuk memahami bagaimana PhpSpec berbeda dengan alat lainnya. Pada dasarnya,   $this mengacu pada contoh kelas yang sebenarnya saat diuji. Mari kita selidiki ini sedikit lebih banyak untuk lebih memahami apa yang kita maksud.

Pertama-tama, kita membutuhkan spec dan kelas untuk dimainkan. Seperti yang anda ketahui, generator PhpSpec membuatnya sangat mudah bagi kita:

1
$ phpspec desc "Suhm\HelloWorld"
2
$ phpspec run
3
Do you want me to create `Suhm\HelloWorld` for you? y

Selanjutnya, buka file spec yang dihasilkan dan mari kita coba peroleh sedikit informasi lebih lanjut mengenai $this:

1
<?php
2
3
namespace spec\Suhm;
4
5
use PhpSpec\ObjectBehavior;
6
use Prophecy\Argument;
7
8
class HelloWorldSpec extends ObjectBehavior
9
{
10
    function it_is_initializable()
11
    {
12
        $this->shouldHaveType('Suhm\HelloWorld');
13
14
        var_dump(get_class($this));
15
    }
16
}

get_class () mengembalikan nama kelas dari objek yang diberikan. Dalam kasus ini, kita hanya melempar $this: di sana untuk melihat apa hasilnya:  

1
$ string(24) "spec\Suhm\HelloWorldSpec"

Oke, jadi tidak terlalu mengherankan,  get_class() memberitahu kita $this adalah sebuah instance dari spec\Suhm\HelloWorldSpec. Ini masuk akal karena, lagipula, ini hanya kode PHP tua biasa.  Jika kita pakai     get_parent_class (), kita akan mendapatkan PhpSpec\ObjectBehavior, karena spek kita memperluas kelas ini.

Ingat, saya baru saja memberitahu anda bahwa  $this sebenarnya disebut kelas yang diuji, yang akan menjadi Suhm\HelloWorld  dalam kasus kita? Seperti yang bisa anda lihat, nilai kembali  get_class($this) bertentangan dengan   $this-> shouldHaveType('Suhm\HelloWorld'); .

Mari kita coba yang lainnya:

1
<?php
2
3
namespace spec\Suhm;
4
5
use PhpSpec\ObjectBehavior;
6
use Prophecy\Argument;
7
8
class HelloWorldSpec extends ObjectBehavior
9
{
10
    function it_is_initializable()
11
    {
12
        $this->shouldHaveType('Suhm\HelloWorld');
13
14
        var_dump(get_class($this));
15
16
        $this->dumpThis()->shouldReturn('spec\Suhm\HelloWorldSpec');
17
    }
18
}

Dengan kode diatas, kita coba memanggil metode bernama dumpThis() di instance HelloWorld.  Kita kaitkan ekspetasi untuk pemanggilan metode, mengharapkan nilai kembali fungsi menjadi string yang berisi "spec\Suhm\HelloWorldSpec". Ini adalah nilai kembali dari get_class() pada baris di atas

Sekali lagi, generator PhpSpec dapat membantu kita dengan beberapa perancah:

1
$ phpspec run
2
Do you want me to create `Suhm\HelloWorld::dumpThis()` for you? y

Mari kita coba memanggil  get_class() dari dalam dumpThis() juga: 

1
<?php
2
3
namespace Suhm;
4
5
class HelloWorld
6
{
7
8
    public function dumpThis()
9
    {
10
        return get_class($this);
11
    }
12
}

Sekali lagi, tidak mengherankan, kita mendapatkan:

1
  10  ✘ it is initializable
2
      expected "spec\Suhm\HelloWorldSpec", but got "Suhm\HelloWorld".

Sepertinya kita melewatkan sesuatu di sini. Saya memulai dengan mengatakan $this tidak mengacu pada apa yang anda kira, tapi sejauh ini eksperimen kami tidak menunjukkan hal yang tidak terduga.  Kecuali satu hal: Bagaimana kita bisa memanggik $this->dumpThis()sebelum ia ada tanpa PHP mencicit pada kita?

Untuk memahami hal ini, kita perlu masuk lebih dalam ke kode sumber PhpSpec. Jika anda ingin melihatnya sendiri, anda bisa membaca kode pada GitHub .

Lihat kode berikut dari src/PhpSpec/ObjectBehavior.php (kelas yang spesifikasi kami perluas):

1
/**

2
 * Proxies all call to the PhpSpec subject

3
 *

4
 * @param string $method

5
 * @param array  $arguments

6
 *

7
 * @return mixed

8
 */
9
public function __call($method, array $arguments = array())
10
{
11
    return call_user_func_array(array($this->object, $method), $arguments);
12
}

Komentar memberi sebagian besar: "Proxy semua panggilan ke subjek PhpSpec". PHP   __call   Metode adalah metode ajaib yang disebut secara otomatis setiap kali metode tidak dapat diakses (atau tidak ada).  

Ini berarti saat kita mencoba menelepon   $this->dumpThis() "Proxies all call to the PhpSpec subject". Jika anda melihat kode tersebut, anda dapat melihat bahwa metode panggilan diproklamasi  ke $this->object.  (Hal yang sama berlaku untuk properti di instance kami, semuanya juga diproklamasi dengan subjek, menggunakan metode magic lainnya. Lihatlah sumbernya untuk mengetahuinya)   

Mari kita berkonsultasi mengenai get_class() sekali lagi dan lihat apa yang harus dikatakannya  tentang $this->object:

1
<?php
2
3
namespace spec\Suhm;
4
5
use PhpSpec\ObjectBehavior;
6
use Prophecy\Argument;
7
8
class HelloWorldSpec extends ObjectBehavior
9
{
10
    function it_is_initializable()
11
    {
12
        $this->shouldHaveType('Suhm\HelloWorld');
13
14
        var_dump(get_class($this->object));
15
    }
16
}

Dan lihat apa yang kita dapatkan:

1
string(23) "PhpSpec\Wrapper\Subject"

Lebih lanjut tentang Subyek

Subyek adalah pembungkus dan implementasi   PhpSpec\Wrapper\WrapperInterface. Ia adalah bagian inti dari PhpSpec dan memungkinkan semua magic [tampaknya] yang dapat dilakukan kerangka itu.  Ia membungkus sebuah instance dari kelas yang sedang kita uji, sehingga kita dapat melakukan bermacam hal seperti memanggil metode dan properti yang tidak ada dan menetapkan harapan.  

Seperti disebutkan, PhpSpec sangat berpendirian tentang bagaimana anda harus menulis dan menentukan kode anda. Satu spec peta untuk satu kelas. Anda hanya memiliki satu subjek per spec, yang PhpSpec akan bungkus dengan hati-hati untuk anda.  Yang penting untuk diperhatikan tentang hal ini adalah ia memungkinkan anda untuk menggunakan $this  seolah-olah ia adalah contoh sebenarnya dan membuat spesifikasi yang benar-benar dapat dibaca dan bermakna.

PhpSpec berisi Wrapper yang mengurus instantiating Subject. Ia membungkus Subject dengan objek yang sebenarnya kita spec'ing.  Karena Subject menerapkan  WrapperInterface, ia harus memiliki  metode  getWrappedObject() yang memberi kita akses ke objek. Ini adalah contoh objek yang kami cari sebelumnya dengan get_class ().  

Mari kita coba lagi:

1
<?php
2
3
namespace spec\Suhm;
4
5
use PhpSpec\ObjectBehavior;
6
use Prophecy\Argument;
7
8
class HelloWorldSpec extends ObjectBehavior
9
{
10
    function it_is_initializable()
11
    {
12
        $this->shouldHaveType('Suhm\HelloWorld');
13
14
        var_dump(get_class($this->object->getWrappedObject()));
15
16
        // And just to be completely sure:

17
        var_dump($this->object->getWrappedObject()->dumpThis());
18
    }
19
}

Dan begitulah:

1
$ vendor/bin/phpspec run
2
string(15) "Suhm\HelloWorld"
3
string(15) "Suhm\HelloWorld"

Meskipun banyak hal terjadi di balik layar, pada akhirnya kita masih bekerja dengan contoh objek sebenarnya Suhm\HelloWorld. Semua baik-baik saja. 

Sebelumnya, saat kita memanggil   $this-> dumpThis(), kita belajar cara panggilan diproksikan ke Subject. Kita juga belajar bahwa Subject hanya menjadi pembungkus dan bukan obyek aktual.  

Dengan pengetahuan ini, jelas bahwa kita tidak dapat memanggil dumpThis() di Subject tanpa metode magic lain. Subject mempunyai metode a __call() juga:

1
/**

2
 * @param string $method

3
 * @param array  $arguments

4
 *

5
 * @return mixed|Subject

6
 */
7
public function __call($method, array $arguments = array())
8
{
9
  if (0 === strpos($method, 'should')) {
10
      return $this->callExpectation($method, $arguments);
11
  }
12
13
  return $this->caller->call($method, $arguments);
14
}

Metode ini melakukan satu dari dua hal. Pertama, ia memeriksa apakah nama metode dimulai dengan 'harus'.  Jika iaya, ini adalah sebuah ekspektasi, dan panggilan tersebut didelegasikan ke metode yang disebut  callExpectation(). Jika tidak, panggilan tersebut malah didelegasikan ke sebuah instance PhpSpec\Wrapper\Subject\Caller

Kita akan mengabaikannya Caller untuk sekarang. Iai juga berisi objek yang dibungkus dan tahu cara memanggil metode di atasnya.  Caller mengembalikan instance yang dibungkus saat ia memanggil metode pada subjek, memungkinkan kita untuk merancang ekspektasi terhadap metode, seperti yang kita lakukan dengan dumpThis ().

Sebagai gantinya, mari kita lihat metode callExpectation() :

1
/**

2
 * @param string $method

3
 * @param array  $arguments

4
 *

5
 * @return mixed

6
 */
7
private function callExpectation($method, array $arguments)
8
{
9
    $subject = $this->makeSureWeHaveASubject();
10
11
    $expectation = $this->expectationFactory->create($method, $subject, $arguments);
12
13
    if (0 === strpos($method, 'shouldNot')) {
14
        return $expectation->match(lcfirst(substr($method, 9)), $this, $arguments, $this->wrappedObject);
15
    }
16
17
    return $expectation->match(lcfirst(substr($method, 6)), $this, $arguments, $this->wrappedObject);
18
}

Metode ini bertanggung jawab untuk membangun sebuah instance PhpSpec\Wrapper\Subject\Expectation\ExpectationInterface. Ia menentukan metode match() yang mana  callExpectation() dipanggil untuk memeriksa ekspektasi.  Ada empat macam ekspektasi:  PositiveNegativePositiveThrow dan NegativeThrow. Masing-masing ekspektasi ini berisi sebuah instance PhpSpec\Matcher\MatcherInterface yang metode match() gunakan Mari kita lihat matchers selanjutnya.

Matchers

Matchers adalah apa yang kita gunakan untuk menentukan tingkah laku objek kita. Kapan pun kita menulis  should... atau shouldnot..., kita menggunakan matcher. Anda dapat menemukan daftar matcher PhpSpec yang lengkap pada blog pribadi saya .

Ada banyak matchers disertakan dengan PhpSpec, yang semuanya memperluasu kelas PhpSpec\Matcher\BasicMatcher, yang menerapkan MatcherInterface.  Cara kerja matcher cukup lurus ke depan. Mari kita lihat bersama-sama dan saya dorong anda untuk melihat Kode sumber juga.

Sebagai contoh, mari kita lihat kode ini dari IdentityMatcher :

1
/**

2
 * @var array

3
 */
4
private static $keywords = array(
5
    'return',
6
    'be',
7
    'equal',
8
    'beEqualTo'
9
);
10
11
/**

12
 * @param string $name

13
 * @param mixed  $subject

14
 * @param array  $arguments

15
 *

16
 * @return bool

17
 */
18
public function supports($name, $subject, array $arguments)
19
{
20
    return in_array($name, self::$keywords)
21
        && 1 == count($arguments)
22
    ;
23
}

Metode supports() didikte oleh MatcherInterface. Dalam kasus ini, empat alias didefinisikan untuk matcher di array $keywords.  Ini akan memungkinkan matcher untuk mendukung shouldReturn()shouldBe()shouldEqual() atau shouldBeEqualTo(), atau shouldNotReturn()shouldNotBe()shouldNotEqual() atau shouldNotBeEqualTo().

Dari   BasicMatcher, dua metode yang diwarisi: positiveMatch() dan negativeMatch (). Mereka terlihat seperti ini:

1
/**

2
 * @param string $name

3
 * @param mixed  $subject

4
 * @param array  $arguments

5
 *

6
 * @return mixed

7
 *

8
 *   @throws FailureException

9
 */
10
final public function positiveMatch($name, $subject, array $arguments)
11
{
12
    if (false === $this->matches($subject, $arguments)) {
13
        throw $this->getFailureException($name, $subject, $arguments);
14
    }
15
16
    return $subject;
17
}

Metode positiveMatch() melempar pengecualian jika metode matches() (metode abstrak yang harus diimplementasikan oleh matchers) kembali false. Metode negativeMatch() bekerja dengan cara yang berlawanan.  Metode matches()  untuk IdentityMatcher menggunakan  === operator untuk membandingkan  $subject dengan argumen yang diberikan pada metode matcher:

1
/**

2
 * @param mixed $subject

3
 * @param array $arguments

4
 *

5
 * @return bool

6
 */
7
protected function matches($subject, array $arguments)
8
{
9
   return $subject === $arguments[0];
10
}

Kita bisa menggunakan matcher seperti ini:

1
$this->getUser()->shouldNotBeEqualTo($anotherUser);

Yang akhirnya akan memanggil  negativeMatch() dan pastikan matches() kembali false

Lihatlah beberapa matcher lainnya dan lihat apa yang mereka lakukan!

Janji lebih banyak magic

Sebelum kita akhiri tur singkat internal PhpSpec ini, mari kita lihat satu keajaiban lagi:

1
<?php
2
3
namespace spec\Suhm;
4
5
use PhpSpec\ObjectBehavior;
6
use Prophecy\Argument;
7
8
class HelloWorldSpec extends ObjectBehavior
9
{
10
    function it_is_initializable(\StdClass $object)
11
    {
12
        $this->shouldHaveType('Suhm\HelloWorld');
13
14
        var_dump(get_class($object));
15
    }
16
}

Dengan menambahkan tipe yang diisyaratkan parameter $object untuk contoh kita, PhpSpec akan otomatis menggunakan refleksi untuk memasukan sebuah instance dari kelas untuk kita gunakan.  Tapi dengan hal-hal yang kita lihat, apakah kita benar-benar percaya bahwa kita dapat sebuah contoh StdClass? Mari kita berkonsultasi get_class()sekali lagi:

1
$ vendor/bin/phpspec run
2
string(28) "PhpSpec\Wrapper\Collaborator"

Tidak. Dari pada StdClass, kita dapatkan sebuah instance PhpSpec\Wrapper\Collaborator. Tentang apakah ini? 

Seperti   SubjectCollaborator  adalah wrapper dan implementasi WrapperInterface. Ia membungkus instance \Prophecy\Prophecy\ObjectProphecy,yang berasal dari Prophecy, kerangka mocking yang datang bersama-sama dengan PhpSpec.  Daripada instance StdClass, PhpSpec memberi kita mock. Hal ini membuat mocking dengan mudah dengan PhpSpec dan memungkinkan kita menambahkan promises ke objek kita seperti ini:

1
$user->getAge()->willReturn(10);
2
3
$this->setUser($user);
4
$this->getUserStatus()->shouldReturn('child');

Dengan tur singkat bagian-bagian internal PhpSpec ini, saya harap anda melihat bahwa ini lebih dari sekedar kerangka pengujian sederhana.

Perbedaan Antara TDD Dan BDD

PhpSpec adalah alat untuk melakukan SpecBDD, jadi untuk mendapatkan pemahaman yang lebih baik, mari kita lihat perbedaan antara test driven development (TDD) dan behavior driven development (BDD). Setelah itu, kita akan melihat sekilas bagaimana PhpSpec berbeda dengan alat lain seperti PHPUnit.

TDD merupakan konsep membiarkan pengujian otomatis menggerakkan disain dan implementasi kode. Dengan menulis tes kecil untuk setiap fitur, sebelum benar-benar menerapkannya, saat kita mendapatkan passing test, kita tahu bahwa kode kita memenuhi fitur spesifik itu.  Dengan passing test, setelah refactoring, kita berhenti melakukan coding dan menulis tes berikutnya. Mantra nya "merah", "hijau", "refactor"!

BDD berasal dari - dan sangat mirip dengan - TDD. Jujur, ini soal kata-kata, yang memang penting karena bisa mengubah cara kita berpikir sebagai pengembang. Dimana TDD berbicara tentang pengujian, BDD berbicara tentang mendeskripsikan perilaku.  

Dengan TDD, kitai fokus untuk memverifikasi bahwa kode kita sesuai dengan cara kita mengharapkannya bekerja, sedangkan dengan BDD, kita fokus untuk memverifikasi bahwa kode kita benar-benar sesuai dengan keinginan yang kita inginkan.  Alasan utama munculnya BDD, sebagai alternatif TDD, adalah untuk menghindari penggunaan kata "test". Dengan BDD, kita tidak terlalu tertarik untuk menguji penerapan kode kita, kita lebih tertarik untuk menguji apa yang dilakukannya (perilakunya).  Saat kita melakukan BDD, bukan TDD, kita memiliki cerita dan spesifikasi. Hal ini membuat penulisan tes tradisional berlebihan.

Cerita dan spesifikasi sangat terkait dengan harapan para pemangku kepentingan proyek. Menulis cerita (dengan alat seperti Behat), akan terjadi bersama-sama dengan para pemangku kepentingan atau ahli domain. Cerita-cerita tersebut mencakup perilaku eksternal.  Kami menggunakan spesifikasi untuk merancang perilaku internal yang diperlukan untuk memenuhi langkah-langkah cerita. Setiap langkah dalam sebuah cerita mungkin memerlukan banyak iterasi dengan spesifikasi penulisan dan penerapan kode, sebelum memuaskan.  Cerita kami, bersama dengan spesifikasi kami, membantu kami untuk memastikan bahwa bukan hanya kami yang sedang membangun hal yang berfungsi, tapi juga hal yang benar. Jadi, BDD banyak berhubungan dengan komunikasi.

Bagaimana PhpSpec Berbeda dengan PHPUnit?

Beberapa bulan yang lalu, anggota penting dari komunitas PHP, Mathias Verraes, memposting " Kerangka unit testing di tweet " di Twitter. Intinya sesuai dengan kode sumber kerangka pengujian unit fungsional menjadi satu tweet tunggal.  Seperti yang dapat anda lihat dari inti ini, kode ini benar-benar fungsional, dan memungkinkan anda menulis tes unit dasar. Konsep unit testing sebenarnya cukup sederhana: periksalah semacam pernyataan dan beri tahukan pengguna hasilnya.

Tentu saja, sebagian besar kerangka kerja pengujian, seperti PHPUnit, memang jauh lebih maju, dan dapat melakukan lebih dari sekadar kerangka kerja Mathias, namun tetap menunjukkan poin penting: anda menegaskan sesuatu dan kemudian kerangka anda menjalankan pernyataan itu untuk anda.

Mari kita lihat tes PHPUnit yang sangat mendasar:

1
public function testTrue()
2
{
3
   $this->assertTrue(false);
4
}

Apakah anda bisa menulis sebuah implementasi super sederhana dari kerangka pengujian yang bisa menjalankan tes ini? Saya cukup yakin bahwa jawabannya adalah "iya" anda bisa melakukan itu.  Lagi pula, satu-satunya hal Metode assertTrue() harus lakukan adalah membandingkan nilai terhadap true  dan melemparkan pengecualian jika gagal. Intinya, apa yang sedang terjadi sebenarnya cukup lurus ke depan.

Jadi bagaimana PhpSpec berbeda? Pertama-tama, PhpSpec bukanlah alat pengujian. Menguji kode anda bukanlah tujuan utama PhpSpec, namun ini menjadi efek samping jika anda menggunakannya untuk merancang perangkat lunak anda dengan menambahkan spesifikasi untuk perilaku (BDD) secara bertahap.  

Kedua, saya pikir bagian di atas seharusnya sudah menjelaskan bagaimana PhpSpec berbeda. Namun, mari bandingkan beberapa kode:

1
// PhpSpec

2
function it_is_initializable()
3
{
4
    $this->shouldHaveType('Suhm\HelloWorld');
5
}
6
7
// PHPUnit

8
function testIsInitializable()
9
{
10
    $object = new Suhm\HelloWorld();
11
12
    $this->assertInstanceOf('Suhm\HelloWorld', $object);
13
}

Karena PhpSpec sangat berpendirian dan membuat beberapa pernyataan mengenai bagaimana kode kita dirancang, ini memberi kita cara yang sangat mudah untuk menggambarkan kode kita.  Di sisi lain, PHPUnit tidak membuat pernyataan apapun terhadap kode kita dan membiarkan kita melakukan cukup banyak apa yang kita inginkan. Pada dasarnya, semua PHPUnit melakukannya untuk kita dalam contoh ini, adalah untuk menjalankannya $object terhadap operator instanceof.   

Meskipun PHPUnit mungkin tampak lebih mudah untuk memulai (saya rasa tidak), jika anda tidak hati-hati, anda dapat dengan mudah terjebak dalam perangkap desain dan arsitektur yang buruk karena memungkinkan anda melakukan hampir semua hal.  Bisa dikatakan, PHPUnit masih bisa menjadi hebat untuk banyak kasus penggunaan, namun ini bukan alat desain seperti PhpSpec. Tidak ada panduan - anda harus tahu apa yang anda lakukan.

PhpSpec: Alat Desain

Dari Situs PhpSpec, kita dapat belajar bahwa PhpSpec adalah:

Alat bantu php untuk mengemudikan desain yang muncul dengan spesifikasi.

Izinkan saya mengatakannya sekali lagi: PhpSpec bukanlah kerangka pengujian. Ini adalah alat pengembangan. Alat perancang perangkat lunak. Ini bukan kerangka pernyataan sederhana yang membandingkan nilai dan membuang pengecualian.  Ini adalah alat yang membantu kita dalam merancang dan membangun kode yang dibuat dengan baik. Ia mengharuskan kita untuk memikirkan struktur kode kita dan menerapkan pola arsitektur tertentu, di mana satu peta kelas menjadi satu spec.  Jika anda melanggar prinsip tanggung jawab tunggal dan perlu melakukan mock sesuatu, anda tidak akan diizinkan melakukannya.

Selamat spec'ing!

Oh! Dan akhirnya, = karena PhpSpec sendiri adalah spec'ed, saya sarankan anda pergi ke GitHub dan  mengeksplorasi sumber untuk mempelajari lebih lanjut .

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.