如何通过更改通过引用传递的参数来对调用具有副作用的函数的方法进行单元测试?



我有一个调用内置PHP函数的方法,openssl_random_pseudo_bytes.

public function generateRandomBytes()
{
$crypto_secure = TRUE;
// $crypto_secure is passed by reference and will be set to FALSE by
// openssl_random_pseudo_bytes if it uses an insecure algorithm
$random_bytes = openssl_random_pseudo_bytes(16, $crypto_secure);
if (!$crypto_secure)
{
throw new Security_Exception('Random bytes not generated by a cryptographically secure PRNG algorithm');
}
return $random_bytes;
}

我有一个 PHPUnit 测试用例来测试此方法(它所做的只是验证随机生成的字符串长度为 16 字节(。

public function testRandomBytesLength()
{
$myclass = new MyClass();
$this->assertEquals(16, strlen($myclass->generateRandomBytes()));
}

我的问题是,如何测试$crypto_secure为 FALSE 并且必须抛出异常的情况?由于此值作为对openssl_random_pseudo_bytes的引用传入和修改,因此我不确定如何获得此执行路径的测试覆盖率。我的第一个想法是,也许有一个 php.ini 配置,我可以用来强制openssl_random_pseudo_bytes使用加密不安全的算法(通过测试用例中的ini_set(。有什么建议吗?

一种选择是抽象出你的代码,以便你可以模拟openssl方法的返回值:

public function generateRandomBytes()
{
$crypto_secure = TRUE;
$random_bytes = $this->randomPseudoBytes(16, $crypto_secure);
if (!$crypto_secure)
{
throw new Security_Exception('Random bytes not generated by a cryptographically secure PRNG algorithm');
}
return $random_bytes;
}
protected function randomPseudoBytes($length, &$crypto_secure)
{
return openssl_random_pseudo_bytes(16, $crypto_secure);
}

然后,您可以控制核心函数周围的包装器,以测试代码对它的变化有何反应:

/**
* @expectedException Security_Exception
* @expectedExceptionMessage Random bytes not generated by a cryptographically secure PRNG algorithm
*/
public function testCryptoIsNotSecure()
{
$myclass = $this->getMockBuilder(MyClass::class)->setMethods(['randomPseudoBytes'])->getMock();
$myclass->expects($this->once())
->method('randomPseudoBytes')
->will($this->returnCallback(function ($length, &$secure) {
// Mock variable assignment via reference
$secure = false;
});
$myclass->generateRandomBytes();
}

下面的答案是在openssl-random-pseudo-bytes需要两个不可变参数的假设下编写的。相反,第二个参数通过引用传递,如果随机字节是由强算法创建的,则提供反馈。鉴于这些信息,Robbie Averill 提供的答案是一种有效的方法,因为基本上必须处理两个返回语句和一个副作用,这本质上使单元测试复杂化。


在这种情况下,您不需要安全例外。

您希望将openssl_random_pseudo_bytes包装到您自己的自定义函数中,并且希望将长度硬编码为 16 个字符,并始终使用true调用openssl_random_pseudo_bytes。因此,您可以将类编写为:

class MyClass
{
public function generateRandomBytes()
{
return openssl_random_pseudo_bytes(16, true);
}
}

这里唯一有意义的测试是检查返回的字符串长度是否为 16 个字符。你已经报道了这个案子。


为了显示异常抛出的不必要性,您宁愿在构造函数中注入标志或作为参数:

class MyClass
{
/**
* @var bool
*/
private $beSecure;
public function __construct(bool $beSecure)
{
$this->beSecure = $beSecure;
}

/**
* @return string
* @throws Exception
*/
public function generateRandomBytes(): string
{
if (!$this->beSecure) {
// will always throw if false is injected, why would we do that?
throw new Exception("I AM NOT SECURE!");
}
return openssl_random_pseudo_bytes(16, true);
}
}

在单元测试中,您现在可以创建两个测试,一个用于安全案例,一个用于不安全案例,但是为什么要向该类注入 false?然后它总是会失败。

相关内容

最新更新