модульное тестирование хранилищ данных в PHP - PullRequest
1 голос
/ 21 июля 2009

Я использую PHPUnit, но мне трудно заставить его создавать хорошие макеты и заглушки для объектов, используемых в качестве хранилища данных.

Пример:

class urlDisplayer {
    private $storage;
    public function __construct(IUrlStorage $storage) { $this->storage = $storage; }
    public function displayUrl($name) {}
    public function displayLatestUrls($count) {}
}

interface IUrlStorage {
    public function addUrl($name, $url);
    public function getUrl($name);
}

class MysqlUrlStorage implements IUrlStorage {
    // saves and retrieves from database
}

class NonPersistentStorage implements IUrlStorage {
    // just stores for this request
}

Например, как сделать так, чтобы заглушки PHPUnit возвращали более одного возможного значения при двух вызовах с разными именами $?

Редактировать: пример теста:

public function testUrlDisplayerDisplaysLatestUrls {
    // get mock storage and have it return latest x urls so I can test whether
    // UrlDisplayer really shows the latest x
}

В этом тесте макет должен возвращать количество URL, однако в документации я только о том, как вернуть одно значение.

1 Ответ

1 голос
/ 20 ноября 2009

Ваш вопрос не очень понятен - но я предполагаю, что вы спрашиваете, как использовать фиктивные объекты phpunit для возврата другого значения в разных ситуациях?

Ложные классы PHPUnit позволяют указывать пользовательскую функцию (т.е. функцию / метод обратного вызова), которая практически не ограничена в своих возможностях.

В приведенном ниже примере я создал фиктивный класс IUrlStorage, который будет возвращать следующий URL в своем хранилище каждый раз, когда он вызывается.

public function setUp()
{
    parent::setUp();
    $this->fixture = new UrlDisplayer(); //change this to however you create your object

    //Create a list of expected URLs for testing across all test cases
    $this->expectedUrls = array(
          'key1' => 'http://www.example.com/url1/'
        , 'key2' => 'http://www.example.net/url2/'
        , 'key3' => 'http://www.example.com/url3/'
    );
}

public function testUrlDisplayerDisplaysLatestUrls {
    //Init        
    $mockStorage = $this->getMock('IUrlStorage');
    $mockStorage->expects($this->any())
        ->method('getUrl')
        ->will( $this->returnCallback(array($this, 'mockgetUrl')) );

    reset($this->expectedUrls); //reset array before testing

    //Actual Tests
    $this->assertGreaterThan(0, count($this->expectedUrls));
    foreach ( $this->expectedUrls as $key => $expected ) {
        $actual = $this->fixture->displayUrl($key);
        $this->assertEquals($expected, $actual);
    }
}

public function mockGetUrl($name)
{
    $value = current($this->expectedUrls);
    next($this->expectedUrls);

    //Return null instead of false when end of array is reached
    return ($value === false) ? null : $value;
}

В качестве альтернативы, иногда проще просто создать реальный класс, который копирует необходимую функциональность. Это особенно легко с хорошо определенными и маленькими интерфейсами.

В этом конкретном случае я бы предложил использовать следующий класс:

class MockStorage implements IUrlStorage
{
    protected $urls = array();

    public function addUrl($name, $url)
    {
        $this->urls[$name] = $url;
    }

    public function getUrl($name)
    {
        if ( isset($this->urls[$name]) ) {
            return $this->urls[$name];
        }
        return null;
    }
}
?>

Затем в классе модульных тестов вы просто создаете экземпляр своего прибора, как показано ниже:

public function setUp() {
   $mockStorage = new MockStorage();

   //Add as many expected URLs you want to test for
   $mockStorage->addUrl('name1', 'http://example.com');
   //etc...

   $this->fixture = new UrlDisplayer($mockStorage);
}
...