Phương thức xác nhận phpunit được gọi với các đối số

Hành vi của diễn viên là mô phỏng các đối tượng khó thiết lập hoặc tốn thời gian để thiết lập để thử nghiệm. Ví dụ cổ điển là một kết nối cơ sở dữ liệu. Việc thiết lập cơ sở dữ liệu thử nghiệm khi bắt đầu mỗi lần thử nghiệm sẽ làm chậm quá trình thử nghiệm khi thu thập dữ liệu và sẽ yêu cầu cài đặt công cụ cơ sở dữ liệu và dữ liệu thử nghiệm trên máy thử nghiệm. Nếu chúng tôi có thể mô phỏng kết nối và trả về dữ liệu theo lựa chọn của mình, chúng tôi không chỉ giành chiến thắng về tính thực dụng của thử nghiệm mà còn có thể cung cấp dữ liệu giả cho mã của chúng tôi để xem nó phản hồi như thế nào. Chúng tôi có thể mô phỏng cơ sở dữ liệu đang ngừng hoạt động hoặc các trạng thái cực đoan khác mà không cần phải tạo cơ sở dữ liệu bị hỏng thực sự. Nói cách khác, chúng tôi có quyền kiểm soát tốt hơn đối với môi trường thử nghiệm

Nếu các đối tượng giả chỉ hoạt động như các diễn viên, chúng sẽ được gọi đơn giản là sơ khai máy chủ. Đây ban đầu là một mẫu được đặt tên bởi Robert Binder (Thử nghiệm hệ thống hướng đối tượng. mô hình, mẫu và công cụ, Addison-Wesley) vào năm 1999

Sơ khai máy chủ là mô phỏng của một đối tượng hoặc thành phần. Nó sẽ thay thế chính xác một thành phần trong hệ thống cho mục đích thử nghiệm hoặc tạo mẫu, nhưng vẫn nhẹ. Điều này cho phép các bài kiểm tra chạy nhanh hơn hoặc nếu lớp mô phỏng chưa được viết, vẫn chạy được

Tuy nhiên, các đối tượng giả không chỉ đóng một phần (bằng cách cung cấp các giá trị trả về đã chọn theo yêu cầu) mà chúng còn nhạy cảm với các thông báo được gửi tới chúng (thông qua kỳ vọng). Bằng cách thiết lập các tham số dự kiến ​​cho một lệnh gọi phương thức, chúng hoạt động như một người bảo vệ rằng các lệnh gọi chúng được thực hiện chính xác. Nếu kỳ vọng không được đáp ứng, chúng tôi sẽ tiết kiệm công sức viết xác nhận kiểm tra không thành công bằng cách thay mặt chúng tôi thực hiện nghĩa vụ đó

Trong trường hợp kết nối cơ sở dữ liệu tưởng tượng, họ có thể kiểm tra xem truy vấn, chẳng hạn như SQL, được tạo chính xác bởi đối tượng đang sử dụng kết nối. Thiết lập chúng với những kỳ vọng khá chặt chẽ và bạn sẽ hầu như không cần đến các xác nhận thủ công

Tạo các đối tượng giả

Cũng giống như cách chúng ta tạo sơ khai máy chủ, tất cả những gì chúng ta cần là một lớp hiện có, giả sử một kết nối cơ sở dữ liệu giống như thế này

class DatabaseConnection {
    function DatabaseConnection() {
    }
    
    function query() {
    }
    
    function selectQuery() {
    }
}
Lớp chưa cần triển khai. Để tạo một phiên bản giả của lớp, chúng ta cần bao gồm thư viện đối tượng giả và chạy trình tạo.
require_once('simpletest/unit_tester.php');
require_once('simpletest/mock_objects.php');
require_once('database_connection.php');

Mock::generate('DatabaseConnection');
Điều này tạo ra một lớp nhân bản có tên là MockDatabaseConnection. Bây giờ chúng ta có thể tạo các thể hiện của lớp mới trong trường hợp thử nghiệm của mình.
require_once('simpletest/unit_tester.php');
require_once('simpletest/mock_objects.php');
require_once('database_connection.php');

Mock::generate('DatabaseConnection');

class MyTestCase extends UnitTestCase {
    
