正如我在今天早些时候的文章中所承诺的,这是我使用phpt在PHP中进行单元测试的速成教程。
首先,据我所知,phpt测试文件是作为PHP-QA工作的一部分创建的。虽然我在PHP-QA站点中找不到链接,但他们有一个详细介绍phpt测试文件的页面,并且该页面显示了phpt测试文件的所有部分,尽管它们不一定显示每个部分的示例。
此外,您可能会发现这篇InternationalPHPMagazine文章内容丰富;在其中,AaronWormus给出了关于它们的简短教程,以及一些使用PHPUnit进行phpt测试的方法。
最后,在开始之前,我想说明:我不是单元测试方面的专家。但是,单元测试背后的想法很简单:保持代码简单和模块化,并测试每个小部分(或模块)所有类型的输入和输出。如果您正在测试的代码是函数或类方法,请测试可以传递给它的参数的所有排列,以及所有可能的返回值。
好的,让我们开始吧!
概览
phpt测试文件的基本格式如下所示:
--TEST-- test name --FILE-- <?php // your PHP code goes here ?> --EXPECT-- Expected output from the PHP code
如您所见,该文件分为几个部分,每个部分都以--TITLE--
开头。--TEST--
为测试名称;这可以是函数名、类名、类方法名或一些自由文本。尝试让它变得有意义。--FILE--
是将要执行的PHP代码,--EXPECT--
是此PHP代码的预期输出。如果PHP代码的输出符合预期,则测试通过。
您还可以使用其他一些部分;我使用--SKIPIF--
部分类型来测试存在哪个版本的PHP(例如,Cgiapp2仅支持PHP5);如果满足条件,则跳过测试。您还可以指定--EXPECTF--
或--EXPECTREGEX--
而不是--EXPECT--
,但我发现在大多数情况下,我可以控制我的代码的输出,这样就不需要这些了。
编写测试的技巧
首先,我对phpt测试的唯一经验是测试Cgiapp和Cgiapp2,它们是类;这些提示在其他情况下可能没有意义。
其次,提示以粗体突出显示。
我发现您应该为每种方法创建一个测试文件。(一般来说,我遇到过一些需要多个文件的情况,主要是在测试使用header()的代码时。)在那个测试文件中,你想测试:
- 方法参数
- 方法返回值
这意味着您需要为多种情况编写代码。在编写了一些测试之后,我发现如果不在测试代码中包含信息输出,调试就会变得很困难。创建有关正在测试的内容的信息输出:
<?php echo "Test 1: single string argument\n"; ?>
当测试失败时,这些陈述是无价的;然后,您可以一目了然地看到您正在测试的内容。
如果您在代码中使用trigger_error()
或PEAR_Error
(您是,不是吗?),在您的测试中包含一个错误处理程序代码,以便您可以捕获这些并将它们转换为您可以格式化和控制的消息。
据推测,--GET--
和--POST--
部分允许您指定这些数组中存在的变量以用于测试目的。然而,这只适用于CGI版本的PHP……而且,如果您像我一样,那么您正在使用CLISAPI。简单的解决方法是简单地在--FILE--
部分中构建$_GET
和$_POST
数组。
$_SESSION
也是如此。但是,如果您指定session_start()
,$_SESSION
数组将出现;它只是空的。
如果需要包含文件,请将其包含在相对于测试目录的目录中。要确定该目录是什么(不要假设它是.
),使用构造dirname(__FILE__)
:
require_once dirname(__FILE__) . '/setup.php.inc';
运行测试
一旦您有了测试文件,只需执行pearrun-teststestFile.phpt(当然,替换为您的测试文件的名称)。如果您希望一次从多个文件运行多个测试,您可以将每个文件的名称作为参数包含在内。如果您想在目录中运行所有测试,只需执行不带任何参数的pearrun-tests。
运行测试时,您将在屏幕上看到信息。如果测试失败,则给出测试文件的名称和测试名称。
调试失败的测试
最终,测试会失败。可能是你写错了,或者你的代码中确实有错误。问题是,phpt测试如何帮助您确定是哪一个?
当对一个文件运行测试时,该文件被拆分成多个部分。--FILE--
部分实际上写入了一个以测试文件命名的文件,但扩展名为.php
。--EXPECT--
部分被写入一个包含.exp
部分的文件;输出通过管道传输到具有.out
部分的文件;并将发生的事件的日志写入扩展名为.log
的文件中。最后,如果测试失败,将创建一个.diff
文件,其中包含.exp
和.out
文件之间的差异。例如,如果我们有一个名为testFile.phpt的测试文件,但它未通过测试,我们现在将拥有以下文件:
run-tests.log testFile.diff testFile.exp testFile.log testFile.out testFile.php testFile.phpt
您的第一站应该是.diff
文件。您一眼就能看出,例如,是否发生了PHP错误。我在我的几个测试中发现,当我看到这些差异中弹出语法错误警告时,我在测试代码中漏掉了分号或大括号。
如果.diff
没有为您充分解释差异,请打开您的.exp
和.out
文件。我使用VIM,我通常会执行:vsplit这样我就可以并排加载这些文件并进行比较。这样做时,我可以直观地看到输出开始与预期不同的地方。(有好几次我在预期中发现了错别字,这意味着我改正错字后测试运行正常。)
还记得我之前说过创建关于正在测试的内容的信息输出吗?这就是它发挥作用的地方。我发现输出内容如下:
. . Bad argument passed something
比以下更难理解:
Test 1: current directory as argument . Test 2: no argument passed . Test 3: object as argument Bad argument passed Test 4: 'something' as argument something
在上面的示例中,如果测试2的预期是其他内容,我现在确切地知道我的测试文件中的哪个测试失败了——这有助于我确定我可能需要在我的代码中修复它的位置。
总结
编写测试的技巧
- 为每个方法创建一个测试文件
- 创建有关正在测试的内容的信息输出
- 如果触发错误,则在测试代码中包含一个错误处理程序
- 在
--FILE--
部分构建你的$_GET
和$_POST
数组;它比--GET--
和--POST--
- 使用构造
dirname(__FILE__)
更便携
运行测试
pear运行测试testFile.phpt
pear运行测试testFile1.phpttestFile2.phpt
pear运行测试
调试失败的测试
- 检查
.diff
文件;在测试代码中查找PHP错误 - 并排比较
.exp
和.out
文件:- 检查预期输出中的拼写错误
- 检查信息输出以确定测试的哪一部分失败
从这里去哪里
显然,要完全理解测试,唯一的方法就是亲自动手。有大量可用的单元测试资源;c2wiki有一些很好的资源,许多书籍都涵盖了这个主题(ThePragmaticProgrammer浮现在脑海中)。
我读过一些论据,认为您应该首先测试界面。这意味着您不会在函数/方法中抛出意外参数。稍后,在代码成熟之后,您可以为意外参数添加测试,或者为已报告的错误添加测试。PHP-QA站点建议为该方法提供一个测试文件,但也有解决特定错误的测试文件;我还没有进行测试。
最后,我在许多资源中读到真正的单元测试应该在您开始编程之前开始。虽然我在一定程度上理解了这个原则,但我也发现,在我编写代码时,我发现了我之前无法预料到的问题中的复杂性……而这些复杂性的解决方案通常是新方法。为此,我觉得编写测试应该在代码初稿之后进行。这样做提供了代码的第一个接口,也有助于在应用程序测试开始之前清理代码和查找错误。但是,这只是我的拙见。
测试愉快!