Advertisement
  1. Code
  2. PHP

Testing Like a Boss di Larvel: Model

Scroll to top
Read Time: 13 min

() translation by (you can also view the original English article)

Jika Anda berharap untuk mempelajari mengapa tes bermanfaat, ini bukanlah artikel untuk Anda. Selama tutorial ini, saya akan berasumsi bahwa Anda sudah memahami keuntungan, dan berharap untuk belajar cara terbaik untuk menulis dan mengatur tes Anda dalam Laravel 4.

Versi 4 Laravel menawarkan perbaikan serius dalam kaitannya dengan testing, bila dibandingkan dengan rilis sebelumnya. Ini adalah artikel pertama dari seri yang akan mencakup bagaimana untuk menulis tes untuk aplikasi Laravel 4. Kami akan mulai seri dengan membahas model testing.


Setup

In-memory database

Kecuali Anda menjalankan raw query pada database Anda, Laravel memungkinkan aplikasi Anda untuk tetap agnostik database. Dengan perubahan sederhana driver, aplikasi Anda sekarang dapat bekerja dengan DBMS yang lain (MySQL, PostgreSQL, SQLite, dll). Di antara pilihan default, SQLite menawarkan fitur yang aneh, namun sangat berguna: database di memori.

Dengan Sqlite, kita dapat mengatur koneksi database: memori:, yang secara drastis akan mempercepat tes kami, karena database tidak ada pada hard disk. Selain itu, produksi pengembangan database akan pernah diisi dengan data uji kiri-atas, karena sambungan,: memori:, selalu dimulai dengan database kosong.

Singkatnya: in-memory database memungkinkan untuk tes cepat dan bersih.

Dalam direktori app/config/testing, membuat file baru, bernama database.php, dan mengisinya dengan konten berikut:

1
// app/config/testing/database.php
2
3
<?php
4
5
return array(
6
7
    'default' => 'sqlite',
8
9
    'connections' => array(
10
        'sqlite' => array(
11
            'driver'   => 'sqlite',
12
            'database' => ':memory:',
13
            'prefix'   => ''
14
        ),
15
    )
16
);

Fakta bahwa database.php ditempatkan dalam konfigurasi testing direktori berarti bahwa pengaturan ini hanya akan digunakan ketika di lingkungan testing (yang Laravel secara otomatis mengatur). Dengan demikian, ketika aplikasi yang diakses biasanya, database di memori tidak akan digunakan.

Sebelum menjalankan tes

Karena  in-memory database selalu kosong saat sambungan dibuat, sangat penting untuk bermigrasi database sebelum setiap tes. Untuk melakukannya, buka app/tests/TestCase.php dan menambahkan metode berikut ke akhir kelas:

1
/**

2
 * Migrates the database and set the mailer to 'pretend'.

3
 * This will cause the tests to run quickly.

4
 *

5
 */
6
private function prepareForTests()
7
{
8
    Artisan::call('migrate');
9
    Mail::pretend(true);
10
}

Catatan: Metode setUp() yang dijalankan oleh PHPUnit sebelum setiap tes.

Metode ini akan menyiapkan database, dan mengubah status Laravel's Mailer kelas untuk berpura-pura. Dengan cara ini, Mailer tidak akan mengirim email nyata ketika menjalankan tes. Sebaliknya, itu akan log "Kirim".

Untuk menyelesaikan app/tests/TestCase.php, panggilan prepareForTests() dalam PHPUnit setUp() metode, yang akan mengeksekusi sebelum setiap tes.

Jangan lupa parent::setUp(), seperti kita sedang menimpa metode parent class.

1
/**

2
 * Default preparation for each test

3
 *

4
 */
5
public function setUp()
6
{
7
    parent::setUp(); // Don't forget this!

8
9
    $this->prepareForTests();
10
}

Pada titik ini, app/tests/TestCase.php akan tampak seperti kode berikut. Ingat bahwa createApplication dibuat secara otomatis oleh Laravel. Anda tidak perlu khawatir tentang hal itu.

1
// app/tests/TestCase.php
2
3
<?php
4
5
class TestCase extends Illuminate\Foundation\Testing\TestCase {
6
7
    /**

8
     * Default preparation for each test

9
     */
10
    public function setUp()
11
    {
12
        parent::setUp();
13
14
        $this->prepareForTests();
15
    }
16
17
    /**

18
     * Creates the application.

19
     *

20
     * @return Symfony\Component\HttpKernel\HttpKernelInterface

21
     */
22
    public function createApplication()
23
    {
24
        $unitTesting = true;
25
26
        $testEnvironment = 'testing';
27
28
        return require __DIR__.'/../../start.php';
29
    }
30
31
    /**

32
     * Migrates the database and set the mailer to 'pretend'.

33
     * This will cause the tests to run quickly.

34
     */
35
    private function prepareForTests()
36
    {
37
        Artisan::call('migrate');
38
        Mail::pretend(true);
39
    }
40
}

