🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# Nette Tester - 愉快的单元测试 即使好的程序员犯错误。 一个好的程序员和一个坏的程序员之间的区别是,一个好的人通过使用自动化测试更快地检测到它。 “一个不测试的人注定要重复他自己的错误。”(谚语) “当我们摆脱一个错误,另一个错误。”(墨菲定律) “做测试,做测试,做测试”(MartinIljičFowler) 你有没有在PHP中编写以下代码? ~~~ $obj = new MyClass; $result = $obj->process($input); var_dump($result); ~~~ 所以,你有没有转储一个函数调用结果只是为了检查它返回应该返回什么? 你肯定每天做很多次。 如果一切正常,你删除这个代码,并期望该类不会被打破在未来? 墨菲定律保证相反:-) 事实上,我们写的测试。 如果我们没有删除它,我们可以在任何时候运行它,以验证一切仍然工作,因为它应该。 你可以随着时间的推移创建大量的这些测试,所以如果我们能够自动运行它们将是很好的。 稍微修改测试不需要我们的检查将是有用的,只是为了能够检查自己。 和Nette测试器帮助完全相同。 ## 安装和要求 测试程序需要PHP版本5.3.0或更高版本。 首选的安装方式是Composer和每个后面的示例假设。 但测试仪可以不使用它,也将在下面显示。 ## 通过Composer安装 假设您已经启动并运行Composer,并且一个名为demo的应用程序具有以下结构: ~~~ demo/ ├── src/ # application code we want to test ├── tests/ # tests we are writing ├── vendor/ └── composer.json ~~~ 在终端中导航到应用程序目录,并使用Composer将Tester添加为依赖关系: ~~~ cd demo php composer.phar require --dev nette/tester ~~~ ## 手动安装 从GitHub下载Tester,并通过git clone https://github.com/nette/tester.git 提取或克隆其存储库。 我们的演示应用程序的目录结构现在如下: ~~~ demo/ ├── src/ # application code we want to test ├── tester/ # source of downloaded Tester │ ├── src/ │ ├── tests/ │ ├── ... │ └── readme.md │ └── tests/ # tests we are writing ~~~ ## 运行测试程序 Nette Tester从命令行运行。 我们可以尝试,没有任何参数,它只会显示帮助摘要。 ~~~ cd demo php vendor/nette/tester/src/tester.php # installation by Composer php tester/src/tester.php # manual installation ~~~ 如果Tester由Composer安装,我们还可以使用更方便的快捷方式脚本: ~~~ cd demo vendor/bin/tester # UNIX vendor\bin\tester.bat # Windows ~~~ ## 运行测试 我们的应用程序还没有测试。 我们创建一个简单的类进行测试,并将其保存到文件src / Greeting.php ~~~ <?php class Greeting { public function say($name) { if (!$name) { throw new InvalidArgumentException('Invalid name'); } return "Hello $name"; } } ~~~ 让我们现在写一个测试。 我们将它保存到文件tests / greeting.phpt。 不要因其长度而气馁,后面会展示如何简化它。 ~~~ <?php use Tester\Assert; # Load Tester library require __DIR__ . '/../vendor/autoload.php'; # installation by Composer require __DIR__ . '/../tester/src/bootstrap.php'; # manual installation # Load the tested class. Composer or your autoloader surely takes # care of that in practice. require __DIR__ . '/../src/Greeting.php'; # Adjust PHP behaviour and enable some Tester features (described later) Tester\Environment::setup(); $o = new Greeting; Assert::same( 'Hello John', $o->say('John') ); # we expect the same Assert::exception(function() use ($o) { # we expect an exception $o->say(''); }, 'InvalidArgumentException', 'Invalid name'); ~~~ 测试是写的,我们可以从命令行第一次运行: ~~~ cd tests php greeting.phpt ~~~ 是的,我们第一次运行测试作为普通PHP脚本。 甚至看起来很普遍,大潜力隐藏在它。 我们可以在IDE中单步测试或通过Web浏览器加载它。 第一个运行发现句法错误,如果我们没有输入错误,测试结束,没有错误报告。 让我们将测试中的断言更改为Assert :: same('Hi John',$ o-> say('John')); 让我们看看运行时会发生什么。 随着应用程序的增长,测试的数量也随之增长。 逐个运行测试是不切实际的。 我们使用测试仪运行: ~~~ cd demo tester tests/greeting.phpt # we run a single test tester tests # we run all tests in directory ~~~ 要查看更多测试运行的样子,让我们尝试运行测试测试器本身的所有测试。 它们是其安装的一部分: ~~~ tester vendor/nette/tester/tests # installation by Composer tester tester/tests # manual installation ~~~ ## 测试结果评价 测试仪在测试期间连续打印测试结果: 。 (点) - 测试通过 s - 测试已跳过 F - 测试失败 输出可能如下: ~~~ _____ ___ ___ _____ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) |_| \___ /___) |_| \___ |_|_\ v1.7.0 PHP 5.4.4-14+deb7u7 | 'php-cgi' -n | 8 threads ........s................F......... -- FAILED: tests/greeting.phpt Failed: 'Hello John' should be ... 'Hi John' in src/Framework/Assert.php(370) in src/Framework/Assert.php(52) Tester\Assert::fail() in tests/greeting.phpt(6) Tester\Assert::same() FAILURES! (35 tests, 1 failures, 1 skipped, 1.7 seconds) ~~~ 运行35个测试,一个失败,一个跳过。 如果没有一个失败,Tester的退出代码为零。 非零否则: ## Environment::setup() 在测试中提到了一个有点神秘的Tester \ Environment :: setup()调用。 它有什么作用? 提高错误转储可读性(包括着色),否则,将打印默认PHP转储 使能检查断言在测试中被调用,否则,没有(例如忘记)断言的测试也通过 当使用--coverage时自动启动代码覆盖收集器(稍后描述) 使用是可选的,但建议。 ## Directories structure 它可能看起来很早谈论它,但结构良好的目录与测试节省了很多工作。 我们通过测试类的命名空间将测试分为子目录: ~~~ demo/ └── tests/ ├── NamespaceOne/ │ ├── MyClass.getUsers.phpt │ ├── MyClass.setUsers.phpt │ └── ... │ ├── NamespaceTwo/ │ ├── MyClass.creating.phpt │ ├── MyClass.dropping.phpt │ └── ... │ ├── test.one.phpt ├── test.two.phpt ├── ... └── bootstrap.php ~~~ 我们创建一个文件bootstrap.php。 它包含所有测试的公共代码。 例如类自动加载,环境配置,临时目录创建,助手等。 每个测试加载引导程序,并注意仅测试。 引导可以看起来像: ~~~ require __DIR__ . '/../vendor/autoload.php'; Tester\Environment::setup(); date_default_timezone_set('Europe/Prague'); define('TMP_DIR', '/tmp/demo-app-tests'); ~~~ 运行具有更改的目录结构的测试没有区别。 测试人员以递归方式查找所有* .phpt测试并运行它们: ~~~ cd demo tester tests ~~~ 但我们可以轻松地运行单个命名空间的测试: ~~~ tester tests/NamespaceOne ~~~ ## Assertions 在开始的示例测试中,我们使用了两个断言:Assert :: same()和Assert :: exception()。 现在我们介绍所有其他类型,我们解释如何使用。 它们是Tester \ Assert类的方法,但是为了简化,我们进一步使用它们而没有命名空间。 Assert::same($expected, $actual)# $expected must be the same as $actual. It is the same as PHP operator ===. Assert::notSame($expected, $actual)# Opposite to Assert::same(). Assert::equal($expected, $actual)# $expected must be equal to $actual. Object identities and array keys order are ignored. Assert::notEqual($expected, $actual)# Opposite to Assert::equal(). Assert::contains($needle, $actual)# $actual must contain $needle. If $actual is string, it must contain $needle substring. If it is an array, it must contain $needle item. Assert::notContains($needle, $actual)# Opposite to Assert::contains(). Assert::true($value)# $value must be TRUE, so $value === TRUE. Assert::truthy($value)# $value must be truthy, so $value == TRUE. Assert::false($value)# $value must be FALSE, so $value === FALSE. Assert::falsey($value)# $value must be falsey, so $value == FALSE. Assert::count($count, $value)# $count must be number of elements in $value. Countable is an array or an object implementing Countable. Assert::null($value)# $value must be NULL, so $value === NULL. Assert::nan($value)# $value must be Not a Number. Assert::type($type, $value)# $value must be of given type. As $type we can use string: array list – same as the array but keys must start by zero and must be incremented by one bool callable float int or integer null object resource scalar string class name or object directly then must pass $value instanceof $type **Assert::exception($callable, $class, $message = NULL)** 在$ callable调用中,必须抛出$ class实例的异常。 如果我们传递$ message,那么异常的消息必须匹配(参见Assert :: match())。 如果我们传递$ code,异常的代码必须是相同的。 例如,此测试失败,因为异常的消息不匹配: ~~~ Assert::exception(function() { throw new App\InvalidValueException('Zero value'); }, 'App\InvalidValueException', 'Value is to low'); ~~~ 通过返回抛出的异常,Assert :: exception()是唯一的。 所以我们可以测试一个以前的异常: ~~~ $e = Assert::exception(function() { throw new MyException('Something is wrong', 0, new RuntimeException); }, 'MyException', 'Something is wrong'); Assert::type('RuntimeException', $e->getPrevious()); ~~~ **Assert::error($callable, $type, $message = NULL)** 如果我们传递一个类名为$ type,这个断言的行为与绝对相同Assert :: exception()。 如果$ type是E _...常量之一(例如E_WARNING),则$ callable在调用时必须生成此错误。 如果我们传递$ message消息的错误必须匹配(见Assert :: match())。 例如: ~~~ Assert::error(function() { $i++; }, E_NOTICE, 'Undefined variable: i'); ~~~ 最后一种可能性,如果$ type是一个数组,$ callable必须生成所有预期的错误。 一个例子最好: ~~~ Assert::error(function() { $a++; $b++; }, [ [E_NOTICE, 'Undefined variable: a'], [E_NOTICE, 'Undefined variable: b'], ]); ~~~ **Assert::noError($callable)** 检查函数$ callable不会抛出任何PHP警告/通知/错误或异常。 它对于测试一段没有其他断言的代码是有用的。 **Assert::match($pattern, $actual)** $ actual必须匹配到$ pattern。 我们可以使用两种模式类型: 我们将正则表达式传递为$ pattern。 要分隔它,我们必须使用〜或#。 不支持其他定界符。 例如,测试$ var必须只包含十六进制数字 ~~~ Assert::match( '#^[0-9a-f]$#i', $var ); ~~~ 其他变体类似于字符串比较,但我们可以在$ pattern中使用一些修饰符: %a%除了行结束字符以外的任何一个或多个 %a?%零个或多个除了行结束字符之外的任何东西 %A%一个或多个任何东西,包括行尾字符 %A?%零个或多个任何东西,包括行尾字符 %s%除了行尾字符之外的一个或多个空格字符 %s?%零个或多个空白字符,除了行尾字符 %S%除空格之外的一个或多个字符 %S?%零个或多个字符,除了空格外 %c%任何类型的单个字符(行尾除外) %d%一个或多个数字 %d?%零个或多个数字 %i%有符号整数值 %f%浮点数 %h%一个或多个HEX数字 例子: ~~~ # Again, hexadecimal number test Assert::match( '%h%', $var ); # Generalized path to file and line number Assert::match( 'Error in file %a% on line %i%', $errorMessage ); ~~~ **Assert::matchFile($file, $actual)** 断言与Assert :: match()相同,但模式是从$ file加载的。 它对于非常长的字符串测试很有用。 测试文件可读。 **Assert::fail($message, $actual = NULL, $expected = NULL)** 这种断言总是失败。 这只是方便。 我们可以选择性地传递期望值和实际值。 ## 失败的断言调查 测试器显示断言失败时的错误位置。 当我们比较复杂结构时,测试器创建比较值的转储并将它们保存到目录输出中。 例如,当虚构测试Arrays.recursive.phpt失败时,转储将保存如下: ~~~ demo/ └── tests/ ├── output/ │ ├── Arrays.recursive.actual # actual value │ └── Arrays.recursive.expected # expected value │ └── Arrays.recursive.phpt # failing test ~~~ 我们可以在Tester \ Dumper :: $ dumpDir中更改目录的名称。 ## 跳过测试 有些测试只能在某些情况下运行。 如果这些情况不能满足,我们可以跳过测试。 例如,缺少PHP扩展名: ~~~ if (!extension_loaded('DOM')) { Tester\Environment::skip('Test requires DOM extension to be loaded.'); } ~~~ 测试将停止并标记为s - 跳过。 稍后我们将了解如何使用@skip注释跳过测试。 ## 测试用例 我们已经展示了一个简单的测试,其中断言在本指南开头一个接一个。 有时,用这种方式包含断言来测试类和结构是有用的。 类必须是Tester \ TestCase的后代,我们只是简单地谈论testcase。 ~~~ use Tester\Assert; class GreetingTest extends Tester\TestCase { public function testOne() { Assert::same(......); } public function testTwo() { Assert::match(......); } } # Testing methods run $testCase = new GreetingTest; $testCase->run(); ~~~ 我们可以通过setUp()和tearDown()方法丰富测试用例。 它们在每个测试方法之前/之后调用: ~~~ use Tester\Assert; class NextTest extends Tester\TestCase { public function setUp() { # Preparation } public function tearDown() { # Clean-up } public function testOne() { Assert::same(......); } public function testTwo() { Assert::match(......); } } # Testing methods run $testCase = new NextTest; $testCase->run(); /* Method calls order ------------------ setUp() testOne() tearDown() setUp() testTwo() tearDown() */ ~~~ 有几个注释可以帮助我们测试方法。 我们将它们写入测试方法。 **@throws** 它在测试方法中等同使用Assert :: exception()。 但是符号更易读: ~~~ /** * @throws RuntimeException */ public function testOne() { ... } /** * @throws LogicException Wrong argument order */ public function testTwo() { ... } ~~~ **@dataProvider** 当我们想要多次运行测试方法但是使用不同的参数时,此注释适合。 作为参数,我们写方法名,它返回测试方法的参数。 方法必须返回一个数组或Traversable。 简单示例: ~~~ public function getLoopArgs() { return [ [1, 2, 3], [4, 5, 6], [7, 8, 9], ]; } /** * @dataProvider getLoopArgs */ public function testLoop($a, $b, $c) { ... } ~~~ 另一个注释@dataProvider变体接受一个INI文件的路径(相对于测试文件)作为参数。 该方法被调用的次数与INI文件中包含的节数一样多。 文件loop-args.ini: ~~~ [one] a=1 b=2 c=3 [two] a=4 b=5 c=6 [three] a=7 b=8 c=9 ~~~ 和使用INI文件的方法: ~~~ /** * @dataProvider loop-args.ini */ public function testLoop($a, $b, $c) { ... } ~~~ 类似地,我们可以将路径传递给PHP脚本而不是INI。 它必须返回数组或Traversable。 文件loop-args.php: ~~~ return [ ['a' => 1, 'b' => 2, 'c' => 3], ['a' => 4, 'b' => 5, 'c' => 6], ['a' => 7, 'b' => 8, 'c' => 9], ]; ~~~ ## 测试文件注释 我们在测试文件的开头编写注释,并且输入不区分大小写。 虚构示例: ~~~ /** * TEST: Basic database query test. * * @dataProvider files/databases.ini * @exitCode 56 * @phpVersion < 5.5 */ require __DIR__ . '/../bootstrap.php'; ~~~ ## TEST: 它实际上不是一个注释。 它只设置打印失败或测试日志中的测试名称。 ## @skip 跳过测试。 它是临时测试停用方便。 ## @phpVersion 如果没有由相应的PHP版本运行,则跳过测试。 我们将注释写为@phpVersion [operator]版本。 我们可以省略运算符,默认值为> =。 例子: ~~~ /** * @phpVersion 5.3.3 * @phpVersion < 5.5 * @phpVersion != 5.4.5 */ ~~~ ## @dataProvider 我们将注释写为@dataProvider file.ini。 INI文件路径是相对于测试文件。 测试运行的次数与INI文件中包含的节数一样多。 让我们假设INI文件databases.ini: ~~~ [mysql] dsn = "mysql:host=127.0.0.1" user = root password = ****** [postgresql] dsn = "pgsql:host=127.0.0.1;dbname=test" user = postgres password = ****** [sqlite] dsn = "sqlite::memory:" ~~~ 和文件database.phpt在同一目录中: ~~~ /** * @dataProvider databases.ini */ $args = Tester\Environment::loadData(); ~~~ 测试运行三次,$ args将包含来自mysql,postgresql或sqlite的值。 当我们用@dataProvider写一个问号的注释时,还有一个变体? file.ini。 在这种情况下,如果INI文件不存在,则跳过测试。 注释的可能性还没有被提及。 我们可以在INI文件之后写入条件。 只有在所有条件匹配时,才对给定段进行测试运行。 让我们扩展INI文件: ~~~ [mysql] dsn = "mysql:host=127.0.0.1" user = root password = ****** [postgresql 8.4] dsn = "pgsql:host=127.0.0.1;dbname=test" user = postgres password = ****** [postgresql 9.1] dsn = "pgsql:host=127.0.0.1;dbname=test;port=5433" user = postgres password = ****** [sqlite] dsn = "sqlite::memory:" ~~~ 我们将使用带条件的注释: ~~~ /** * @dataProvider databases.ini postgresql, >=9.0 */ ~~~ 测试对于postgresql 9.1节只运行一次。 其他部分不符合条件。 类似地,我们可以将路径传递给PHP脚本而不是INI。 它必须返回数组或Traversable。 文件databases.php: ~~~ return [ 'postgresql 8.4' => [ 'dsn' => '...', 'user' => '...', ], 'postgresql 9.1' => [ 'dsn' => '...', 'user' => '...', ], ]; ~~~ ## @multiple 我们将它写为@multiple N,其中N是整数。 测试运行正好N次。 ## @testCase 注释没有参数。 我们使用它,当我们写一个测试作为Tester \ TestCase类。 在这种情况下,测试运行的次数与测试方法的次数一样多。 每个测试方法分开运行。 它可以大大加快整个测试过程。 我们建议使用此注释。 ## @ exitCode 我们将它写为@exitCode N,其中N是测试的退出代码。 例如,如果在测试中调用exit(10),我们将注释写为@exitCode 10.如果测试以不同的代码结束,则认为失败。 如果我们省略注释,则验证退出代码0(零) ## @ httpCode 仅当PHP二进制是CGI时才评估注释。 否则忽略。 我们将它写为@httpCode NNN,其中NNN是预期的HTTP代码。 如果我们省略注释,则验证HTTP代码200。 如果我们将NNN写为一个字符串为零,例如any,那么HTTP代码根本不会被检查。 ## @outputMatch a @ outputMatchFile 注释的行为与Assert :: match()和Assert :: matchFile()断言一致。 但是模式是在测试的标准输出中找到的。 一个合适的用例是当我们假设测试以致命错误结束,并且我们需要验证其输出。 ## @ phpIni 它设置测试的INI配置值。 例如,我们将它写为@phpIni precision = 20,它的工作方式与通过参数-d precision = 20从命令行传递值的方式相同。 ## 帮助 **DomQuery** Tester \ DomQuery类有助于测试HTML或XML内容。 所有方法的说明在API文档中。 在这里,我们显示基本用法: ~~~ # HTML which we want to test $html = $template->render(); # We create DOM structure from HTML $dom = Tester\DomQuery::fromHtml($html); # We can test Assert::true( $dom->has('form#registration') ); Assert::true( $dom->has('input[name="username"]') ); Assert::true( $dom->has('input[name="password"]') ); Assert::true( $dom->has('input[type="submit"]') ); ~~~ 我们使用CSS选择DomQuery::有($选择)参数。 ## FileMock Tester \ FileMock模拟内存中的文件,并帮助您测试使用fopen(),file_get_contents()或parse_ini_file()等函数的代码。 例如: ~~~ # Tested class class Logger { private $logFile; public function __construct($logFile) { $this->logFile = $logFile; } public function log($message) { file_put_contents($this->logFile, $message . "\n", FILE_APPEND); } } # New empty file $file = Tester\FileMock::create(''); $logger = new Logger($file); $logger->log('Login'); $logger->log('Logout'); # Created content testing Assert::same( "Login\nLogout\n", file_get_contents($file) ); ~~~ **purge()** Tester \ Helpers类提供方法purge()创建传递目录,但只有当还不存在。 如果是这样,它清除其所有内容。 它是临时目录创建方便。 例如在tests / bootstrap.php: ~~~ @mkdir(__DIR__ . '/tmp'); # @ - directory may already exist define('TEMP_DIR', __DIR__ . '/tmp/' . getmypid()); Tester\Helpers::purge(TEMP_DIR); ~~~ **lock()** 后来我们了解到测试并行运行。 有时我们不需要重叠测试运行。 通常,数据库测试需要准备数据库中的结构和数据,并且它们在测试的运行时间期间不需要干扰它们。 在这些情况下,我们使用Tester \ Environment :: lock($ name,$ dir): ~~~ Tester\Environment::lock('database', __DIR__ . '/tmp'); ~~~ 第一个参数是锁名称。 第二个是保存锁的目录路径。 获取锁的测试首先运行。 其他测试必须等待,直到完成。 **Command line options** 通过运行不带参数的测试程序或使用选项-h,我们获得命令行选项概述。 **-p <path>** 测试程序使用这个PHP二进制测试运行: ~~~ tester -p /home/user/php-5.5.0/php-cgi tests ~~~ **-c <path>** 该选项与PHP二进制相同。 通过的php.ini用于测试。 默认情况下不使用php.ini。 当我们分发php.ini和测试的例子: ~~~ tester -c tests/php.ini tests ~~~ **-l | –log <path>** 测试的进度写入文件。 所有失败,跳过和成功的测试: ~~~ tester -log /var/log/tests.log tests ~~~ **-d <key = value>** 该选项与PHP二进制相同。 它定义了测试的INI值。 该选项可以多次使用。 **-s** 将显示有关跳过的测试的信息。 **--stop-on-fail** 测试人员在第一次失败测试时停止测试。 **-j <num>** 测试以<num>并行线程运行。 默认值为8.如果我们希望连续运行测试,则使用值1。 **-o <console | tap | junit | none>** 输出格式。 默认为控制台格式。 控制台:与默认相同,但在这种情况下不打印标志 tap:适用于机器处理的TAP格式(替换--tap选项) junit:JUnit XML格式,适合机器处理 none:没有打印 **-w | - watch <path>** 测试程序不会在测试完成后结束,但它会保持运行并且正在查看给定的目录。 每当目录更改时,它再次运行测试。 参数可以多次使用。 它在测试写和调试期间很方便。 **-i | –info** 它显示有关测试运行环境的信息。 例如: ~~~ tester -p /home/php/php-5.5.6 -c tests/php.ini --info PHP binary: /home/php/php-5.5.6 PHP version: 5.5.6 (cli) Loaded php.ini files: /var/www/dev/demo/tests/php.ini Loaded extensions: Core, ctype, date, dom, ereg, fileinfo, filter, hash, ... ~~~ **--setup <path>** 测试器在开始时加载给定的PHP脚本。 变量测试器\ Runner \ Runner $ runner可用。 让我们假设文件测试/ runner-setup.php: ~~~ $runner->outputHandlers[] = new MyOutputHandler; ~~~ 我们运行测试器: ~~~ tester --setup tests/runner-setup.php tests ~~~ **--colors 1 | 0** 测试仪在默认情况下检测可着色终端并对其输出进行着色。 此选项超过自动检测。 我们可以通过系统环境变量NETTE_TESTER_COLORS全局设置颜色。 **--coverage <path>** 测试人员将生成一个报告,概述测试覆盖的源代码有多少。 此选项需要启用PHP扩展Xdebug,或PHP 7与PHPDBG SAPI更快。 目标文件扩展名确定内容格式。 HTML或Clover XML。 ~~~ tester tests --coverage coverage.html # HTML report tester tests --coverage coverage.xml # Clover XML report ~~~ **--coverage-src <path>** 我们同时使用选项--coverage。 <path>是我们生成报告的源代码的路径。 **HHVM support** 测试程序支持HHVM 3.3.0或更高版本。 用法很简单,只需使用-p选项传递HHVM二进制路径。 ~~~ tester -p hhvm ~~~ **Tips** 使用exit()和die()结束测试失败消息是不好的做法。 例如exit('Error in connection')结束使用退出代码0(零)的测试,这意味着成功。 始终使用echo'连接错误',然后退出(1)。 编写准确的断言是方便的Assert :: same($ a,$ b),而不是Assert :: true($ a === $ b)。 仅在第一种情况下,我们得到有意义的错误消息。 在另一种情况下,我们得到的FALSE应该只为TRUE,它没有说明$ a和$ b变量。 使用Assert :: nan()进行NAN(非值)测试。 NAN值非常具体,断言Assert :: same()或Assert :: equal()可以工作不可预测。 **Own php.ini** 测试器使用-n选项运行PHP进程,因此没有加载php.ini(甚至不是从UNIX中的/etc/php/conf.d/*.ini加载)。 它确保测试运行的最干净的环境,但它也取消了系统PHP通常加载的所有外部PHP扩展。 如果您需要一些扩展或一些特殊的INI设置,创建自己的php.ini文件,并在测试中分发它。 然后我们运行带有-c选项的Tester。 测试器-c测试/ php.ini。 INI文件可能如下所示: ~~~ [PHP] extension=php_pdo_mysql.dll extension=php_pdo_pgsql.dll memory_limit=512M ~~~