Как использовать PHPUnit с CodeIgniter?


Я читал и читал статьи о PHPUnit, SimpleTest и других системах модульного тестирования. Они все звучат так здорово! Я, наконец, получил PHPUnit работает с Codeigniter благодаряhttps://bitbucket.org/kenjis/my-ciunit/overview

теперь мой вопрос, как я могу его использовать?

каждый учебник, который я вижу, имеет некоторое абстрактное использование, например assertEquals(2, 1+1) или:

public function testSpeakWithParams()
{
    $hello = new SayHello('Marco');
    $this->assertEquals("Hello Marco!", $hello->speak());
}

это здорово, если бы у меня была функция, которая выводила бы такую предсказуемую строку. Обычно мой приложения захватывают кучу данных из базы данных, а затем отображают их в какой-то таблице. Итак, как я могу проверить контроллеры Codeigniter?

Я хотел бы сделать тестовую разработку, и я прочитал учебник на сайте PHPUnits, но еще раз пример кажется таким абстрактным. Большинство моих функций codeigniter отображают данные.

есть ли книга или отличный учебник с практическим применением и примерами тестирования PHPUnit?

2 56

2 ответа:

похоже, вы понимаете основную структуру/синтаксис того, как писать тесты и модульное тестирование кода CodeIgniter не должен отличаться от тестирования кода без CI, поэтому я хочу сосредоточиться на ваших основных проблемах/проблемах ...

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

утверждения тестирования кажутся абстрактными, потому что большинство ваших методов/функций, вероятно, выполняют несколько различных дискретных задач. Успешное тестирование менталитета требует изменения в том, как вы думаете о своем коде. Вы должны перестать определять успех с точки зрения "работает?"Вместо этого вы должны спросить:" работает ли он, будет ли он хорошо играть с другим кодом, есть он разработан таким образом, что делает его полезным в других приложениях, и я могу проверить, что он работает?"

например, ниже приведен упрощенный пример того, как вы, наверное, писали код до этого момента:

function parse_remote_page_txt($type = 'index')
{
  $remote_file = ConfigSingleton::$config_remote_site . "$type.php";
  $local_file  = ConfigSingleton::$config_save_path;

  if ($txt = file_get_contents($remote_file)) {
    if ($values_i_want_to_save = preg_match('//', $text)) {
      if (file_exists($local_file)) {
        $fh = fopen($local_file, 'w+');
        fwrite($fh, $values_i_want_to_save);
        fclose($fh);
        return TRUE;
      } else {
        return FALSE;
      }
  } else {
    return FALSE;
  }  
}

именно то, что здесь происходит, не важно. Я пытаюсь проиллюстрировать, почему этот код трудно проверить:

  • он использует одноэлементный класс конфигурации для генерации значений. Успех вашей функции зависит от значения Синглтон, и как вы можете проверить, что эта функция работает правильно в полной изоляции, когда вы не можете создать экземпляр новых объектов конфигурации с разными значениями? Лучшим вариантом может быть передача вашей функции a $config аргумент, состоящий из объекта конфигурации или массива, значениями которого можно управлять. Это в широком смысле называется"Инъекции Зависимостей

  • обратите внимание на вложенную IF заявления. Тестирование означает, что вы покрываете каждую исполняемую строку каким-то тестом. При вложении операторов IF вы создаете новые ветви кода, которые требуют нового тестового пути.

  • наконец, вы видите, как эта функция, хотя она, кажется, делает одну вещь (разбор содержимого удаленного файла) на самом деле выполняет несколько задач? Если вы рьяно отделяете свои проблемы, ваш код становится бесконечно более проверяемым. Гораздо более проверяемый способ сделать это же так и будет ...


class RemoteParser() {
  protected $local_path;
  protected $remote_path;
  protected $config;

  /**
   * Class constructor -- forces injection of $config object
   * @param ConfigObj $config
   */
  public function __construct(ConfigObj $config) {
    $this->config = $config;
  }

  /**
   * Setter for local_path property
   * @param string $filename
   */
  public function set_local_path($filename) {
    $file = filter_var($filename);
    $this->local_path = $this->config->local_path . "/$file.html";
  }

  /**
   * Setter for remote_path property
   * @param string $filename
   */
  public function set_remote_path($filename) {
    $file = filter_var($filename);
    $this->remote_path = $this->config->remote_site . "/$file.html";
  }

  /**
   * Retrieve the remote source
   * @return string Remote source text
   */
  public function get_remote_path_src() {
    if ( ! $this->remote_path) {
      throw new Exception("you didn't set the remote file yet!");
    }
    if ( ! $this->local_path) {
      throw new Exception("you didn't set the local file yet!");
    }
    if ( ! $remote_src = file_get_contents($this->remote_path)) {
      throw new Exception("we had a problem getting the remote file!");
    }

    return $remote_src;
  }

  /**
   * Parse a source string for the values we want
   * @param string $src
   * @return mixed Values array on success or bool(FALSE) on failure
   */
  public function parse_remote_src($src='') {
    $src = filter_validate($src);
    if (stristr($src, 'value_we_want_to_find')) {
      return array('val1', 'val2');
    } else {
      return FALSE;
    }
  }

  /**
   * Getter for remote file path property
   * @return string Remote path
   */
  public function get_remote_path() {
    return $this->remote_path;
  }

  /**
   * Getter for local file path property
   * @return string Local path
   */
  public function get_local_path() {
    return $this->local_path;
  }
}

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

IMHO, чем больше вы вникаете в тестирование, тем больше вы понимаете, что это больше о хорошем дизайне кода и разумной архитектуре, чем просто создание конечно, все работает так, как ожидалось. И вот здесь преимущества ООП действительно начинают сиять. Вы можете протестировать процедурный код просто отлично, но для большого проекта с взаимозависимыми частями тестирование имеет способ обеспечения хорошего дизайна. Я знаю, что это может быть приманкой троллей для некоторых процедурных людей, но хорошо.

чем больше вы тестируете, тем больше вы будете писать код и спрашивать себя: "Смогу ли я это проверить?"А если нет, то вы, вероятно, измените структуру тогда и там.

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

чем больше я тестирую, тем больше я понимаю, что если у меня есть трудное время тестирования чего-то это, скорее всего, потому что у меня есть основная проблема дизайна. Если я выпрямлюсь это обычно приводит ко всем зеленым полосам в моих результатах теста.

наконец, вот несколько ссылок, которые действительно помогли мне начать думать в тестовом режиме. Первый - это щекотливый список того, что не нужно делать, если вы хотите написать тестируемый код. На самом деле, если вы просмотрите весь этот сайт, вы найдете много полезных вещей, которые помогут вам установить путь к 100% охвату кода. Еще одна полезная статья это обсуждение зависимостей инъекция.

удачи!

я безуспешно пытался использовать PHPUnit с Codeigniter. Например, если бы я хотел проверить свои модели CI, я столкнулся с проблемой того, как я получу экземпляр этой модели, поскольку ему каким-то образом нужна вся структура CI для ее загрузки. Рассмотрим, как вы загружаете модель, например:

$this->load->model("domain_model");

проблема в том, что если вы посмотрите на суперкласс для метода загрузки, вы его не найдете. Это не так просто, если вы тестируете простые старые объекты PHP, где вы можете издеваться над своими зависимости легко и проверить функциональность.

следовательно, я согласился на класс модульного тестирования CI.

my apps grab a bunch of data from the database then display it in some sort of table.

если вы тестируете свои контроллеры, вы по существу тестируете бизнес-логику (если у вас есть), а также SQL-запрос, который "захватывает кучу данных" из базы данных. Это уже интеграционное тестирование.

лучший способ-это сначала проверить модель CI, чтобы проверить захват данных - - - это будет полезно, если у вас есть очень сложный запрос - и затем контроллер рядом с тестом бизнес-логики, которая применяется к данным, захваченным моделью CI. это хорошая практика, чтобы проверить только одну вещь одновременно. Так что же вы будете тестировать? Запрос или бизнес-логика?

Я предполагаю, что вы хотите сначала проверить захват данных, общие шаги

  1. получить некоторые тестовые данные и настройки вашей базы данных, таблиц и т. д.

  2. есть некоторый механизм для заполнения базы данных с тестовыми данными, а также удалить его после теста. расширение базы данных PHPUnit есть способ сделать это, хотя я не знаю, поддерживается ли это рамками, которые вы опубликовали. Дайте нам знать.

  3. напишите свой тест, пройдите его.

метод тестирования может выглядеть следующим образом:

// At this point database has already been populated
public function testGetSomethingFromDB() {
    $something_model = $this->load->model("domain_model");
    $results = $something_model->getSomethings();
    $this->assertEquals(array(
       "item1","item2"), $results);

}
// After test is run database is truncated. 

на всякий случай вы хотите использовать модульное тестирование CI класс, вот модифицированный фрагмент кода одного теста, который я написал с его помощью:

class User extends CI_Controller {
    function __construct() {
        parent::__construct(false);
        $this->load->model("user_model");
        $this->load->library("unit_test");
    }

public function testGetZone() {
            // POPULATE DATA FIRST
    $user1 = array(
        'user_no' => 11,
        'first_name' => 'First',
        'last_name' => 'User'
    );

    $this->db->insert('user',$user1);

            // run method
    $all = $this->user_model->get_all_users();
            // and test
    echo $this->unit->run(count($all),1);

            // DELETE
    $this->db->delete('user',array('user_no' => 11));

}