Sekarang, untuk menulis kami tes, hanya extend TestCase, dan database akan diinisialisasi dan bermigrasi sebelum setiap tes.


Tes

Memang benar untuk mengatakan bahwa, dalam artikel ini, kami tidak akan mengikuti proses TDD. Masalahnya di sini didaktik, dengan tujuan untuk menunjukkan bagaimana tes dapat ditulis. Karena ini, saya memilih untuk mengungkapkan model dalam pertanyaan pertama, dan kemudian mereka tes yang terkait. Saya percaya bahwa ini adalah cara yang lebih baik untuk menggambarkan tutorial ini.

Konteks dari aplikasi demo ini adalah sebuah blog/CMS sederhana, yang mengandung pengguna (otentikasi), posting dan halaman statis (yang ditampilkan dalam menu).

Post model

Harap dicatat bahwa model extend kelas, Ardent, daripada Eloquent. Ardent adalah paket yang membuat untuk validasi mudah, saat menyimpan model (Lihat properti $rules).

Selanjutnya, kami memiliki array public statis $factory, yang memanfaatkan paket FactoryMuff, untuk membantu dengan pembuatan obyek ketika testing.

Ardentx dan FactoryMuff juga tersedia melalui Packagist dan composer.

Dalam model Post kami, kami memiliki relationship dengan model User, melalui metode author magic.

Akhirnya, kami memiliki sebuah metode sederhana yang mengembalikan tanggal, diformat sebagai "hari/bulan/tahun".

1
// app/models/Post.php
2
3
<?php
4
5
use LaravelBook\Ardent\Ardent;
6
7
class Post extends Ardent {
8
9
    /**

10
     * Table

11
     */
12
    protected $table = 'posts';
13
14
    /**

15
     * Ardent validation rules

16
     */
17
    public static $rules = array(
18
        'title' => 'required',              // Post tittle

19
        'slug' => 'required|alpha_dash',    // Post Url

20
        'content' => 'required',            // Post content (Markdown)

21
        'author_id' => 'required|numeric',  // Author id

22
    );
23
24
    /**

25
     * Array used by FactoryMuff to create Test objects

26
     */
27
    public static $factory = array(
28
        'title' => 'string',
29
        'slug' => 'string',
30
        'content' => 'text',
31
        'author_id' => 'factory|User', // Will be the id of an existent User.

32
    );
33
34
    /**

35
     * Belongs to user

36
     */
37
    public function author()
38
    {
39
        return $this->belongsTo( 'User', 'author_id' );
40
    }
41
42
    /**

43
     * Get formatted post date

44
     *

45
     * @return string

46
     */
47
    public function postedAt()
48
    {
49
        $date_obj =  $this->created_at;
50
51
        if (is_string($this->created_at))
52
            $date_obj =  DateTime::createFromFormat('Y-m-d H:i:s', $date_obj);
53
54
        return $date_obj->format('d/m/Y');
55
    }
56
}

Post test

Untuk menjaga hal-hal yang terorganisir, saya sudah ditempatkan kelas dengan tes model Post di app/tests/models/PostTest.php. Kita melalui semua tes, satu bagian pada suatu waktu.

