Ứng dụng của Reflection trong PHPUnit
PHPUnit
3
PHP
74
White

Vu Nhat Minh viết ngày 27/05/2015

Reflection trong PHP

Reflection đã được trình bày ở bài viết trước về Reverse Engineering trong PHP. Ở đây mình nhắc lại một chút. Reflection là một bộ API được cung cấp từ PHP5 trở đi, rất hữu dụng cho cho developer khi muốn phân tích source code trong trường hợp document không đầy đủ.

Ở bài viết này mình sẽ trình bày ứng dụng của Reflection trong Unit test của PHP, mà cụ thể là PHPUnit.

Private method

Trong một class rất nhiều trường hợp bạn sẽ gặp private method, ví dụ như sau

class Robot
{
    private function greeting($name, $address)
    {
        return $name . ' at ' . $address;
    }
}

Vì nhiều lý do mà method bắt buộc phải là private, ví dụ đơn giản là khi bạn không muốn method được sử dụng bên ngoài class đó.

Vấn đề bắt đầu khi bạn phải viết Unit test cho class Robot nói trên. Nếu tạo mới một instance của Robot thì chỉ gọi được các public method, còn nếu tạo một class mới kế thừa Robot thì cũng chỉ động được đến các protected method. Vậy với private method như greeting bên trên thì làm thế nào ?

Một giải pháp hay được sử dụng ở đây là sử dụng ReflectionMethod trong bộ Reflection API của PHP

$instance = new Robot();
$rm = new ReflectionMethod("Robot", "greeting");
$rm->setAccessible(true);
$result = $rm->invokeArgs($instance, array("Batman","Gotham")); 
print $result;
// Batman at Gotham

Với cách sử dụng như trên thì có thể viết test cho method greeting như sau

class RobotTest extends PHPUnit_Framework_TestCase
{
    public function testGreeting()
    {
        $instance = new Robot();
        $rm = new ReflectionMethod("Robot", "greeting");
        $rm->setAccessible(true);
        $result = $rm->invokeArgs($instance, array("Batman","Gotham"));
        $this->assertSame("Batman at Gotham", $result);
    }
}

Private Property

Tương tự như private method, bạn cũng có thể bắt gặp private property trong PHP, class Robot có thể như sau

require_once "DbDao.php";

class Robot
{
    private $db;

    public function __construct()
    {
        $this->db = new DbDao();
    }

    public function greeting($name, $address)
    {
        $name = $this->db->getPerson($name);
        return $name . ' at ' . $address;
    }
}

Ở đây vấn đề phức tạp lên một chút. Class Robot có một private propertydb, khởi tạo trong hàm __contruct (khi tạo instance mới) và được sử dụng trong hàm greeting. db có thể phát sinh một kết nối đến Database, điều mà bạn muốn loại bỏ khi chạy test. Class DbDao có thể rất đơn giản, ở đây mình thử cho method getPerson ném ra một Exception.

class DbDao {
    public function getPerson($name)
    {
        throw new Exception('Joker rule the Gotham!');
    }
}

Bạn có thể nhận thấy, muốn test được greeting trong trường hợp này, cần phải thay đế được db bằng một object tự tạo trước. Chúng ta có thể tạo một stub thay thế cho db và dùng Reflection để inject vào một instance của class Robot.

require_once "DbDao.php";
require_once "Robot.php";

class RobotTest extends PHPUnit_Framework_TestCase
{
    private function dbStub($expected)
    {
        $stub = $this->getMockBuilder('DbDao')
            ->setMethods(array('getPerson'))
            ->getMock();
        $stub->expects($this->any())
            ->method('getPerson')
            ->with($this->anything())
            ->will($this->returnValue($expected));
        return $stub;
    }

    public function testGreeting()
    {
        $instance = $this->getMockBuilder('Robot')
            ->setMethods(null)
            ->disableOriginalConstructor()
            ->getMock();
        $rp = new ReflectionProperty("Robot", "db");
        $rp->setAccessible(true);
        $rp->setValue($instance, $this->dbStub("Mocked Person"));
        $result = $instance->greeting("Anything", "Gotham");
        $this->assertSame("Mocked Person at Gotham", $result);
    }
}

Mình sẽ giải thích rõ hơn một chút.
Hàm dbStub trả về một stub của class DbDao , Stub này khi gọi method getPerson sẽ trả về một giá trị chỉ định sẵn.
testGreeting là method test của chúng ta, tạo ra một instance của class Robotkhông chạy qua __construct, sau đó inject một stub nói trên vào private property db.

Chạy method test nói trên sẽ không ném ra Exception .

$ phpunit RobotTest.php
PHPUnit 3.7.8 by Sebastian Bergmann.

.

Time: 0 seconds, Memory: 3.50Mb

OK (1 test, 2 assertions)

Kết luận

Hi vọng qua bài viết này các bạn có thể nắm bắt được rõ hơn về Reflection thông qua ví dụ cụ thể trong PHPUnit.

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Vu Nhat Minh

54 bài viết.
723 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
116 29
Nếu bạn thường vào trang mua sắm của amazon, chắc sẽ chẳng lạ gì với menu Shop by Department. Tốc độ hiển thị nội dung của menu là tức thì so với d...
Vu Nhat Minh viết hơn 2 năm trước
116 29
White
87 4
Lời người dịch Người dịch là một developer , sau khi tìm đọc được bài viết này bằng bản gốc tiếng Anh đã cảm thấy như được "khai sáng" về khả năng...
Vu Nhat Minh viết hơn 2 năm trước
87 4
White
56 5
Đây là phần cuối của một series chuyên về thiết kế UI. Bạn nên đọc (Link) trước khi bắt đầu đọc phần này. Luật số 7: "Ăn trộm" như là một nghệ sỹ...
Vu Nhat Minh viết hơn 2 năm trước
56 5
Bài viết liên quan
White
4 0
Static function trong PHP Static function không phải là gì xa lạ đối với hầu hết các ngôn ngữ hiện đại. Trong PHP, static function hay được sử dụn...
Đặng Thành Nam viết gần 3 năm trước
4 0
Male avatar
0 0
Cách cài đặt như sau: Bước 1: SSH vào server centos (Nếu sử dụng vagrant thì dùng lệnh vagrant ssh) Bước 2: Download file phpunit4.8 Sử dụng đ...
skul169 viết hơn 1 năm trước
0 0
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
54 bài viết.
723 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!