php单元测试代码
现在是凌晨3点,您知道代码是否仍在运行吗?
Web应用程序每天7天24小时不停地运行,想知道我的应用程序是否仍在运行,这让我彻夜难眠。 单元测试使我对代码有很大的信心-睡个好觉。
单元测试是用于在代码上编写测试并自动运行这些测试的框架。 测试驱动的开发是一种单元测试方法,它表示您首先编写测试并确认测试发现错误,然后编写测试通过所需的代码。 当所有测试通过后,您正在开发的功能应已完成。 这些单元测试的价值在于您可以在任何时间运行它们-在检入代码之前,在进行重大重构之后或在将其部署到正在运行的系统之后。
对于PHP,单元测试框架是PHPUnit2。 您可以使用PEAR命令行将系统作为PEAR模块% pear install PHPUnit2
: % pear install PHPUnit2
。
安装框架之后,可以通过创建从PHPUnit2_Framework_TestCase
派生的测试类来开始编写单元测试。
我发现开始单元测试的最佳位置是应用程序的业务逻辑模块。 我举一个简单的例子:一个将两个数字相加的函数。 要开始测试,我首先编写测试,如下所示。
assertTrue( add( 1, 2 ) == 3 ); }
function test2() { $this->assertTrue( add( 1, 1 ) == 2 ); }
}
?>
这个TestAdd
类中有两个方法,都带有前缀test
。 每种方法都定义了一个测试,该测试可以像清单1一样简单,也可以根据您要完全测试所开发功能的某些方面而复杂。 在这种情况下,我只是断言在第一个测试中一加二等于三,在第二个测试中一加一等于二。
PHPUnit2系统定义了assertTrue assertTrue()
方法,该方法用于测试参数中包含的条件的评估结果是否为true。 然后,我编写Add.php模块,该模块实现了足以使测试最初失败的代码。
现在运行单元测试时,两个测试均失败。
% phpunit TestAdd.php
PHPUnit 2.2.1 by Sebastian Bergmann.
FF
Time: 0.0031270980834961
There were 2 failures:
1) test1(TestAdd)
2) test2(TestAdd)
FAILURES!!!
Tests run: 2, Failures: 2, Errors: 0, Incomplete Tests: 0.
现在,我知道这两种测试都有效。 因此,我可以修改add()
函数以实际执行正确的操作。
现在两个测试都通过了。
% phpunit TestAdd.php
PHPUnit 2.2.1 by Sebastian Bergmann.
..
Time: 0.0023679733276367
OK (2 tests)
%
这个测试驱动的开发示例非常简单,但是您明白了。 首先创建测试,并创建足够的代码以使测试运行,但失败了。 然后,您验证测试失败并实现代码以使其通过。
我发现在实现代码时,最终要添加更多测试,直到拥有完整的测试集,该测试集将检查代码路径中的所有变体。 您可以在本文末尾找到有关编写哪些测试以及如何编写这些测试的建议。
在模块测试之后,您将测试数据库访问。 数据库访问测试带来了两个有趣的问题。 首先,您必须在每次测试之前将数据库重置到某个已知点。 其次,请注意,此重置可能会对活动数据库造成数据库损坏,因此,您必须针对与生产数据库不同的数据库进行测试或编写测试,以免影响现有数据库的内容。
数据库单元测试始于数据库。 为了说明这一点,我需要下面显示的简单架构。
DROP TABLE IF EXISTS authors;
CREATE TABLE authors (
id MEDIUMINT NOT NULL AUTO_INCREMENT,
name TEXT NOT NULL,
PRIMARY KEY ( id )
);
清单5是一个作者表,每个作者都有一个关联的ID。
接下来,我编写测试。
assertTrue( Authors::delete_all() );
}
function test_insert() {
$this->assertTrue( Authors::delete_all() );
$this->assertTrue( Authors::insert( 'Jack' ) );
}
function test_insert_and_get() {
$this->assertTrue( Authors::delete_all() );
$this->assertTrue( Authors::insert( 'Jack' ) );
$this->assertTrue( Authors::insert( 'Joe' ) );
$found = Authors::get_all();
$this->assertTrue( $found != null );
$this->assertTrue( count( $found ) == 2 );
}
}
?>
这组测试涵盖从表中删除作者,将作者插入表中以及在验证作者是否在其中的同时插入作者。 这是我发现可以方便地发现错误的附加测试级联。 通过查看哪些测试有效,哪些测试无效,我可以快速分辨出什么是失败的,然后了解它们之间的差异。
dblib.php PHP数据库访问代码的初始失败版本如下所示。
getMessage()); }
return $db;
}
public static function delete_all()
{
return false;
}
public static function insert( $name )
{
return false;
}
public static function get_all()
{
return null;
}
}
?>
对清单8中的代码运行单元测试表明,所有三个测试均失败:
% phpunit TestAuthors.php
PHPUnit 2.2.1 by Sebastian Bergmann.
FFF
Time: 0.007500171661377
There were 3 failures:
1) test_delete_all(TestAuthors)
2) test_insert(TestAuthors)
3) test_insert_and_get(TestAuthors)
FAILURES!!!
Tests run: 3, Failures: 3, Errors: 0, Incomplete Tests: 0.
%
现在,我可以逐个方法添加代码,该代码将正确访问数据库,直到所有三个测试均通过。 dblib.php代码的最终版本如下所示。
getMessage()); }
return $db;
}
public static function delete_all()
{
$db = Authors::get_db();
$sth = $db->prepare( 'DELETE FROM authors' );
$db->execute( $sth );
return true;
}
public static function insert( $name )
{
$db = Authors::get_db();
$sth = $db->prepare( 'INSERT INTO authors VALUES (null,?)' );
$db->execute( $sth, array( $name ) );
return true;
}
public static function get_all()
{
$db = Authors::get_db();
$res = $db->query( "SELECT * FROM authors" );
$rows = array();
while( $res->fetchInto( $row ) ) { $rows []= $row; }
return $rows;
}
}
?>
当我在此代码上运行测试时,所有测试均无错误运行,并且我知道我的代码可以正常工作。
测试整个PHP应用程序的下一步是测试前端的超文本标记语言(HTML)接口。 为此,您需要一个如下所示的网页。
此页面添加两个数字。 要测试页面,请从单元测试代码开始。
get( $url );
$resp = $client->currentResponse();
return $resp['body'];
}
function test_get()
{
$page = TestPage::get_page( 'http://localhost/unit/add.php' );
$this->assertTrue( strlen( $page ) > 0 );
$this->assertTrue( preg_match( '//', $page ) == 1 );
}
function test_add()
{
$page = TestPage::get_page( 'http://localhost/unit/add.php?a=10&b=20' );
$this->assertTrue( strlen( $page ) > 0 );
$this->assertTrue( preg_match( '//', $page ) == 1 );
preg_match( '/(.*?)<\/span>/', $page, $out );
$this->assertTrue( $out[1]=='30' );
}
}
?>
此测试使用PEAR的HTTP客户端模块。 我发现它比内置PHP客户端URL库(CURL)容易一些,尽管您也可以使用它。
一种测试检查页面是否返回,并确定页面是否包含HTML。 第二个测试通过将这些值放在请求的URL中,然后检查页面中编码的结果跨度,要求10和20之和。
该页面的代码如下所示。
此页面非常简单。 两个输入字段显示请求中的当前值。 结果跨度显示这两个值的总和。 标记具有所有不同:用户不可见,但单元测试不可见。 因此,单元测试不需要复杂的树逻辑即可找到该值。 而是,它检索特定
标记的值。 这样,当界面更改时,只要跨度存在,测试就会通过。
与以前一样,您首先对测试进行编码,然后创建页面的失败版本。 您测试失败,然后更改页面以使其正常工作。 结果如下:
% phpunit TestPage.php
PHPUnit 2.2.1 by Sebastian Bergmann.
..
Time: 0.25711488723755
OK (2 tests)
%
两个测试都通过了,这意味着代码可以正常工作。
不过,测试HTML前端有一个陷阱:JavaScript。 超文本传输协议(HTTP)客户端代码检索页面,但不执行JavaScript。 因此,如果您JavaScript文件中包含大量代码,则必须创建用户代理级的单元测试。 我发现最好的方法是使用Microsoft®InternetExplorer®内置的自动化层。 用PHP编写的MicrosoftWindows®脚本可以使用组件对象模型(COM)接口来控制Internet Explorer,让它导航到页面,然后使用文档对象模型(DOM)方法来找出特定条件后页面元素的外观。用户操作。
这是我发现对前端JavaScript代码进行单元测试的唯一方法。 而且我很容易承认,编写或维护并不容易,并且在您对页面进行少量修改时,这些类型的测试很容易损坏。
在编写测试时,我喜欢满足以下条件:
这些是单元测试的一些想法。 我也对如何编写单元测试有一些建议:
单元测试对工程师来说很有价值。 它们是敏捷开发过程(强调代码的过程)的基石之一,因为文档需要一些证明来证明代码符合规范。 单元测试提供了证明。 该过程从单元测试开始,该单元测试定义应执行的代码,但当前不执行。 因此,所有测试均以失败告终。 然后,随着代码接近完成,测试开始通过。 当所有测试通过时,代码完成。
我从来没有编写大型代码,也没有单元测试就无法重构大型或复杂的代码块。 我经常发现自己在修改代码之前先对现有代码编写单元测试,以确保在进行更改时我知道自己正在破坏(或不破坏)什么。 这种保证使我非常有信心,即使在凌晨3点,我交付给客户的代码也能正常运行
翻译自: https://www.ibm.com/developerworks/opensource/library/os-php-unit/index.html
php单元测试代码