1
// app/tests/models/PostTest.php
2
3
<?php
4
5
use Zizaco\FactoryMuff\Facade\FactoryMuff;
6
7
class PostTest extends TestCase
8
{

Kami extend beberapa TestCase kelas, yang merupakan persyaratan untuk PHPUnit di Laravel. Juga, jangan lupa kami metode prepareTests yang akan berjalan sebelum setiap tes.

1
    public function test_relation_with_author()
2
    {
3
        // Instantiate, fill with values, save and return

4
        $post = FactoryMuff::create('Post');
5
6
        // Thanks to FactoryMuff, this $post have an author

7
        $this->assertEquals( $post->author_id, $post->author->id );
8
    }

Tes ini adalah salah satu yang "opsional". Kami menguji bahwa hubungan "Post dimilik oleh User". Tujuan di sini adalah sebagian besar untuk menunjukkan fungsi FactoryMuff.

Setelah kelas Post memiliki $factory statis array yang berisi 'author_id' => ' factory | User ' (Perhatikan kode sumber dari model, ditampilkan di atas) FactoryMuff instantiate mengisi User baru atributnya, menyimpan dalam database dan akhirnya kembali dengan id untuk atribut author_id dalam Post.

Untuk ini menjadi mungkin, User model harus memiliki array $factory yang menggambarkan kolom.

Perhatikan bagaimana Anda dapat mengakses relasi User melalui $post->author. Sebagai contoh, kita dapat mengakses $post->author->username, atau atribut pengguna apapun lainnya yang sudah ada.

Package FactoryMuff memungkinkan cepat Instansiasi object-object yang konsisten untuk tujuan testubg, tetap menghargai dan instantiating hubungan yang diperlukan. Dalam kasus ini, ketika kita membuat Post dengan FactoryMuff::create('Post') User akan juga disiapkan dan dibuat tersedia.

1
    public function test_posted_at()
2
    {
3
        // Instantiate, fill with values, save and return

4
        $post = FactoryMuff::create('Post');
5
6
        // Regular expression that represents d/m/Y pattern

7
        $expected = '/\d{2}\/\d{2}\/\d{4}/';
8
9
        // True if preg_match finds the pattern

10
        $matches = ( preg_match($expected, $post->postedAt()) ) ? true : false;
11
12
        $this->assertTrue( $matches );
13
    }
14
}

Untuk menyelesaikan, kita menentukan jika string kembali dengan metode postedAt() mengikuti format "hari/bulan/tahun". Untuk verifikasi seperti itu, kalimat biasa digunakan untuk menguji jika pola \d{2}\/\d{2}\/\d{4} ("2 nomor" + "bar" + "2 nomor" + "bar" + "4 angka") ditemukan.

Selain itu, kita bisa menggunakan PHPUnit's assertRegExp matcher.

Pada titik ini, app/tests/models/PostTest.php file adalah sebagai berikut:

1
// app/tests/models/PostTest.php
2
3
<?php
4
5
use Zizaco\FactoryMuff\Facade\FactoryMuff;
6
7
class PostTest extends TestCase
8
{
9
    public function test_relation_with_author()
10
    {
11
        // Instantiate, fill with values, save and return

12
        $post = FactoryMuff::create('Post');
13
14
        // Thanks to FactoryMuff this $post have an author

15
        $this->assertEquals( $post->author_id, $post->author->id );
16
    }
17
18
    public function test_posted_at()
19
    {
20
        // Instantiate, fill with values, save and return

21
        $post = FactoryMuff::create('Post');
22
23
        // Regular expression that represents d/m/Y pattern

24
        $expected = '/\d{2}\/\d{2}\/\d{4}/';
25
26
        // True if preg_match finds the pattern

27
        $matches = ( preg_match($expected, $post->postedAt()) ) ? true : false;
28
29
        $this->assertTrue( $matches );
30
    }
31
}

PS: saya memilih untuk tidak menulis nama tes di CamelCase untuk tujuan terbaca. PSR-1 Maafkan aku, tapi testRelationWithAuthor tidak dapat dibaca sebagai saya pribadi lebih suka. Anda bebas untuk menggunakan gaya yang Anda paling inginkan, tentu saja.

Page model

CMS kita membutuhkan model untuk mewakili halaman statis. Model ini implementasi sebagai berikut:

1
<?php
2
3
// app/models/Page.php

4
5
use LaravelBook\Ardent\Ardent;
6
7
class Page extends Ardent {
8
9
    /**

10
     * Table

11
     */
12
    protected $table = 'pages';
13
14
    /**

15
     * Ardent validation rules

16
     */
17
    public static $rules = array(
18
        'title' => 'required',              // Page Title

19
        'slug' => 'required|alpha_dash',    // Slug (url)

20
        'content' => 'required',            // Content (markdown)

21
        'author_id' => 'required|numeric',  // Author id

22
    );
23
24
    /**

25
     * Array used by FactoryMuff

26
     */
27
    public static $factory = array(
28
        'title' => 'string',
29
        'slug' => 'string',
30
        'content' => 'text',
31
        'author_id' => 'factory|User',  // Will be the id of an existent User.

32
    );
33
34
    /**

35
     * Belongs to user

36
     */
37
    public function author()
38
    {
39
        return $this->belongsTo( 'User', 'author_id' );
40
    }
41
42
    /**

43
     * Renders the menu using cache

44
     *

45
     * @return string Html for page links.

46
     */
47
    public static function renderMenu()
48
    {
49
        $pages = Cache::rememberForever('pages_for_menu', function()
50
        {
51
            return Page::select(array('title','slug'))->get()->toArray();
52
        });
53
54
        $result = '';
55
56
        foreach( $pages as $page )
57
        {
58
            $result .= HTML::action( 'PagesController@show', $page['title'], ['slug'=>$page['slug']] ).' | ';
59
        }
60
61
        return $result;
62
    }
63
64
    /**

65
     * Forget cache when saved

66
     */
67
    public function afterSave( $success )
68
    {
69
        if( $success )
70
            Cache::forget('pages_for_menu');
71
    }
72
73
    /**

74
     * Forget cache when deleted

75
     */
76
    public function delete()
77
    {
78
        parent::delete();
79
        Cache::forget('pages_for_menu');
80
    }
81
82
}

