我有一个调用内置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
)。有什么建议吗?
答案 0 :(得分:4)
一种选择是抽象出代码,以便可以模拟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();
}
答案 1 :(得分:1)
下面的答案是在openssl-random-pseudo-bytes接受两个不可混淆的参数的前提下编写的。相反,第二个参数由引用传递,如果随机字节是由强大算法创建的,则给出反馈。有了这些信息,Robbie Averill提供的答案是一种有效的方法,因为必须处理两个基本的返回声明和一个副作用,这从本质上使单元测试变得复杂。
在您的情况下,您不需要安全例外。
您想将openssl_random_pseudo_bytes
包装到您自己的自定义函数中,并且想要将长度硬编码为16个字符,并始终使用openssl_random_pseudo_bytes
调用true
。因此,您可以将您的课程写为:
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?然后它将总是失败。