    function testSomething() {
        $connection = &new MockDatabaseConnection();
    }
}
Không giống như sơ khai được tạo, hàm tạo mô phỏng cần tham chiếu đến trường hợp thử nghiệm để nó có thể gửi các lượt vượt qua và lỗi trong khi kiểm tra các kỳ vọng của nó. Điều này có nghĩa là các đối tượng giả chỉ có thể được sử dụng trong các trường hợp thử nghiệm. Mặc dù vậy, sức mạnh bổ sung của chúng có nghĩa là sơ khai hầu như không được sử dụng nếu có sẵn giả

Giả làm diễn viên

Phiên bản giả của một lớp có tất cả các phương thức của bản gốc, do đó các thao tác như $connection->query() vẫn hợp lệ. Giá trị trả về sẽ là null, nhưng chúng ta có thể thay đổi giá trị đó bằng

$connection->setReturnValue('query', 37)
Bây giờ mỗi khi chúng ta gọi $connection->query() chúng ta sẽ nhận được kết quả là 37. Chúng ta có thể đặt giá trị trả về thành bất kỳ thứ gì, chẳng hạn như hàm băm của kết quả cơ sở dữ liệu tưởng tượng hoặc danh sách các đối tượng liên tục. Các tham số không liên quan ở đây, chúng tôi luôn nhận lại các giá trị giống nhau mỗi khi chúng được thiết lập theo cách này. Điều đó nghe có vẻ không giống như một bản sao thuyết phục của kết nối cơ sở dữ liệu, nhưng đối với nửa tá dòng của phương pháp kiểm tra, đó thường là tất cả những gì bạn cần

Chúng ta cũng có thể thêm các phương thức bổ sung vào mô phỏng khi tạo nó và chọn tên lớp của riêng mình

Mock::generate('DatabaseConnection', 'MyMockDatabaseConnection', array('setOptions'));
Ở đây mô hình sẽ hoạt động như thể setOptions() đã tồn tại trong lớp ban đầu. Điều này rất hữu ích nếu một lớp đã sử dụng cơ chế quá tải () của PHP để thêm các phương thức động. Bạn có thể tạo một mô phỏng đặc biệt để mô phỏng tình huống này

Mọi thứ không phải lúc nào cũng đơn giản như vậy. Một vấn đề phổ biến là các trình vòng lặp, trong đó việc liên tục trả về cùng một giá trị có thể gây ra một vòng lặp vô tận trong đối tượng đang được kiểm tra. Đối với những điều này, chúng ta cần thiết lập các chuỗi giá trị. Giả sử chúng ta có một iterator đơn giản trông như thế này

class Iterator {
    function Iterator() {
    }
    
    function next() {
    }
}
Đây là về iterator đơn giản nhất mà bạn có thể có. Giả sử rằng trình vòng lặp này chỉ trả về văn bản cho đến khi nó kết thúc, khi nó trả về false, chúng ta có thể mô phỏng nó bằng.
Mock::generate('Iterator');

class IteratorTest extends UnitTestCase() {
    
    function testASequence() {
        $iterator = &new MockIterator();
        $iterator->setReturnValue('next', false);
        $iterator->setReturnValueAt(0, 'next', 'First string');
        $iterator->setReturnValueAt(1, 'next', 'Second string');
        ...
    }
}
Khi next() được gọi trên trình lặp mô phỏng, trước tiên nó sẽ trả về "Chuỗi đầu tiên", ở lần gọi thứ hai "Chuỗi thứ hai" sẽ được trả về và trên bất kỳ lệnh gọi nào khác, false sẽ được trả về. Các giá trị trả về theo trình tự được ưu tiên hơn giá trị trả về không đổi. Hằng số là một loại mặc định nếu bạn thích

Một tình huống phức tạp khác là thao tác get() bị quá tải. Một ví dụ về điều này là một người giữ thông tin với các cặp tên/giá trị. Giả sử chúng ta có một lớp cấu hình như

class Configuration {
    function Configuration() {
    }
    
