Là đối tượng tiêm thực sự cần thiết?
Để có thể kiểm tra đầy đủ trong sự cô lập, mã cần tránh để khởi tạo trực tiếp các lớp và tiêm bất kỳ đối tượng nào cần thiết vào bên trong các đối tượng.
Tuy nhiên, có ít nhất hai ngoại lệ cho quy tắc này:
- Lớp học là một lớp ngôn ngữ, vd
ArrayObject
- Lớp học là một "đối tượng giá trị" thích hợp .
Không cần ép buộc cho các đối tượng cốt lõi
Trường hợp đầu tiên rất dễ giải thích: đó là một cái gì đó được nhúng trong ngôn ngữ để bạn chỉ cần giả sử nó hoạt động. Nếu không, bạn cũng nên kiểm tra bất kỳ chức năng lõi PHP hoặc tuyên bố như thế return
...
Không cần ép buộc đối tượng giá trị
Trường hợp thứ hai, liên quan đến bản chất của một đối tượng giá trị. Thực ra:
- nó là bất biến
- nó không có bất kỳ sự thay thế đa hình nào
- theo định nghĩa, một đối tượng giá trị không thể phân biệt với một đối tượng khác có cùng các đối số hàm tạo
Nó có nghĩa là một đối tượng giá trị có thể được xem như là một loại bất biến của chính nó, giống như, ví dụ, một chuỗi.
Nếu một số mã $myEmail = 'some.email@example.com'
không ai quan tâm đến việc chế nhạo chuỗi đó, và theo cách tương tự, không ai nên quan tâm đến việc chế nhạo một dòng như new Email('some_name@example.com')
(giả sử đó Email
là bất biến và có thể final
).
Đối với những gì tôi có thể đoán từ mã của bạn, My_Admin_Notice_Action
là một ứng cử viên tốt để trở thành / trở thành một đối tượng giá trị. Nhưng, không thể chắc chắn mà không nhìn thấy mã.
Tôi không thể nói như vậy My_Notice
, nhưng đó chỉ là một phỏng đoán khác.
Nếu tiêm là cần thiết
Trong trường hợp lớp được khởi tạo trong một lớp khác không phải là một trong hai trường hợp trên, điều đó chắc chắn tốt hơn để tiêm nó.
Tuy nhiên, giống như ví dụ trong OP, lớp được xây dựng cần các đối số phụ thuộc vào ngữ cảnh.
Không có câu trả lời "một" trong trường hợp này, nhưng các cách tiếp cận khác nhau có thể hoàn toàn hợp lệ tùy theo trường hợp.
Khởi tạo trong mã máy khách
Một cách tiếp cận đơn giản là tách "mã đối tượng" khỏi "mã máy khách". Trong đó mã khách hàng là mã sử dụng các đối tượng.
Theo cách này, bạn có thể kiểm tra mã đối tượng bằng các kiểm tra đơn vị và để kiểm tra mã máy khách cho các kiểm tra chức năng / tích hợp, trong đó bạn không phải lo lắng về sự cô lập.
Trong trường hợp của bạn, nó sẽ là một cái gì đó như:
add_action( 'activate_plugin', function( $plugin, $network_wide ) {
$message = My_Admin_Notices_Message( 'plugin', 'activated' );
if ( $message->has_text() ) {
$notice = new My_Notice( $plugin, $message, 'wpml-st-string-scan' );
$notice->add_action( new My_Admin_Notice_Action( 'Scan now', '#' ) );
$notice->add_action( new My_Admin_Notice_Action( 'Skip', '#', true ) );
$notices = new My_Notices();
$notices->add_notice( $notice );
$handler = new My_Admin_Notices_Handler( $notices );
$handler->handle_notices();
}
}, 10, 2);
Tôi đã đưa ra một số dự đoán về mã của bạn và viết các phương thức và các lớp có thể không tồn tại (như My_Admin_Notices_Message
), nhưng vấn đề ở đây là việc đóng ở trên có chứa tất cả mã khách hàng cần để khởi tạo và "sử dụng" các đối tượng. Sau đó, bạn có thể kiểm tra các đối tượng của mình một cách cô lập vì không ai trong số các đối tượng đó cần khởi tạo các đối tượng khác, nhưng tất cả chúng đều nhận được các thể hiện cần thiết trong hàm tạo hoặc dưới dạng tham số phương thức.
Đơn giản hóa mã khách hàng với các nhà máy
Cách tiếp cận ở trên có thể hoạt động tốt đối với các plugin nhỏ (hoặc một phần nhỏ của plugin có thể tách biệt với phần còn lại của plugin), nhưng đối với các cơ sở mã lớn hơn, chỉ sử dụng cách tiếp cận đó, bạn có thể kết thúc bằng mã máy khách lớn, trong số đó điều, rất khó để kiểm tra và duy trì.
Trong những trường hợp đó, các nhà máy có thể giúp bạn. Các nhà máy là các đối tượng với phạm vi duy nhất của các đối tượng khác. Hầu hết thời gian là tốt để có các nhà máy cụ thể cho các đối tượng cùng loại (thực hiện cùng một giao diện).
Với các nhà máy, mã ở trên có thể trông như thế này:
add_action( 'activate_plugin', function( $plugin, $network_wide ) {
$notice = $notice_factory->create_for( 'plugin', 'activated' );
if ( $notice instanceof My_Notice_Interface ) {
$handler_factory = new My_Admin_Notices_Handler_Factory();
$handler = $handler_factory->build_for_notices( [ $notice ] );
$handler->handle_notices();
}
}, 10, 2);
Tất cả các mã khởi tạo là trong các nhà máy. Bạn vẫn có thể kiểm tra các nhà máy một cách cô lập, bởi vì bạn cần kiểm tra các đối số phù hợp mà họ tạo ra các lớp dự kiến (hoặc đưa ra các đối số sai mà họ tạo ra các lỗi dự kiến).
Và bạn vẫn có thể kiểm tra tất cả các đối tượng khác một cách cô lập vì không có đối tượng nào cần tạo phiên bản, trong thực tế, mã khởi tạo là tất cả trong các nhà máy.
Tất nhiên, hãy nhớ rằng các đối tượng giá trị không cần nhà máy ... nó sẽ giống như tạo ra các nhà máy cho chuỗi ...
Nếu tôi không thể thay đổi mã thì sao?
Sơ khai
Đôi khi không thể thay đổi mã khởi tạo các đối tượng khác, vì những lý do khác nhau. Ví dụ mã là bên thứ 3, tính tương thích ngược, v.v.
Trong các trường hợp đó, nếu có thể chạy các bài kiểm tra mà không tải các lớp đang được khởi tạo, thì bạn có thể viết một số sơ khai.
Giả sử bạn có một mã làm:
class Foo {
public function run_something() {
$something = new Something();
$something->run();
}
}
Nếu bạn có thể chạy thử nghiệm mà không cần tải Something
lớp, bạn có thể viết một Something
lớp tùy chỉnh chỉ với mục đích thử nghiệm (một "sơ khai").
Luôn luôn tốt hơn để giữ sơ khai rất đơn giản, ví dụ:
class Something{
public function run() {
return 'I ran'.
}
}
Khi các bài kiểm tra chạy, sau đó bạn có thể tải tệp chứa sơ khai này cho Something
lớp (ví dụ từ các bài kiểm tra setUp()
) và khi lớp được kiểm tra sẽ khởi tạo một new Something
, bạn sẽ kiểm tra nó một cách đơn giản, vì thiết lập rất đơn giản và bạn có thể tạo nó trong một cách mà theo thiết kế nó làm những gì bạn mong đợi.
Tất nhiên điều này không đơn giản để duy trì, nhưng xem xét rằng thông thường bạn không kiểm tra đơn vị mã bên thứ 3, hiếm khi bạn cần làm điều này.
Tuy nhiên, đôi khi, điều này hữu ích cho việc thử nghiệm các plugin / mã chủ đề cô lập để khởi tạo các đối tượng WordPress (ví dụ WP_Post
).
Mockery quá tải
Sử dụng Mockery
(một thư viện để cung cấp các công cụ cho các bài kiểm tra đơn vị PHP), bạn thậm chí có thể tránh để viết các sơ khai đó. Với Mockery "dụ mock" (còn gọi là "quá tải") có thể chặn việc tạo cá thể mới và thay thế bằng một giả. Bài viết này giải thích khá tốt làm thế nào để làm điều đó.
Khi lớp được tải ...
Nếu mã để kiểm tra có các phụ thuộc cứng (khởi tạo các lớp đang sử dụng new
) và không có khả năng tải các kiểm tra mà không tải lớp sẽ được khởi tạo thì sẽ có rất ít cơ hội để kiểm tra nó một cách đơn lẻ mà không chạm vào mã (hoặc viết một bản tóm tắt lớp xung quanh nó).
Tuy nhiên, lưu ý rằng tệp bootstrap thử nghiệm thường được tải dưới dạng điều đầu tiên tuyệt đối. Vì vậy, ít nhất, có hai trường hợp trong bạn có thể buộc tải các cuống của bạn:
Mã sử dụng một trình tải tự động. Trong trường hợp này, nếu bạn tải sơ khai trước khi tải trình tải tự động, thì lớp "thực" không bao giờ được tải, bởi vì khi new
được sử dụng, lớp đã được tìm thấy và trình tải tự động không được kích hoạt.
Mã kiểm tra class_exists
trước khi xác định / tải lớp. Trong trường hợp đó để tải các sơ khai như điều đầu tiên, sẽ ngăn lớp "thực" được tải.
Phương án cuối cùng khó khăn
Khi mọi thứ khác thất bại, có một điều khác bạn có thể làm để kiểm tra các phụ thuộc cứng.
Các phụ thuộc rất thường được lưu trữ dưới dạng các biến riêng tư.
class Foo {
public function __construct() {
$this->something = new Something();
}
public function run_something() {
return $this->something->run();
}
}
trong những trường hợp như thế này, bạn có thể thay thế các phụ thuộc cứng bằng mock / stub sau khi thể hiện được tạo.
Điều này bởi vì private
các thuộc tính thậm chí có thể dễ dàng được thay thế trong PHP. Trong nhiều hơn một cách, thực sự.
Không đào sâu vào chi tiết, tôi có thể nói rằng ràng buộc đóng có thể được sử dụng để làm điều đó. Tôi thậm chí đã viết một thư viện gọi là "Andrew" có thể được sử dụng cho phạm vi.
Sử dụng Andrew (và Mockery) để kiểm tra lớp Foo
ở trên, bạn có thể làm:
public function test_run_something() {
$foo = new Foo();
$something_mock = Mockery::mock( 'Something' );
$something_mock
->shouldReceive('run')
->once()
->withNoArgs()
->andReturn('I ran.');
// changing proxy properties will change private properties of proxied object
$proxy = new Andrew\Proxy( $foo );
$proxy->something = $something_mock;
$this->assertSame( 'I ran.', $foo->run_something() );
}