如何对这样的东西进行单元测试?

How to unit test something like this?

我有一个这样的例子,它基于 Laravel 密钥生成器:

public function generateRandomKey(): string
{
    $generatedKey = base64_encode(random_bytes(16));

    // Encrypt the generated key with our public key so it is not a 'plain password' anymore.
    $value = openssl_public_encrypt($generatedKey, $crypted, $this->getPublicKey());

    if (!$value) {
        throw new \RuntimeException('Encryption failed: could not generate a random key.');
    }

    return base64_encode($crypted);
}

我想对此进行单元测试,我可以,期待我的 RuntimeException。我想要 100% 的代码覆盖率,但我也不想强制场景只是为了获得 100% 的代码覆盖率。

在这个例子中,我也会让 PHPUnit 触发异常。我不能提供错误的密钥,因为我的 getPublicKey() 是私有的,并且会在我到达此方法的错误之前抛出错误。

触发解密错误并不难,因为我可以提供一个未正确加密的随机值。

那么我怎样才能测试这样的场景并实现 100% 的代码覆盖率。测试这样的东西是否可能甚至是明智的,还是我应该用 PHPUnit 注释或其他东西忽略它?

干杯。

如果 100% 的覆盖率是您的目标,那么您将需要为此继续执行可能 "over optimising" 您的代码的路线。在这种情况下,它不会受到太大的打击。

一个选项是将加密行抽象到它自己的部分可模拟方法中,但是这样做(并告诉 PHPUnit 它会做什么 return)你实际上只是在检查是否可以抛出异常(字面意思)。

从语义上讲,getter/setter 方法往往是 public - 虽然没有任何机构严格执行。我完全可以理解为什么您不希望 getPrivateKey 作为 API 的一部分,但是您 可以 切实可行地向 setPrivateKey 添加一个方法到您的 public API - 这将解决您的单元测试问题:

# File: YourClass
public function setPrivateKey(string $key) : YourClass
{
    $this->privateKey = $key;
    return $this;
}

然后:

# File: YourClassTest
/**
 * @expectedException RuntimeException
 * @expectedExceptionMessage Encryption failed: could not generate a random key.
 */
public function testGenerateRandomKeyThrowsExceptionWhenCannotEncrypt()
{
    $class = new YourClass;
    $class->setPrivateKey('highly-unlikely-to-be-a-valid-private-key');
    $class->generateRandomKey();
}

当然 - 通过这样做你 运行 进入 "you should not test code you don't own" 的论点,即 openssl 方法。这是一个 trade-off 如果这是您的目标,您将实现 100% 的覆盖率。

您需要将 openssl_public_encrypt 存根为 return false。

假设您的 generateRandomKey 方法属于 class StephanV,测试可能如下所示:

namespace Tests;

function openssl_public_encrypt()
{
    return false;
}

class StephanVTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @expectedException \RuntimeException
     */
    public function testGenerateRandomKeyThrowsException()
    {
        $cut = new StephanV;
        $cut->generateRandomKey();
    }
}