    function getValue($key) {
    }
}
Đây là một tình huống cổ điển khi sử dụng các đối tượng giả vì cấu hình thực tế sẽ thay đổi từ máy này sang máy khác, hầu như không giúp tăng độ tin cậy của các thử nghiệm nếu chúng tôi sử dụng trực tiếp. Tuy nhiên, vấn đề là tất cả dữ liệu đều đến từ phương thức getValue() và chúng tôi muốn các kết quả khác nhau cho các khóa khác nhau. May mắn thay, các mô phỏng có một hệ thống lọc.
$config = &new MockConfiguration();
$config->setReturnValue('getValue', 'primary', array('db_host'));
$config->setReturnValue('getValue', 'admin', array('db_user'));
$config->setReturnValue('getValue', 'secret', array('db_password'));
Tham số bổ sung là danh sách các đối số để cố khớp. Trong trường hợp này, chúng tôi đang cố gắng chỉ khớp một đối số là khóa tra cứu. Bây giờ khi đối tượng giả có phương thức getValue() được gọi như thế này.
$config->getValue('db_user')
. nó sẽ trả về "admin". Nó tìm thấy điều này bằng cách cố gắng khớp các đối số đang gọi với danh sách trả về của nó lần lượt cho đến khi tìm thấy một kết quả khớp hoàn chỉnh

Bạn có thể đặt đối số đối số mặc định như vậy

require_once('simpletest/unit_tester.php');
require_once('simpletest/mock_objects.php');
require_once('database_connection.php');

Mock::generate('DatabaseConnection');
0Điều này không giống với việc đặt giá trị trả về mà không có bất kỳ yêu cầu đối số nào như thế này.
require_once('simpletest/unit_tester.php');
require_once('simpletest/mock_objects.php');
require_once('database_connection.php');

Mock::generate('DatabaseConnection');
1Trong trường hợp đầu tiên, nó sẽ chấp nhận bất kỳ đối số nào, nhưng bắt buộc phải có chính xác một đối số. Trong trường hợp thứ hai, bất kỳ số lượng đối số nào cũng được và nó đóng vai trò là một đối số sau tất cả các đối số khác. Lưu ý rằng nếu chúng ta thêm các tùy chọn tham số đơn khác sau ký tự đại diện trong trường hợp đầu tiên, chúng sẽ bị bỏ qua vì ký tự đại diện sẽ khớp trước. Với các danh sách tham số phức tạp, thứ tự có thể quan trọng nếu không các kết quả phù hợp mong muốn có thể bị che dấu bởi các ký tự đại diện trước đó. Khai báo các kết quả khớp cụ thể nhất trước nếu bạn không chắc chắn

Đôi khi bạn muốn một đối tượng cụ thể được mô phỏng loại bỏ hơn là một bản sao. Ngữ nghĩa sao chép PHP4 buộc chúng tôi phải sử dụng một phương pháp khác cho việc này. Bạn có thể đang mô phỏng một container chẳng hạn

require_once('simpletest/unit_tester.php');
require_once('simpletest/mock_objects.php');
require_once('database_connection.php');

Mock::generate('DatabaseConnection');
2Trong trường hợp này, bạn có thể đặt tham chiếu vào danh sách trả về của mô hình.
require_once('simpletest/unit_tester.php');
require_once('simpletest/mock_objects.php');
require_once('database_connection.php');

Mock::generate('DatabaseConnection');
3Với sự sắp xếp này, bạn biết rằng mỗi lần $vector->get(12) được gọi, nó sẽ trả về cùng một $thing mỗi lần. Điều này cũng tương thích với PHP5

Ba yếu tố này, thời gian, tham số và liệu có sao chép hay không, có thể được kết hợp trực giao. Ví dụ

require_once('simpletest/unit_tester.php');
require_once('simpletest/mock_objects.php');
require_once('database_connection.php');

Mock::generate('DatabaseConnection');
4Điều này sẽ chỉ trả về $stuff trong lần gọi thứ ba và chỉ khi hai tham số được đặt, tham số thứ hai phải là số nguyên 1. Điều đó sẽ bao gồm hầu hết các tình huống tạo mẫu đơn giản

Trường hợp phức tạp cuối cùng là một đối tượng tạo ra một đối tượng khác, được gọi là mẫu xuất xưởng. Giả sử rằng trên một truy vấn thành công tới cơ sở dữ liệu tưởng tượng của chúng ta, một tập kết quả được trả về dưới dạng một trình vòng lặp với mỗi lệnh gọi next() đưa ra một hàng cho đến khi sai. Điều này nghe giống như một cơn ác mộng mô phỏng, nhưng trên thực tế, tất cả đều có thể bị chế giễu bằng cách sử dụng các cơ chế ở trên

