PHP Unit Test 301: Test các phương thức Private / Protected
https://viblo.asia/p/php-unit-test-301-test-cac-phuong-thuc-private-protected-bJzKmWdEl9N
PHP Unit Test 301: Test các phương thức Private / Protected
Bài đăng này đã không được cập nhật trong 5 năm
Trong bài trước, chúng ta đã thực hành nhiều hơn các unit test và tìm hiểu về khái niệm data provider
trong việc sử dụng bộ input cho 1 unit test. Đến bài này, chúng ta sẽ tìm hiểu phương pháp test các method private hoặc protected.
Giới thiệu
Nếu bạn đã đọc phần thứ hai của loạt bài này, bạn sẽ nhận thấy rằng chúng ta tạo đối tượng thuộc lớp cần test thông qua toán tử new
thông thường. Bạn có thể tự hỏi là làm thế nào để test các phương thức private hay protected nếu bạn không thể gọi các method đó trực tiếp thông qua đối tượng đã tạo ra $url->someProtectedMethod()
.
Thường thì câu trả lời sẽ là: "Bạn không trực tiếp test các phương thức private hay protected". Vì bất cứ điều gì ngoài các phương thức, thuộc tính public, chỉ có thể truy cập được trong phạm vi của lớp, chúng ta giả định rằng các phương thức public của lớp sẽ tương tác với phương thức private / proteced, do đó, cuối cùng bạn đã gián tiếp test các phương thức này.
Tất nhiên, luôn luôn có những ngoại lệ: Điều gì sẽ xảy ra nếu bạn đang test một lớp trừu tượng có các phương thức protect nhưng nó không được trực tiếp sử dụng trong lớp đó?
Điều gì sẽ xảy ra nếu bạn muốn test các kịch bản khác nhau cho một phương thức cụ thể trong khi bạn không thể áp dụng các kịch bản đó thông qua các public method?
Sau đây chúng ta sẽ tìm hiểu quá trình.
Stupid User class
Tạo một tệp mới tại ./phpunit-tut/src/User.php
có nội dung sau:
Ghi chú: Code của lớp User này không tốt. Việc sử dụng
md5()
để hash mật khẩu là điều nên tránh bằng mọi giá! Trong thực tế, nó là một class được implement khá tồi. Nhưng nó cung cấp một ví dụ rất đơn giản cho việc testing.
Unit test của chúng ta sẽ khởi tạo một đối tượng User mới user = new User($details);
Bạn có thể gọi phương thức ::setPassword()
, nhưng không thể gọi ::cryptPassword()
, nhưng trong trường hợp này bạn không cần phải làm điều đó. Thực tế là việc method public gọi đến method private đã đủ để nói rằng "Method này đã được test", ít nhất là với đoạn code cụ thể này.
Như vậy thì bạn sẽ tạo unit test cho method này như thế nào? Bạn có thể thấy method khởi tạo và ::setPassword()
đều yêu cầu 1 tham số được truyền vào. PHPUnit không đòi hỏi phép thuật nào đặc biệt để làm việc với các tham số của method, như bạn sẽ sớm thấy.
Tạo unit test cho class User
Tạo file test ./phpunit-tut/tests/UserTest.php
Chúng ta cần xác định xem sẽ test cái gì trước khi đi xa hơn. Class src/User.php
là rất đơn giản, nên chúng ta có thể có 2 kịch bản đơn giản:
::setPassword()
returns true khi password được thiết lập,::getUser()
trả về array chứa password mới và password này sẽ được so sánh với kết quả mong đợi.
Chúng ta sẽ bắt đầu với ::setPassword()
trả về true:
Bây giờ chúng ta sẽ định nghĩa ra tham số bắt buộc cho ::setPassword()
method và gọi nó:
Chúng ta mong muốn $result
sẽ bằng true:
Nếu bạn chạy phpunit bây giờ, bạn sẽ nhận được 1 green bar rất đẹp.
Bây giờ chúng ta có thể tập trung vào test ::getUser
, phương thức chỉ có 1 hàm, do đó nó rất dễ dàng để test đúng không? Thực sự thì... không đúng lắm. Bạn thấy đấy, tất cả lý do để test ::getUser()
là để cho chúng ta truy cập vào các method private và lấy ra thuộc tính $user
. Chúng ta muốn xác nhận rằng $user
có giá trị như mong muốn. Điều này có nghĩa là bằng việc test ::getUser()
chúng ta cũng đang test ::__construct()
, ::setPassword()
và cryptPassword()
.
Đây là test method của chúng ta:
Điều duy nhất chúng ta có thể thực sự test trong kịch bản này đó là password được tạo bởi ::cryptPassword()
khớp với giá trị mong đợi. Chúng ta không bắt giá trị của ::setPassword()
bởi vì ta đang giả sử rằng nó đã passed test. Nếu không giả sử như thế chúng ta sẽ không biết được chắc chắn được bước tiết theo.
Chúng ta biết rằng mật khẩu raw ban đầu là fubar
, nó được hash bên trong ::setPassword()
sử dụng md5()
. Do đó chúng ta có thể xác định giá trị mong muốn đó là:
Sau đó, chúng ta gọi hàm ::getUser()
để lấy user trong trạng thái hiện tại
Và chúng ta đang mong đợi ::getUser()
trả về 1 mảng, và muốn so sánh giá trị của phần tử password trong mảng với giá trị mong muốn. Sử dụng assertEquals()
chúng ta có method test hoàn chỉnh như sau:
Test trực tiếp các method private/protected
Điều gì xảy ra nếu chúng ta muốn test thêm nhiều kịch bản cho phương thức protected mà không phải gián tiếp thông qua public API?
Điều này có thể thực hiện khá dễ dàng bằng cách sử dụng ReflectionClass
:
Sử dụng invokeMethod()
bạn có thể dễ dàng gọi các hàm private hay protected một cách trực tiếp. Để sử dụng nó, chỉ đơn giản gọi:
Last updated