Как тестировать звонки в Google API - PullRequest
6 голосов
/ 31 января 2011

У меня есть следующий метод, который извлекает самые посещаемые страницы из Google Analytics:

public function getData($limit = 10)
{
    $ids = '12345';
    $dateFrom = '2011-01-01';
    $dateTo = date('Y-m-d');

    // Google Analytics credentials
    $mail = 'my_mail';
    $pass = 'my_pass';

    $clientLogin = Zend_Gdata_ClientLogin::getHttpClient($mail, $pass, "analytics");
    $client = new Zend_Gdata($clientLogin);

    $reportURL = 'https://www.google.com/analytics/feeds/data?';

    $params = array(
        'ids' => 'ga:' . $ids,
        'dimensions' => 'ga:pagePath,ga:pageTitle',
        'metrics' => 'ga:visitors',
        'sort' => '-ga:visitors',
        'start-date' => $dateFrom,
        'end-date' => $dateTo,
        'max-results' => $limit
    );

    $query = http_build_query($params, '');
    $reportURL .= $query;

    $results = $client->getFeed($reportURL);

    $xml = $results->getXML();
    Zend_Feed::lookupNamespace('default');
    $feed = new Zend_Feed_Atom(null, $xml);

    $top = array();
    foreach ($feed as $entry) {
        $page['visitors'] = (int) $entry->metric->getDOM()->getAttribute('value');
        $page['url'] = $entry->dimension[0]->getDOM()->getAttribute('value');
        $page['title'] = $entry->dimension[1]->getDOM()->getAttribute('value');
        $top[] = $page;
    }

    return $top;
}

Конечно, требуется некоторый рефакторинг, но вопрос:

  • Как бывы пишете тесты PHPUnit для этого метода?

Ответы [ 3 ]

6 голосов
/ 31 января 2011

Насколько я понимаю, обычно вы хотите внедрить зависимость (объект клиента Google) в тестируемую систему (SUT, класс, содержащий метод getData()).

Я всегда вижуЭксперты используют инжекцию в конструктор - и я уверен, что это лучший подход, поскольку он четко определяет зависимости сразу.Но, по правде говоря, я никогда не смогу спроектировать свои объекты достаточно хорошо, чтобы всегда делать эту работу.Так что я заканчиваю делать инъекцию сеттера.

Примерно так:

public function getClient()
{
    if (null === $this->_client){
        // $mail and $pass are stored somewhere, right?
        $clientLogin = Zend_Gdata_ClientLogin::getHttpClient($mail, $pass, "analytics");
        $this->_client = new Zend_Gdata($clientLogin);
    }
    return $this->_client;
}

public function setClient($client)
{
    $this->_client = $client;
    return $this;
}

Затем в модульном тесте вы создаете объект $client как макет вашего живого $client, устанавливаете ожидания и затем вводитеэто в ваше SUT, используя setClient($client) метод, описанный выше.

Понимаете, что я имею в виду?

2 голосов
/ 04 февраля 2011

Дэвид Вайнрауб дал вам первую половину (как настроить ваш класс так, чтобы он был насмешливым), поэтому я расскажу о второй половине (как построить макет).

PHPUnit предоставляет отличное средство для насмешки с простым API. Передача имени пользователя и пароля слишком проста для тестирования в моей книге, поэтому я просто высмеиваю обработку запроса и результатов. Для этого требуются макеты для Zend_Gdata и Zend_Gdata_App_Feed.

public function testGetData() {
    // expected input to and output from mocks
    $url = 'https://www.google.com/analytics/feeds/data?ids=ga:12345...';
    $xml = <<<XML
<feed>
    ...
</feed>
XML;
    // setup the mocks and method expectations
    $client = $this->getMock('Zend_Gdata', array('getFeed'));
    $feed = $this->getMock('Zend_Gdata_App_Feed', array('getXML'));
    $client->expects($this->once())
           ->method('getFeed')
           ->with($url)
           ->will($this->returnValue($feed));
    $feed->expects($this->once())
         ->method('getXML')
         ->will($this->returnValue($xml));
    // create the report (SUT) and call the method being tested
    $report = new MyReport();
    $report->setClient($client);
    $top = $report->getData();
    // check the final output; mocks are verified automatically
    $this->assertEquals(10, count($top));
    $this->assertEquals(array(
            'visitors' => 123, 
            'url' => 'http://...', 
            'title' => 'My Home Page'
        ), $top[0]);
}

Выше будет проверено, что URL был правильным и вернет XML-фид, ожидаемый от Google. Удаляет всю зависимость от классов Zend_Gdata. Если вы не используете подсказки типов в setClient (), вы даже можете использовать stdClass в качестве основы для двух макетов, поскольку вы будете использовать только фиктивные методы.

0 голосов
/ 31 января 2011

Я хочу сказать вам, что эта единственная функция getData - один из самых неприятных и уродливых фрагментов кода.Вы спрашиваете, как проверить это.Ну, угадайте, что моя рекомендация будет?Refactor.

Для рефакторинга этого кода вам потребуется тест покрытия .

Причин для рефакторинга множество:

  1. Зависимость от стороннего фреймворка.
  2. Зависимость от внешнего сервиса.
  3. getData имеет слишком много откликов.

    a.Войдите во внешнюю службу, используя стороннюю платформу.

    b.Создайте запрос для внешней службы.

    c.Разберите ответ на запрос от внешней службы.

Как вы изолировали свой код от изменений в сторонней платформе и от внешней службы?

Вы действительно должны взглянутьв книге Майкла Пера. Эффективная работа с устаревшим кодом

[EDIT]

Моя точка зрения на вас (спойлер идет), это то, что сэтот код вы никогда не сможете получить настоящий модульный тест.Это из-за зависимости от внешнего сервиса.Модульный тест не имеет контроля над сервисом или данными, которые он возвращает.Модульный тест должен быть в состоянии выполнить так, чтобы каждый раз, когда он выполняет, его результат был последовательным.С внешним сервисом это может быть не так. У ВАС НЕТ КОНТРОЛЯ ЗА ЧТО ВОЗВРАЩАЕТСЯ ВНЕШНИЙ СЕРВИС.

Что вы делаете, если сервис не работает?Модульный тест FAIL .

Что делать, если возвращаются результаты изменений?Модульный тест FAIL .

Результаты модульных тестов должны оставаться согласованными от выполнения к выполнению.В противном случае это не модульный тест.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...