Đây là cách

require_once('simpletest/unit_tester.php');
require_once('simpletest/mock_objects.php');
require_once('database_connection.php');

Mock::generate('DatabaseConnection');
5Bây giờ chỉ khi kết nối $ của chúng tôi được gọi với truy vấn chính xác () thì kết quả $ sẽ được trả về mà chính nó đã hết sau lần gọi thứ ba tới next(). Đây phải là thông tin đủ cho lớp UserFinder của chúng tôi, lớp thực sự đang được thử nghiệm ở đây, để đưa ra hàng hóa. Một bài kiểm tra rất chính xác và không phải là cơ sở dữ liệu thực trong tầm nhìn

Chế giễu như những nhà phê bình

Mặc dù cách tiếp cận sơ khai máy chủ bảo vệ các bài kiểm tra của bạn khỏi sự gián đoạn trong thế giới thực, nhưng nó chỉ mang lại một nửa lợi ích. Bạn có thể yêu cầu lớp đang kiểm tra nhận các tin nhắn được yêu cầu, nhưng lớp mới của bạn có gửi đúng tin nhắn không?

Ví dụ, giả sử chúng ta có một lớp SessionPool mà chúng ta muốn thêm ghi nhật ký vào. Thay vì phát triển lớp ban đầu thành một thứ gì đó phức tạp hơn, chúng tôi muốn thêm hành vi này bằng một trình trang trí (GOF). Mã SessionPool hiện trông như thế này

require_once('simpletest/unit_tester.php');
require_once('simpletest/mock_objects.php');
require_once('database_connection.php');

Mock::generate('DatabaseConnection');
6Trong khi mã đăng nhập của chúng tôi trông như thế này.
require_once('simpletest/unit_tester.php');
require_once('simpletest/mock_objects.php');
require_once('database_connection.php');

Mock::generate('DatabaseConnection');
7Trong số tất cả những thứ này, lớp duy nhất chúng tôi muốn kiểm tra ở đây là LoggingSessionPool. Cụ thể, chúng tôi muốn kiểm tra xem phương thức findSession() có được gọi với ID phiên chính xác trong cookie hay không và phương thức này đã gửi thông báo "Đang bắt đầu phiên $cookie" tới bộ ghi nhật ký

Mặc dù thực tế là chúng tôi chỉ kiểm tra một vài dòng mã sản xuất, đây là những gì chúng tôi sẽ phải làm trong một trường hợp thử nghiệm thông thường

  1. Tạo một đối tượng nhật ký
  2. Đặt một thư mục để đặt tệp nhật ký
  3. Đặt quyền thư mục để chúng tôi có thể ghi nhật ký
  4. Tạo một đối tượng SessionPool
  5. Tự tay bắt đầu một phiên, có thể làm được nhiều việc
  6. Gọi findSession()
  7. Đọc ID phiên mới (hy vọng có một người truy cập. )
  8. Đưa ra xác nhận thử nghiệm để xác nhận rằng ID khớp với cookie
  9. Đọc dòng cuối cùng của tệp nhật ký
  10. Mẫu phù hợp với dấu thời gian ghi nhật ký bổ sung, v.v.
  11. Khẳng định rằng thông báo phiên được chứa trong văn bản
Không có gì đáng ngạc nhiên khi các nhà phát triển ghét viết bài kiểm tra khi chúng cực nhọc như vậy. Tệ hơn nữa, mỗi khi định dạng ghi nhật ký thay đổi hoặc phương pháp tạo phiên mới thay đổi, chúng tôi phải viết lại các phần của bài kiểm tra này mặc dù bài kiểm tra này không chính thức kiểm tra các phần đó của hệ thống. Chúng tôi đang làm đau đầu các nhà văn của các lớp khác này

Thay vào đó, đây là phương pháp kiểm tra hoàn chỉnh bằng cách sử dụng ma thuật đối tượng giả

require_once('simpletest/unit_tester.php');
require_once('simpletest/mock_objects.php');
require_once('database_connection.php');

