.phpt 文件结构
现在我们知道了如何使用 run-tests 运行测试,让我们更详细地研究一下 phpt 文件。phpt 文件只是一个普通的 PHP 文件,但它包含 run-tests 支持的许多不同部分。
基本测试示例
这是测试 echo
构造的 PHP 源测试的基本示例。
--TEST--
echo - basic test for echo language construct
--FILE--
<?php
echo 'This works ', 'and takes args!';
?>
--EXPECT--
This works and takes args!
您知道 echo 可以接受参数列表 吗?现在您知道了。
phpt 文件中还有 许多其他部分 可供我们使用,但这三个部分是最低要求。 --EXPECT--
部分有一些变化,但我们稍后会介绍这些部分。
请注意,在这三个部分中,我们拥有运行黑盒测试所需的一切。我们有测试名称、一些代码和预期输出。同样,黑盒测试不关心代码如何运行,它只关心最终结果。
一些值得注意的部分
现在我们已经了解了每个 .phpt
文件所需的三个部分,让我们来看看我们肯定会遇到的其他几个常见部分。
--TEST--
-
测试名称
–TEST– section 仅用一行文字描述测试内容(针对人类)。这将在运行测试时显示在控制台中,因此最好能描述清楚,但不要过于冗长。如果测试需要更长的描述,可添加 -DESCRIPTION-section 部分。
--TEST-- json_decode() with large integers
--TEST--
部分必须是 phpt 文件的第一行。否则 run-tests 将不会认为它是一个有效的测试文件,并将测试标记为 “borked”。 --FILE--
-
要运行的 PHP 代码
FILE- section 是我们要测试的 PHP 代码。在上面的示例中,我们要确保
echo
结构可以接收参数列表并将其连接为标准输出。--FILE-- <?php $json = '{"largenum":123456789012345678901234567890}'; $x = json_decode($json); var_dump($x->largenum); $x = json_decode($json, false, 512, JSON_BIGINT_AS_STRING); var_dump($x->largenum); echo "Done\n"; ?>
虽然在用户区省略结尾的 PHP 标记 (
?>
) 被认为是一种最佳做法,但 phpt 文件并非如此。如果省略了结尾的 PHP 标记,run-tests 在运行测试时不会有任何问题,但测试将无法再像正常的 PHP 文件那样运行。这也会让你的集成开发环境发疯。因此,请务必记住在每个--FILE--
部分都包含关闭的 PHP 标记。 --EXPECT--
-
期望的输出
–EXPECT– section 包含我们期望从标准输出中看到的内容。如果您期望像在 PHPUnit 中那样获得花哨的断言,那么您在这里不会得到任何断言。请记住,这些是 “功能测试”,因此我们只是在提供输入后检查输出。
--EXPECT-- float(1.2345678901235E+29) string(30) "123456789012345678901234567890" Done
运行测试会根据预期输出和实际输出修剪尾随的新行,因此您不必担心在
--EXPECT--
部分末尾添加或删除尾随的新行。 --EXPECTF--
-
替换后的预期输出
由于测试需要在多种环境中运行,我们常常不知道脚本的实际输出是什么。或者也许您测试的功能是不确定的。对于此用例,我们有 –EXPECTF– section,它允许我们用替换字符替换输出部分,就像 PHP 中的
sprintf()
函数一样。--EXPECTF-- string(%d) "%s" Done
这在创建输出 PHP 文件的绝对路径的错误案例测试时特别方便;这会因环境而异。
--TEST-- Test error operation of password_hash() with bcrypt hashing --FILE-- <?php var_dump(password_hash("foo", PASSWORD_BCRYPT, array("cost" => 3))); ?> --EXPECTF-- Warning: password_hash(): Invalid bcrypt cost parameter specified: 3 in %s on line %d NULL
--SKIPIF--
-
应跳过测试的条件
由于 PHP 可以配置无数选项,因此您正在运行的 PHP 版本可能未使用运行测试所需的依赖项进行编译。最常见的情况是扩展测试。
如果测试需要安装扩展才能运行测试,则将有一个 –SKIPIF– section,用于检查扩展是否确实已安装。
--SKIPIF-- <?php if (!extension_loaded('json')) die('skip ext/json must be installed'); ?>
满足
--SKIPIF--
条件的任何测试都将被 run-tests 标记为 “跳过”,并继续执行队列中的下一个测试。当您从 run-tests 运行测试时,单词 “skip” 后面的任何文本都将在输出中返回,作为跳过测试的原因。如果满足
--SKIPIF--
条件,许多测试将使用die()
或exit()
停止脚本执行,如上例所示。重要的是要明白,仅仅因为您在--SKIPIF--
部分中die()
,并不意味着 run-tests 会跳过您的测试。Run-tests 只是检查--SKIPIF--
的输出并查找单词 “skip” 作为前四个字符。如果第一个单词不是 “skip”,则不会跳过测试。事实上,只要 “skip” 是输出的第一个单词,您根本不必停止执行。
以下示例将跳过测试。请注意,我们没有停止脚本执行。
--SKIPIF-- <?php if (!extension_loaded('json')) echo 'skip'; ?>
相比之下,请检查以下示例。请注意它如何停止脚本执行,但由于单词 “skip” 不是输出中的第一个单词,因此 run-tests 仍会顺利运行测试而不会跳过它。
--SKIPIF-- <?php if (!extension_loaded('json')) exit; ?>
虽然不需要在
--SKIPIF--
部分暂停脚本执行,但始终强烈建议这样做,以便您仍然可以将 phpt 文件作为普通 php 文件运行,并看到 “必须安装 skip ext/json” 之类的友好消息,而不是收到大量随机错误。 --INI--
-
有时测试依赖于非常具体的 INI 设置。在这种情况下,您可以使用 –INI– section 定义任何 INI 设置。每个 INI 设置都放在该部分的新行上。
--INI-- date.timezone=America/Chicago
Run-tests 为您完成设置 INI 配置的所有神奇工作。
编写简单测试
让我们编写第一个测试,以熟悉该过程。
通常,测试存储在我们要测试的代码附近的 tests/
目录中。例如, PDO 扩展位于 PHP 源代码中的 ext/pdo
。如果打开该目录,您将看到一个 tests/directory,其中包含大量 .phpt
文件。所有其他扩展都以相同的方式设置。 Zend/tests/ 中还有 Zend 引擎的测试。
对于此示例,我们将在根 php-src
目录中临时创建一个测试。使用您最喜欢的编辑器创建并打开一个新文件。
$ vi echo_basic.phpt
如果你以前从未使用过 vim,那么运行上述命令后你可能会陷入困境。只需多次按 |
现在将上面的示例测试复制并粘贴到新的测试文件中。以下是测试文件,可帮您省去滚动浏览的时间。
--TEST--
echo - basic test for echo language construct
--FILE--
<?php
echo 'This works ', 'and takes args!';
?>
--EXPECT--
This works and takes args!
在 PHP 源代码根目录下将文件保存为 echo_basic.phpt
,并退出编辑器后,使用 make 运行示例测试。
$ make test TESTS=echo_basic.phpt
如果一切顺利,您将看到以下通过的测试摘要。
=====================================================================
Running selected tests.
PASS echo - basic test for echo language construct [echo_basic.phpt]
=====================================================================
Number of tests : 1 1
Tests skipped : 0 ( 0.0%) --------
Tests warned : 0 ( 0.0%) ( 0.0%)
Tests failed : 0 ( 0.0%) ( 0.0%)
Expected fail : 0 ( 0.0%) ( 0.0%)
Tests passed : 1 (100.0%) (100.0%)
---------------------------------------------------------------------
Time taken : 0 seconds
=====================================================================
注意测试的 --TEST--
部分的文本是如何在控制台中显示的:
PASS echo - basic test for echo language construct [echo_basic.phpt]
为了说明黑盒测试只关心输出,让我们更改 --FILE--
部分中的 PHP 代码,其余部分保持不变。
<?php
const BANG = '!';
class works {}
echo sprintf('This %s and takes args%s', works::class, BANG);
?>
现在让我们再次运行测试。
$ make test TESTS=echo_basic.phpt
测试应该仍然会通过,因为预期输出仍然与之前相同。让我们尝试另一个示例。将测试的 --FILE--
部分中的 PHP 代码替换为以下代码,然后再次运行测试。
<?php
$url = 'https://gist.githubusercontent.com/SammyK/9c7bf6acdc5bcaa2cfbb404adc61abe6/';
$url .= 'raw/04af30473fc78033f7d8941ecd567934b0f804c0/foo-phpt-output.txt';
echo file_get_contents($url);
?>
虽然这个看起来不太清楚,但我设置了一个具有预期输出的 Gist,我们只是将 HTTP 请求的主体转储到该 Gist。除非出现网络连接问题或删除了 Gist,否则这将产生与其他代码相同的输出,并且测试仍将通过。如果您没有安装 ext/openssl 扩展,这将失败,因为 Gist 位于 https 后面。
让我们再试一个例子。将 --FILE--
部分中的 PHP 代码替换为以下内容。
<?php
ob_start();
echo 'and ';
sleep(1);
echo 'takes ';
sleep(1);
echo 'args!';
$foo = ob_get_contents();
ob_clean();
echo 'This works ';
sleep(1);
echo $foo;
?>
太疯狂了吧?输出一个简单的字符串需要几秒钟的时间,而且在现实生活中你永远不会这样做,但测试仍然会通过。运行测试并不关心你的代码是慢(超时:运行测试的默认超时为 60 秒(或测试内存泄漏时为 300 秒),但您可以使用 --set-timeout
标志指定不同的超时。)还是效率低下,或者只是很糟糕,如果预期输出与实际输出相匹配,你的测试就会通过。