如何使用PHPUnit和CodeIgniter?

我已阅读并阅读关于PHPUnit,SimpleTest和其他unit testing框架的文章。 他们都听起来很棒! 我最终得到了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()); } 

如果我有一个函数可以输出这样一个可预测的string,那就太好了。 通常,我的应用程序从数据库中获取一堆数据,然后将其显示在某种表格中。 那么如何testingCodeigniter的控制器呢?

我想做testing驱动的开发,我已经阅读了PHPUnits网站的教程,但是再一次,这个例子看起来很抽象。 我的大部分codeigniter函数都显示数据。

有没有一本书或一个伟大的教程与实际应用程序和PHPUnittesting的例子?

看来你理解如何编写testing和unit testing的基本结构/语法CodeIgniter的代码应该没有任何不同于testing非CI代码,所以我想专注于您的基本问题…

不久前,我有类似的问题与PHPUnit。 作为一个没有正式培训的人,我发现进入unit testing的思维模式起初看起来抽象和不自然。 我认为这样做的主要原因 – 就我而言,也可能是你自己的问题 – 也就是说,到现在为止,你还没有专注于真正将代码中的问题分离出来。

testing断言看起来很抽象,因为你的大多数方法/函数可能执行几个不同的离散任务。 一个成功的testing思路需要改变你对代码的看法。 你应该停止在“是否有效”方面定义成功? 相反,你应该问,“它是否工作,是否会和其他代码一起运行,它的devise方式是否使其在其他应用程序中有用,并且我可以validation它是否有效?

例如,下面是一个简单的例子,你可能已经写了代码到这一点:

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

到底发生了什么并不重要。 我试图说明为什么这个代码很难testing:

  • 它使用单例configuration类来生成值。 你的函数的成功取决于来自单例的值,当你不能实例化具有不同值的新configuration对象时,如何testing这个函数能否完全隔离。 一个更好的select可能是传递你的函数一个$config参数,它由一个你可以控制的configuration对象或数组组成。 这被广义地称为“ dependency injection ”,并且在整个互联网上都有关于这种技术的讨论。

  • 注意嵌套的IF语句。 testing意味着你正在使用某种testing覆盖每个可执行文件。 嵌套IF语句时,您正在创build需要新testingpath的新代码分支。

  • 最后,你是否看到这个函数是怎么做的(parsing一个远程文件的内容)实际上是在执行几个任务? 如果你热心地分离你的问题,你的代码变得无限可测。 一个更可testing的方式来做同样的事情将是…


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

正如你所看到的,这些类的每一个方法都可以处理这个类的一个特定的函数,这个函数很容易被testing。 远程文件检索工作? 我们是否find了我们试图parsing的价值? 等突然之间,那些抽象的断言似乎更有用。

恕我直言,你越深入到testing越多,你意识到这是更好的代码devise和合理的体系结构,而不是简单地确保事情按预期工作。 在这里,OOP的好处真的开始闪耀。 你可以很好的testing程序代码,但是对于一个相互依赖的部分testing的大型项目来说,可以强制执行好的devise。 我知道这可能是一些程序人员的巨魔诱饵,但是哦。

你testing得越多,你越会发现自己编写代码并问自己:“我能testing这个吗? 如果不是的话,你可能会改变结构。

但是,代码不一定是可testing的。 存根和嘲弄使您能够testing外部操作的成功或失败完全失去控制。 您可以创build固定装置来testing数据库操作和其他任何东西。

我testing得越多,我越是意识到,如果我经历了一个艰难的时间testing,最有可能的原因是我有一个潜在的devise问题。 如果我把它弄出来,通常会导致我的testing结果中出现所有绿条。

最后,这里有几个链接确实帮助我开始以易于testing的方式开始思考。 第一个是如果你想编写可testing的代码,不要做什么 。 事实上,如果你浏览整个网站,你会发现很多有用的东西,这将有助于你设置100%的代码覆盖率。 另一篇有用的文章是关于dependency injection的讨论 。

祝你好运!

我没有成功尝试与Codeigniter一起使用PHPUnit。 例如,如果我想testing我的CI模型,我遇到了如何获得该模型的实例的问题,因为它以某种方式需要整个CI框架加载它。 考虑如何加载一个模型,例如:

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

问题是如果你看一个加载方法的超类,你不会find它。 如果您正在testingPlain Old PHP对象,那么您可以轻松地模拟您的依赖关系并testingfunction,这并不那么简单。

因此,我已经解决了CI的unit testing课 。

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

如果你正在testing你的控制器,你本质上是在testing业务逻辑(如果有的话)以及从数据库中“抓取一堆数据”的sql查询。 这已经是集成testing。

最好的方法是首先testingCI模型以testing数据的抓取—如果您有一个非常复杂的查询,那么这将非常有用,然后控制器将testing应用于抓取的数据的业务逻辑由CI模型。 一次只能testing一件东西是一种很好的做法。 那么你会testing什么? 查询或业务逻辑?

我假设你想先testing数据的抓取,一般的步骤是

  1. 获取一些testing数据并设置您的数据库,表格等

  2. 有一些机制用testing数据填充数据库,并在testing后删除它。 PHPUnit的数据库扩展有一个办法做到这一点,虽然我不知道你是否支持你发布的框架。 让我们知道

  3. 写你的testing,通过它。

您的testing方法可能如下所示:

 // 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的unit testing类,下面是我使用它编写的一个testing的修改代码片段:

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