Mock::generate('DatabaseConnection');
8Chúng tôi bắt đầu bằng cách tạo một phiên giả. Chúng tôi không cần phải quá cầu kỳ về điều này vì việc kiểm tra phiên nào chúng tôi muốn được thực hiện ở nơi khác. Chúng tôi chỉ cần kiểm tra xem đó có phải là phiên bản đến từ nhóm phiên không

findSession() là một phương thức xuất xưởng mô phỏng được mô tả. Điểm khởi hành đi kèm với lời gọi đầu tiên kỳ vọng(). Dòng này nói rằng bất cứ khi nào findSession() được gọi trên mô hình giả, nó sẽ kiểm tra các đối số đến. Nếu nó nhận được một đối số duy nhất của một chuỗi "abc" thì một bài kiểm tra sẽ được gửi đến bộ kiểm tra đơn vị, nếu không thì một lỗi sẽ được tạo. Đây là phần mà chúng tôi đã kiểm tra xem phiên phù hợp đã được yêu cầu chưa. Danh sách đối số tuân theo định dạng giống như danh sách để đặt giá trị trả về. Bạn có thể có các ký tự đại diện và trình tự và thứ tự đánh giá là như nhau

Chúng tôi sử dụng cùng một mẫu để thiết lập bộ ghi giả. Chúng tôi nói với nó rằng nó nên có message() được gọi một lần duy nhất với đối số "Bắt đầu phiên abc". Bằng cách kiểm tra các đối số gọi, thay vì đầu ra của bộ ghi, chúng tôi cách ly kiểm tra khỏi bất kỳ thay đổi hiển thị nào trong bộ ghi

Chúng tôi bắt đầu chạy thử nghiệm khi chúng tôi tạo LoggingSessionPool mới và cung cấp cho nó các đối tượng giả định sẵn của chúng tôi. Mọi thứ bây giờ nằm ​​trong tầm kiểm soát của chúng tôi

Đây vẫn là một đoạn mã kiểm tra khá ít, nhưng mã rất nghiêm ngặt. Nếu nó vẫn có vẻ khá khó khăn thì sẽ ít hơn rất nhiều so với việc chúng tôi thử điều này mà không có mô phỏng và thử nghiệm cụ thể này, các tương tác thay vì đầu ra, luôn cần nhiều công việc hơn để thiết lập. Thường xuyên hơn, bạn sẽ kiểm tra các tình huống phức tạp hơn mà không cần mức độ hoặc độ chính xác này. Ngoài ra, một số trong số này có thể được tái cấu trúc thành phương thức setUp() trong trường hợp thử nghiệm

Đây là danh sách đầy đủ các kỳ vọng bạn có thể đặt trên một đối tượng giả trong SimpleTest

