如何将 PHPUnit 与 CodeIgniter 结合使用?

2024-02-04

我读过并阅读过有关 PHPUnit、SimpleTest 和其他单元测试框架的文章。他们听起来都很棒!我终于让 PHPUnit 与 Codeigniter 一起工作了,感谢https://bitbucket.org/kenjis/my-ciunit/overview https://bitbucket.org/kenjis/my-ciunit/overview

现在我的问题是,我该如何使用它?

我看到的每个教程都有一些抽象用途,例如assertEquals(2, 1+1) or:

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

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

我想做测试驱动开发,并且我已经阅读了 PHPUnits 网站上的教程,但这个示例再次显得如此抽象。我的大多数 codeigniter 功能都在显示数据。

是否有一本包含 PHPUnit 测试实际应用和示例的书或很棒的教程?


看来您了解如何编写测试和单元测试 CodeIgniter 代码的基本结构/语法应该与测试非 CI 代码没有任何不同,所以我想重点关注您的潜在关注点/问题...

不久前我对 PHPUnit 也有类似的问题。作为一个没有接受过正规培训的人,我发现进入单元测试思维模式一开始似乎很抽象且不自然。我认为造成这种情况的主要原因 - 就我而言,也可能是你的问题 - 是你没有专注于REALLY到目前为止,正在努力区分代码中的问题。

测试断言看起来很抽象,因为大多数方法/函数可能执行几个不同的离散任务。成功的测试心态需要改变您对代码的看法。你不应该再用“它有效吗?”来定义成功。相反,您应该问:“它是否有效,是否可以与其他代码很好地配合,它的设计方式是否使其在其他应用程序中有用,我可以验证它是否有效?”

例如,下面是到目前为止您可能如何编写代码的简化示例:

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

这里到底发生了什么并不重要。我试图说明为什么这段代码难以测试:

  • 它使用单例配置类来生成值。函数的成功取决于单例的值,当您无法使用不同的值实例化新的配置对象时,如何在完全隔离的情况下测试该函数是否正常工作?更好的选择可能是将您的函数传递给$config参数由您可以控制其值的配置对象或数组组成。这被广义地称为“依赖注入 http://www.potstuck.com/2009/01/08/php-dependency-injection/”并且互联网上到处都有关于这种技术的讨论。

  • 注意嵌套的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;
  }
}

正如您所看到的,这些类方法中的每一个都处理该类的一个易于测试的特定函数。远程文件检索有效吗?我们是否找到了要解析的值?等等。突然之间,这些抽象的断言似乎更有用了。

恕我直言,您越深入测试,您就越意识到良好的代码设计和合理的架构不仅仅是确保事情按预期工作。这就是 OOP 的好处真正开始显现的地方。您可以很好地测试程序代码,但对于具有相互依赖的部分的大型项目,测试有一种强制执行良好设计的方法。我知道这对于一些程序性的人来说可能是巨魔的诱饵,但是哦,好吧。

您测试的越多,您就会发现自己越多地编写代码并问自己:“我能够测试这个吗?”如果没有,您可能会立即更改结构。

然而,代码不必是基本的即可测试。存根和嘲笑 http://www.phpunit.de/manual/current/en/test-doubles.html允许您测试外部操作,其成功或失败完全不受控制。您可以创建fixtures http://www.phpunit.de/manual/current/en/fixtures.html测试数据库操作和几乎其他任何东西。

我测试得越多,我就越意识到,如果我在测试某些东西时遇到困难,很可能是因为我有一个潜在的设计问题。如果我纠正它,通常会导致我的测试结果中出现所有绿色条。

最后,这里有几个链接确实帮助我开始以测试友好的方式进行思考。第一个是如果你想编写可测试的代码,不应该做什么的半开玩笑的列表 http://misko.hevery.com/2008/07/24/how-to-write-3v1l-untestable-code/。事实上,如果您浏览整个网站,您会发现很多有用的内容,它们将帮助您走上 100% 代码覆盖率的道路。另一篇有用的文章是这个依赖注入的讨论 http://www.potstuck.com/2009/01/08/php-dependency-injection/.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何将 PHPUnit 与 CodeIgniter 结合使用? 的相关文章