Kita dapat amati bahwa metode statis, renderMenu(), menjadikan jumlah link semua halaman yang sudah ada. Nilai ini disimpan dalam cache kunci, 'pages_for_menu'. Dengan cara ini, nanti kita panggal ke renderMenu(), akan ada tidak perlu hit database asli. Ini dapat memberikan peningkatan yang signifikan untuk kinerja aplikasi kami.

Namun, jika Page yang disimpan atau dihapus (metode afterSave() dan delete()), nilai cache akan dihapus, menyebabkan renderMenu() untuk mencerminkan keadaan baru database. Jadi, jika nama halaman  berubah, atau jika akan dihapus, key 'pages_for_menu' dihapus dari cache. (Cache::forget('pages_for_menu');)

Catatan: Metode, afterSave(), tersedia melalui Ardent package. Jika tidak, maka akan diperlukan untuk menerapkan method save() untuk membersihkan cache dan memanggil parent::save();

Page Test

Di: app/tests/models/PageTest.php, kita akan menulis testing berikut:

1
<?php
2
3
// app/tests/models/PageTest.php

4
5
use Zizaco\FactoryMuff\Facade\FactoryMuff;
6
7
class PageTest extends TestCase
8
{
9
    public function test_get_author()
10
    {
11
        $page = FactoryMuff::create('Page');
12
13
        $this->assertEquals( $page->author_id, $page->author->id );
14
    }

Sekali lagi, kami memiliki tes "opsional" untuk mengkonfirmasi relasi. Karena hubungan tanggung jawab Illuminate\Database\Eloquent, yang telah ditutupi oleh Laravel sendiri tes, kita tidak perlu untuk menulis tes lain untuk mengkonfirmasi bahwa kode ini bekerja seperti yang diharapkan.

1
    public function test_render_menu()
2
    {
3
        $pages = array();
4
5
        for ($i=0; $i < 4; $i++) {
6
            $pages[] = FactoryMuff::create('Page');
7
        }
8
9
        $result = Page::renderMenu();
10
11
        foreach ($pages as $page)
12
        {
13
            // Check if each page slug(url) is present in the menu rendered.

14
            $this->assertGreaterThan(0, strpos($result, $page->slug));
15
        }
16
17
        // Check if cache has been written

18
        $this->assertNotNull(Cache::get('pages_for_menu'));
19
    }

Ini adalah salah satu paling penting tes untuk model Page. Pertama, empat halaman dibuat dalam for loop. Setelah itu, hasil dari panggilan renderMenu() disimpan dalam variabel $result. Variabel ini harus berisi string HTML, yang berisi link ke halaman yang sudah ada.

Foreach loop memeriksa apakah slug (url) setiap halaman di $result. Ini adalah cukup, karena format yang sama persis dari HTML tidak relevan dengan kebutuhan kita.

Akhirnya, kita menentukan apakah cache kunci, pages_for_menu, memiliki sesuatu yang disimpan. Dengan kata lain, renderMenu() panggilan benar-benar disimpan beberapa nilai ke cache?

1
    public function test_clear_cache_after_save()
2
    {
3
        // An test value is saved in cache

4
        Cache::put('pages_for_menu','avalue', 5);
5
6
        // This should clean the value in cache

7
        $page = FactoryMuff::create('Page');
8
9
        $this->assertNull(Cache::get('pages_for_menu'));
10
    }

Tes ini bertujuan untuk memverifikasi jika, saat menyimpan Page baru, tombol cache 'pages_for_menu' dikosongkan. FactoryMuff::create('Page'); akhirnya memicu save() method, sehingga yang harus cukup untuk kunci, 'pages_for_menu', harus dibersihkan.

1
    public function test_clear_cache_after_delete()
2
    {
3
        $page = FactoryMuff::create('Page');
4
5
        // An test value is saved in cache

6
        Cache::put('pages_for_menu','value', 5);
7
8
        // This should clean the value in cache

9
        $page->delete();
10
11
        $this->assertNull(Cache::get('pages_for_menu'));
12
    }

Mirip dengan tes sebelumnya, yang satu ini menentukan jika tombol 'pages_for_menu' dikosongkan benar setelah menghapus Page.

PageTest.php Anda akan terlihat seperti:

1
<?php
2
3
// app/tests/models/PageTest.php

4
5
use Zizaco\FactoryMuff\Facade\FactoryMuff;
6
7
class PageTest extends TestCase
8
{
9
    public function test_get_author()
10
    {
11
        $page = FactoryMuff::create('Page');
12
13
        $this->assertEquals( $page->author_id, $page->author->id );
14
    }
15
16
    public function test_render_menu()
17
    {
18
        $pages = array();
19
20
        for ($i=0; $i < 4; $i++) {
21
            $pages[] = FactoryMuff::create('Page');
22
        }
23
24
        $result = Page::renderMenu();
25
26
        foreach ($pages as $page)
27
        {
28
            // Check if each page slug(url) is present in the menu rendered.

29
            $this->assertGreaterThan(0, strpos($result, $page->slug));
30
        }
31
32
        // Check if cache has been written

33
        $this->assertNotNull(Cache::get('pages_for_menu'));
34
    }
35
36
    public function test_clear_cache_after_save()
37
    {
38
        // An test value is saved in cache

39
        Cache::put('pages_for_menu','avalue', 5);
40
41
        // This should clean the value in cache

42
        $page = FactoryMuff::create('Page');
43
44
        $this->assertNull(Cache::get('pages_for_menu'));
45
    }
46
47
    public function test_clear_cache_after_delete()
48
    {
49
        $page = FactoryMuff::create('Page');
50
51
        // An test value is saved in cache

52
        Cache::put('pages_for_menu','value', 5);
53
54
        // This should clean the value in cache

55
        $page->delete();
56
57
        $this->assertNull(Cache::get('pages_for_menu'));
58
    }
59
}

User model

Terkait dengan model sebelumnya, kami sekarang memiliki User. Berikut adalah kode untuk model:

1
<?php
2
3
// app/models/User.php

4
5
use Zizaco\Confide\ConfideUser;
6
7
class User extends ConfideUser {
8
9
    // Array used in FactoryMuff

10
    public static $factory = array(
11
        'username' => 'string',
12
        'email' => 'email',
13
        'password' => '123123',
14
        'password_confirmation' => '123123',
15
    );
16
17
    /**

18
     * Has many pages

19
     */
20
    public function pages()
21
    {
22
        return $this->hasMany( 'Page', 'author_id' );
23
    }
24
25
    /**

26
     * Has many posts

27
     */
28
    public function posts()
29
    {
30
        return $this->hasMany( 'Post', 'author_id' );
31
    }
32
33
}

Model ini tidak ada tes.

Kita dapat mengamati bahwa, dengan pengecualian dari relasi (yang dapat membantu untuk menguji), tidak ada setiap metode pelaksanaan di sini. Gimana tentang otentikasi? Yah, penggunaan package Confide sudah menyediakan implementasi dan tes untuk ini.

Test untuk Zizaco\Confide\ConfideUser terletak di ConfideUserTest.php.

Hal ini penting untuk menentukan tanggung jawab kelas sebelum menulis tes Anda. Pengujian pilihan untuk "reset password" User akan redundant. Hal ini karena tanggung-jawab yang tepat untuk tes ini dalam Zizaco\Confide\ConfideUser. bukan di User

Hal yang sama juga berlaku untuk tes validasi data. Sebagai paket, Ardent, menangani tanggung jawab ini, itu tidak akan membuat banyak akal untuk menguji fungsionalitas lagi.

Singkatnya: menjaga tes Anda bersih dan terorganisir. Menentukan tanggung-jawab yang tepat dari setiap kelas, dan menguji hanya apa adalah benar-benar tanggung jawabnya.


Kesimpulan

Running Tests

Menggunakanin-memory database adalah praktik yang baik untuk melaksanakan tes terhadap database dengan cepat. Berkat bantuan dari beberapa package, seperti Ardent, FactoryMuff dan Confide, Anda dapat meminimalkan jumlah kode pada model Anda, sementara menjaga tes bersih dan objektif.

Dalam sekuel untuk artikel ini, kami akan meninjau Controller pengujian. Menantikan!

Mari kita masih memulai dengan Laravel 4, mengajarkan Anda penting!

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.