Kỳ vọngCần tally()expect($method, $args)NoexpectAt($timing, $method, $args)NoexpectCallCount($method, $count)YesexpectMaximumCallCount($method, $count)NoexpectMinimumCallCount($method, $count)YesexpectNever($ . $methodTên phương thức, dưới dạng một chuỗi, để áp dụng điều kiện cho. $argsCác đối số dưới dạng danh sách. Các ký tự đại diện có thể được bao gồm theo cách tương tự như đối với setReturn(). Đối số này là tùy chọn cho kỳ vọngOnce() và kỳ vọngAtLeastOnce(). $timingThời điểm duy nhất để kiểm tra điều kiện. Cuộc gọi đầu tiên bắt đầu từ số không. $count Số lượng cuộc gọi dự kiến. Phương thức kỳ vọngMaximumCallCount() hơi khác một chút ở chỗ nó sẽ chỉ tạo ra lỗi. Nó im lặng nếu không bao giờ đạt đến giới hạn

Ngoài ra, nếu bạn chỉ có một cuộc gọi trong thử nghiệm của mình, hãy đảm bảo rằng bạn đang sử dụng kỳ vọng
Sử dụng $mocked->expectAt(0, 'method', 'args); . kiểm tra các đối số và tổng số ô hiện đang độc lập

Giống như các xác nhận trong các trường hợp thử nghiệm, tất cả các kỳ vọng có thể ghi đè thông báo làm tham số bổ sung. Ngoài ra, thông báo lỗi ban đầu có thể được nhúng trong đầu ra dưới dạng "%s"

cách tiếp cận khác

Có ba cách tiếp cận để tạo mô hình giả bao gồm cả cách mà SimpleTest sử dụng. Mã hóa chúng bằng tay bằng cách sử dụng một lớp cơ sở, tạo chúng thành một tệp và tự động tạo chúng một cách nhanh chóng

Các đối tượng giả được tạo bằng SimpleTest là động. Chúng được tạo trong thời gian chạy trong bộ nhớ, sử dụng eval(), thay vì ghi ra tệp. Điều này làm cho các mô hình giả dễ dàng tạo ra, một lớp lót, đặc biệt là so với việc tạo thủ công chúng trong hệ thống phân cấp lớp song song. Vấn đề là hành vi thường được thiết lập trong các bài kiểm tra. Nếu các đối tượng ban đầu thay đổi các phiên bản mô phỏng mà các bài kiểm tra dựa vào có thể không đồng bộ. Điều này cũng có thể xảy ra với cách tiếp cận phân cấp song song, nhưng được phát hiện nhanh hơn nhiều

Tất nhiên, giải pháp là thêm một số thử nghiệm tích hợp thực tế. Bạn không cần quá nhiều và sự tiện lợi thu được từ các bản mô phỏng vượt xa số lượng thử nghiệm bổ sung nhỏ. Bạn không thể tin tưởng mã chỉ được thử nghiệm với mô hình giả

Nếu bạn vẫn quyết tâm xây dựng các thư viện mô phỏng tĩnh vì bạn muốn mô phỏng hành vi rất cụ thể, bạn có thể đạt được hiệu quả tương tự bằng cách sử dụng trình tạo lớp SimpleTest. Trong tệp thư viện của bạn, giả sử mocks/connection. php để kết nối cơ sở dữ liệu, tạo mô hình giả và kế thừa để ghi đè các phương thức đặc biệt hoặc thêm giá trị đặt trước

require_once('simpletest/unit_tester.php');
require_once('simpletest/mock_objects.php');
require_once('database_connection.php');

Mock::generate('DatabaseConnection');
9Cuộc gọi tạo yêu cầu trình tạo lớp tạo một lớp có tên là BasicMockConnection thay vì MockConnection thông thường. Sau đó, chúng tôi kế thừa từ điều này để có phiên bản MockConnection của chúng tôi. Bằng cách chặn theo cách này, chúng tôi có thể thêm hành vi, ở đây đặt giá trị mặc định của query() thành false. Bằng cách sử dụng tên mặc định, chúng tôi đảm bảo rằng trình tạo lớp giả sẽ không tạo lại một tên khác khi được gọi ở nơi khác trong các bài kiểm tra. Nó không bao giờ tạo một lớp nếu nó đã tồn tại. Miễn là tệp trên được đưa vào trước thì tất cả các thử nghiệm đã tạo MockConnection bây giờ sẽ sử dụng tệp của chúng tôi để thay thế. Nếu chúng ta không sắp xếp đúng thứ tự và thư viện giả tạo trước thì việc tạo lớp sẽ thất bại

Sử dụng thủ thuật này nếu bạn thấy mình có nhiều hành vi giả định phổ biến hoặc bạn thường xuyên gặp sự cố tích hợp ở các giai đoạn thử nghiệm sau này

Làm cách nào để thử một phương thức trong PHPUnit?

PHPUnit cung cấp các phương thức được sử dụng để tự động tạo các đối tượng sẽ thay thế đối tượng ban đầu trong thử nghiệm của chúng tôi. Các phương thức createMock($type) và getMockBuilder($type) được sử dụng để tạo đối tượng giả . Phương thức createMock ngay lập tức trả về một đối tượng giả của loại đã chỉ định.

Khẳng định trong PHPUnit là gì?

Hàm assertSame() PHPUnit . Xác nhận này sẽ trả về true trong trường hợp nếu giá trị mong đợi giống với giá trị thực tế, ngược lại trả về false. used to assert whether the actually obtained value is the same as the expected value or not. This assertion will return true in the case if the expected value is the same as the actual value else returns false.

PHPUnit có phải là một khuôn khổ không?

PHPUnit là khung thử nghiệm dành cho lập trình viên dành cho PHP . Nó là một ví dụ về kiến ​​trúc xUnit cho các khung kiểm tra đơn vị. PHPUnit 9 là phiên bản ổn định hiện tại. PHPUnit 10 hiện đang được phát triển.