看来您了解如何编写测试和单元测试 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/.