Как использовать 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?

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

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

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

У меня были подобные вопросы не так давно с PHPUnit. Как кто-то без формального обучения, я обнаружил, что проникновение в менталитет Unit Testing сначала казалось абстрактным и неестественным. Я думаю, что основная причина этого – и в моем случае, и, возможно, у вас тоже вопрос, – то, что вы не сосредоточились на ДЕЙСТВИТЕЛЬНО работе над тем, чтобы разделить проблемы в вашем коде до сих пор.

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

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

 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; } } 

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

  • Для генерации значений используется одноуровневый класс конфигурации. Успех вашей функции зависит от значений из singleton и как вы можете проверить, что эта функция работает правильно в полной изоляции, когда вы не можете создавать новые объекты конфигурации с разными значениями? Лучшим вариантом может быть передача вашей функции аргумента $config который состоит из объекта конфигурации или массива, значения которого вы можете контролировать. Это в целом называется « Injection Dependency », и во всем мире есть дискуссии об этом методе.

  • Обратите внимание на вложенные операторы 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; } } 

Как вы можете видеть, каждый из этих методов класса обрабатывает определенную функцию класса, который легко тестируется. Работает ли удаленный поиск файлов? Мы находили ценности, которые мы пытались проанализировать? И т. Д. Внезапно эти абстрактные утверждения кажутся гораздо более полезными.

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

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

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

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

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

Удачи!

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

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

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

Следовательно, я согласился на класс тестирования модуля 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)); }