Ответ 1
Два подхода, которые приходят на ум:
-
Создайте два адаптера для вашего класса FTP:
- "Реальный", который использует PHP ftp-функции для подключения к удаленному серверу и т.д.
-
"макет", который фактически не подключается к чему-либо и возвращает только семенные данные.
Метод FTP
connect()
выглядит следующим образом:public function connect($name, $opt=array()) { return $this->getAdapter()->connect($name, $opt); }
Макет адаптера может выглядеть примерно так:
class FTPMockAdapter implements IFTPAdapter { protected $_seeded = array(); public function connect($name, $opt=array()) { return $this->_seeded['connect'][serialize(compact('name', 'opt'))]; } public function seed($data, $method, $opt) { $this->_seeded[$method][serialize($opt)] = $data; } }
В вашем тесте вы затем высеваете адаптер с результатом и убедитесь, что
connect()
получает вызов соответствующим образом:public function setUp( ) { $this->_adapter = new FTPMockAdapter(); $this->_fixture->setAdapter($this->_adapter); } /** This test is worthless for testing the FTP class, as it * basically only tests the mock adapter, but hopefully * it at least illustrates the idea at work. */ public function testConnect( ) { $name = '...'; $opt = array(...); $success = true // Seed the connection response to the adapter. $this->_adapter->seed($success, 'connect', compact('name', 'opt')); // Invoke the fixture connect() method and make sure it invokes the // adapter properly. $this->assertEquals($success, $this->_fixture->connect($name, $opt), 'Expected connect() to connect to correct server.' ); }
В приведенном выше тестовом примере
setUp()
вводит макет адаптера, чтобы тесты могли вызвать метод классаconnect()
для FTP, фактически не инициировав FTP-соединение. Затем тест семя адаптера получает результат, который будет возвращен только в том случае, если был вызван метод адаптераconnect()
с правильными параметрами.Преимущество этого метода заключается в том, что вы можете настраивать поведение макетного объекта и сохранять весь код в одном месте, если вам нужно использовать макет в нескольких тестовых случаях.
Недостатки этого метода заключаются в том, что вы должны дублировать много функциональных возможностей, которые уже были построены (см. подход № 2), и, возможно, вы теперь представили еще один класс, для которого вы должны писать тесты.
-
Альтернативой является использование PHPUnit mocking framework для создания динамических макетных объектов в вашем тесте. Вам все равно придется вставлять адаптер, но вы можете создать его на лету:
public function setUp( ) { $this->_adapter = $this->getMock('FTPAdapter'); $this->_fixture->setAdapter($this->_adapter); } public function testConnect( ) { $name = '...'; $opt = array(...); $this->_adapter ->expects($this->once()) ->method('connect') ->with($this->equalTo($name), $this->equalTo($opt)) ->will($this->returnValue(true)); $this->assertTrue($this->_fixture->connect($name, $opt), 'Expected connect() to connect to correct server.' ); }
Обратите внимание, что приведенный выше тест издевается над адаптером для класса FTP, а не с самим классом FTP, поскольку это было бы довольно глупой задачей.
Этот подход имеет преимущества перед предыдущим подходом:
- Вы не создаете никаких новых классов, а PHPUnit mocking framework имеет собственное тестовое покрытие, поэтому вам не нужно писать тесты для класса mock.
- Тест действует как документация для того, что происходит "под капотом" (хотя некоторые могут утверждать, что это не очень хорошо).
Однако существуют некоторые недостатки этого подхода:
- Он довольно медленный (с точки зрения производительности) по сравнению с предыдущим подходом.
- Вы должны написать много дополнительного кода в каждом тесте, который использует макет (хотя вы можете реорганизовать общие операции, чтобы смягчить некоторые из них).
Подробнее см. http://phpunit.de/manual/current/en/test-doubles.html#test-doubles.mock-objects.