随机推荐

  • History.js 有没有办法知道何时按下后退按钮

    我已经开始测试历史 js https github com balupton history js 在了解了它的工作原理并且没有popstate 而是有statechange 我正在寻找一种在按下浏览器后退按钮时有所不同的方法 原因是我需要
  • Linux 上的 Squeak SMTP

    我正在使用 Squeak 5 类 SecureSMTPClient 通过 SSL TLS 发送电子邮件 它在我的 Windows 机器上运行良好 感谢答案那个问题 https stackoverflow com questions 3761
  • 使用可变长度数组有任何开销吗?

    使用可变长度数组有一些开销吗 数组的大小可以在运行时通过命令行参数传递吗 与自动和动态分配数组相比 为什么要引入它 VLA 确实有一些开销 与 普通 命名的编译时大小的数组相比 首先 它具有运行时长度 而且该语言为您提供了在运行时获取数组实
  • 从返回响应数据的 Fetch Post 获取数据

    我在带有 redux 的 React 应用程序中使用交叉获取 在我的减速器中 我使用 cross fetch 的 post 方法将一些数据保存到数据库中 我的调用返回一个响应 其中包含一些我需要在保存后分配给状态的数据 但我在解析数据时遇到
  • 当我直到运行时才知道长度时,如何声明数组?

    我最初有一个数组 1 1000 它被定义为全局变量 但现在我需要它是 n 而不是 1000 直到后来我才找到 n 在填充数组之前我知道 n 是什么 但我需要它是全局的 因此需要一种在运行时定义全局数组的大小的方法 上下文是通过文件中字节的线
  • 电话号码中的正则表达式可选空格[重复]

    这个问题在这里已经有答案了 可能的重复 用于电话号码验证的综合正则表达式 https stackoverflow com questions 123559 a comprehensive regex for phone number val
  • Pig 和 Hive 之间的区别?为什么两者都有? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 我的背景 进入 Hadoop 世界已经 4 周了 使用 Cloudera 的 Hadoop VM 涉足 Hive Pig 和 Hadoop 读过
  • 片段着色器 - 确定整个(单色)图像的最小/最大值并使用它们进行进一步的像素操作

    我想正常化单色图像像素以这种方式最小值为黑色 最大值为白色 并且两者之间的值按比例分布 目前我在 canvas 中分两步完成 但我相信在 WebGL 中应该更快 我可以想象通过片段着色器操纵颜色 但我找不到任何有效的方法来 1 确定图像的实
  • 如何在 IE11 中显示 html5 视频元素的控件?

    IE11 也可能是 10 不会显示视频控件 直到您将鼠标悬停在视频本身上 就我个人而言 我认为完全没有用 特别是当您也使用海报元素时 因为用户无法知道图像实际上是视频 有没有办法像 Chrome 一样 强制 IE 显示控件 我使用的代码是
  • ServiceStack CredentialAuthProvider 具有多个用户/密码

    我想使用自定义身份验证提供程序 但我不知道如何使标准身份验证内容处理更多用户和密码作为参数 这可以做到吗 取决于您想要做什么 如果您想通过继承 Credentials AuthProvider 创建自己的自定义身份验证提供程序 您可以通过以
  • 获取c++ windows中每个进程使用的磁盘

    I am trying to build a tool which is something similar to Task Manager I was able to get the CPU and Memory of each proc
  • Xcode 中的 XCBUtil.PropertyListConversionError

    当我尝试运行我的项目时 出现此错误 指向我的本地化 strings 文件 读取失败 操作无法完成 XCBUtil PropertyListConversionError错误1 我可以做什么来解决这个问题 错误所指向的 string 文件内存
  • 计算子集的唯一交集

    Given a set S si zj z N what is a time efficient algorithm for computing the unique sets of intersections of the subsets
  • Ivy:使用动态修订

    我在理解如何使用动态修订版时遇到问题Ivy http ant apache org ivy 在我的 Java 项目中有效 目前 我有以下布局 lib a revision 1 0 0 status release dependencies
  • Powershell启动作业同步输出

    我有一个启动作业的 powershell 脚本 start job scriptblock while true echo Running Start Sleep 2 然后它继续执行脚本的其余部分 该工作是一种对该进程 PID 的监控工作
  • Apache 用户帐户无密码访问服务器 - Ubuntu

    我有同样的问题this https stackoverflow com questions 9089350 rsync via php exec with ssh passwordless ssh login问题 如果我再解释一遍 我可以使
  • 前向声明类成员的前向声明

    是否可以前向声明一个在另一个前向声明的类中声明的类 基本上 我有这样的东西 A h class A struct B 现在我想声明另一个这样的类 Q h class A struct A B class Q A B Foo 不 这是不可能的
  • 在 WordPress 中获取类别 ID 数组?

    cats get categories array order gt ASC orderby gt id hierarchical gt 0 hide empty gt 0 taxonomy gt edu year 我想生成一个变量 其中包
  • 当作为 *.a 静态库链接时,为什么“WinMain”无法解析?

    给定一个简单的程序 include
  • 如何将 PHPUnit 与 CodeIgniter 结合使用?

    我读过并阅读过有关 PHPUnit SimpleTest 和其他单元测试框架的文章 他们听起来都很棒 我终于让 PHPUnit 与 Codeigniter 一起工作了 感谢https bitbucket org kenjis my ciun