PHPUnit 在预期异常后不继续测试
Posted
技术标签:
【中文标题】PHPUnit 在预期异常后不继续测试【英文标题】:PHPUnit doesn't continue test after expecting an exception 【发布时间】:2013-01-11 18:48:59 【问题描述】:为什么 phpUnit 在这段代码中不做最后一个异常断言?
public function testConfigOverriding()
$this->dependencyContainer = new DependencyContainer(__DIR__ . "/../../Resources/valid_json.json");
$this->assertEquals('overriden', $this->dependencyContainer->getConfig('shell_commander')['pygmentize_command']);
$unexisting = "unexisting_file";
$this->setExpectedException('Exception', "Configuration file at path \"$unexisting\" doesn't exist.");
$this->dependencyContainer = new DependencyContainer($unexisting);
$invalid = __DIR . "/../../Resources/invalid_json.json";
$this->setExpectedException('Exception', "Configuration JSON file provided is not valid.");
$this->dependencyContainer = new DependencyContainer($invalid);
所以基本上:它测试是否抛出了“unexsisting_file”异常,但完全忽略了“invalid json”测试。我需要为每个抛出的异常进行单独的测试吗?
【问题讨论】:
【参考方案1】:即使使用setExpectedException
,您的测试仍然是常规的 PHP 代码,并且遵循 PHP 的常规规则。如果抛出异常,程序流会立即跳出当前上下文,直到到达try
/catch
块。
在 PHPUnit 中,当您使用 setExpectedException
时,它会告诉 PHPUnit 的核心,它应该在即将运行的代码中期待异常。因此,它会使用 try
/catch
块等待它,如果 catch
被调用并带有预期的异常类型,则通过测试。
但是,在您的测试方法中,正常的 PHP 规则仍然适用——当异常发生时,当前代码块就结束了。除非您在测试方法中有自己的 try
/catch
块,否则不会执行该方法中的任何其他内容。
因此,为了测试多个异常,您有几个选择:
将您自己的try
/catch
添加到测试方法中,这样您就可以在第一个异常之后在该方法中继续进行进一步的测试。
将测试拆分为单独的方法,以便每个异常都在自己的测试中。
这个特殊的例子看起来是使用 PHPUnit 的dataProvider
机制的一个很好的例子,因为你基本上是在用两组数据测试相同的功能。 dataProvider
功能允许您定义一个单独的函数,该函数包含要测试的每组值的输入数据数组。然后将这些值一次一组传递到测试方法中。您的代码将如下所示:
/**
* @dataProvider providerConfigOverriding
*/
public function testConfigOverriding($filename, $expectedExceptionText)
$this->dependencyContainer = new DependencyContainer(__DIR__ . "/../../Resources/valid_json.json");
$this->assertEquals('overriden', $this->dependencyContainer->getConfig('shell_commander')['pygmentize_command']);
$this->setExpectedException('Exception', $expectedExceptionText);
$this->dependencyContainer = new DependencyContainer($filename);
public function providerConfigOverriding()
return array(
array('unexisting_file', 'Configuration file at path "unexisting_file" doesn\'t exist.'),
array(__DIR__ . "/../../Resources/invalid_json.json", "Configuration JSON file provided is not valid."),
);
希望对您有所帮助。
【讨论】:
如果可以的话会给你+2 ;)【参考方案2】:我发现在异常发生后继续测试的最简单方法是在测试中实现 try/finally 块。这实质上允许测试的执行继续进行,而不管抛出任何异常。
这是我的实现:
$this->expectException(InvalidOperationException::class);
try
$report = $service->executeReport($reportId, $jobId);
finally
$this->assertEquals($report->getStatus(), StatusMapper::STATUS_ABORTED);
【讨论】:
我喜欢这个解决方案,它可以解决问题并提醒您仍然抛出异常 迄今为止最优雅的解决方案 简单优雅的解决方案 爱它。这应该是#1【参考方案3】:如果您需要在抛出异常后执行额外的断言,只需使用此模板:
//You can use annotations instead of this method
$this->expectException(FooException::class);
try
$testable->setFoo($bar);
catch (FooException $exception)
//Asserting that $testable->foo stays unchanged
$this->assertEquals($foo, $testable->getFoo());
//re-throwing exception
throw $exception;
【讨论】:
【参考方案4】:对于任何想要做问题标题中的事情的人,这是我想出的最干净的事情。
$exception_thrown = false
try
... stuff that should throw exception ...
catch (SomeTypeOfException $e)
$exception_thrown = true;
$this->assertSame(true, $exception_thrown);
【讨论】:
【参考方案5】:基于@SDC 的回答,我推荐以下内容
进一步拆分测试 避免使用实例属性来引用被测系统进一步拆分测试
如果断言与相同的行为不相关,则单个测试中的多个断言会出现问题:您无法正确命名测试,甚至可能最终在测试方法名称中使用and
。如果发生这种情况,请将测试拆分为单独的测试
避免使用 SUT 的实例属性
当我开始编写测试时,我觉得在 setUp
中安排被测系统 (SUT) 并在各个测试中通过相应的实例属性引用 SUT 时,有机会减少代码重复。
这很诱人,但过了一段时间,当您开始从 SUT 中提取协作者时,您将需要设置测试替身。一开始这可能对您仍然有效,但随后您将开始在不同的测试中以不同方式设置测试替身,而之前旨在避免的所有重复都会回到您身上:您最终会设置两个测试替身,并在您的测试中再次安排 SUT。
当我在代码审查中遇到这个时,我喜欢参考
What does “DAMP not DRY” mean when talking about unit tests?我推荐阅读它。
重要的一点是,您想让编写和测试变得容易。维护测试(或任何代码,如果你愿意的话)主要意味着使代码易于阅读。如果您阅读一些代码,比如说,一个类方法,您想轻松理解它的含义,理想情况下,该方法应该按照您的类名做您期望它做的事情。如果您正在测试不同的行为,请通过创建不同的测试方法使其显而易见。
这还有一个好处是,如果您使用
运行测试$ phpunit --testdox
您最终会得到一份不错的预期行为列表,请参阅
https://phpunit.de/manual/current/en/other-uses-for-tests.html#other-uses-for-tests.agile-documentation基于您的问题的示例
注意我在这个例子中提供的 cmets 只是为了说明进一步拆分测试的想法,在实际代码中我不会有它们。
/**
* The name of this method suggests a behaviour we expect from the
* constructor of DependencyContainer
*/
public function testCanOverrideShellCommanderConfiguration()
$container = new DependencyContainer(__DIR__ . '/../../Resources/valid_json.json');
$this->assertEquals(
'overriden',
$container->getConfig('shell_commander')['pygmentize_command']
);
/**
* While the idea of using a data provider is good, splitting the test
* further makes sense for the following reasons
*
* - running tests with --testdox option as lined out above
* - modifying the behaviour independently
* Currently, a generic Exception is thrown, but you might
* consider using a more specific exception from the SPL library,
* (see http://php.net/manual/en/spl.exceptions.php),
* or creating your own NonExistentConfigurationException class,
* and then a data provider might not make sense anymore)
*/
public function testConstructorRejectsNonExistentConfigurationFile()
$path = 'unexisting_file';
$this->setExpectedException(\Exception::class, sprintf(
'Configuration file at path "%s" doesn\'t exist.',
$path
));
new DependencyContainer($path);
public function testConstructorRejectsInvalidConfigurationFile()
$path = __DIR__ . '/../../Resources/invalid_json.json';
$this->setExpectedException(
\Exception::class,
'Configuration JSON file provided is not valid.'
);
new DependencyContainer($path);
注意我也建议看看
https://thephp.cc/news/2016/02/questioning-phpunit-best-practices【讨论】:
【参考方案6】:首先,有一个错字。替换
__DIR
与
__DIR__
:)
感谢@SDC 的评论,我意识到您确实需要为每个异常提供单独的测试方法(如果您使用的是 PHPUnit 的 expectedException
功能)。您的代码的第三个断言只是没有被执行。如果您需要在一种测试方法中测试多个异常,我建议您在测试方法中编写自己的 try
catch
语句。
再次感谢@SDC
【讨论】:
确实,很好,但是如果执行了该代码,我应该在输出中得到一个错误。它只是不运行任何低于第一个预期异常的东西。 在修正错字之后? 好的。将在本地准备测试 这里是已经准备好的链接:dropbox.com/s/601oxl2lu4afurl/PHPygmentizator.tar.gz 这样就不用再写了。 抱歉,但在此处给出的示例中,永远不会测试第二个异常。测试将通过,因为第一个异常在预期时抛出,但此时测试方法将停止,因为它已抛出异常。这可能是意料之中的,但它并没有被包含在测试方法中;它在 PHPUnit 中被进一步捕获,因此测试方法的其余部分将永远不会运行。您可以通过不期待第二个例外来证明这一点。测试仍然会通过,因为没有调用throwExceptionB
。您需要两种测试方法或测试方法中的try
/catch
。以上是关于PHPUnit 在预期异常后不继续测试的主要内容,如果未能解决你的问题,请参考以下文章
如何在无类 php 文件上使用 PHPUnit Skeleton Generator?
phpunit 测试 assertNotRegExp() 没有按预期工作?
PHPUnit:预期状态码 200 但使用 Laravel 收到 419