【问题标题】:How to unit test something like this?如何对这样的东西进行单元测试?
【发布时间】:2017-07-10 20:13:25
【问题描述】:

我有一个这样的例子,它基于 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 注释或其他东西忽略它?

干杯。

【问题讨论】:

    标签: php unit-testing phpunit code-coverage


    【解决方案1】:

    您需要存根 openssl_public_encrypt 才能返回 false。

    假设您的generateRandomKey 方法属于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();
        }
    }
    

    【讨论】:

    • 问题在于你基本上只是在测试“如果 false 为 false 则抛出异常”的逻辑
    • @RobbieAverill,我正在测试 generateRandomKey 在本机函数返回 false 时抛出预期的异常。
    • 可以说是真的,是的 - 从公共 API 的角度来看,这就是你应该在一天结束时测试的 - 我想这个类比应该是测试最终结果,而不是路径到达那里(除非路径也是公共 API)
    • 等一下 - 如果我在 PHP7 世界中的错误,请纠正我,但你不能像那样重新声明原生 PHP 函数...?
    • @RobbieAverill 和 Stephan-v,你试过了吗?关键是要命名它。简化证明repl.it/FrY3/1
    【解决方案2】:

    如果 100% 的覆盖率是您的目标,那么您将需要为实现代码而采取可能“过度优化”代码的路线。在这种情况下,它不会太受欢迎。

    一种选择是将加密行抽象为它自己的部分可模拟方法,但是这样做(并告诉 PHPUnit 它将返回什么)实际上只是检查是否可以抛出异常(字面意思)。

    从语义上讲,getter/setter 方法往往是公开的——而这并没有被任何机构严格执行。我完全可以理解为什么您不希望将 getPrivateKey 作为 API 的一部分,但是您可以setPrivateKey 方法添加到您的公共 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();
    }
    

    当然——这样做你会遇到“你不应该测试你不拥有的代码”的论点,即openssl 方法。如果这是您的目标,这是您为实现 100% 覆盖率所做的权衡。

    【讨论】:

    • 顺便说一句 - 如果您的 getPrivateKey 进行了自己的验证并抛出了自己的异常,您需要对其进行部分模拟,这样它就不会出现在您的 generateRandomKey 测试中
    猜你喜欢
    • 1970-01-01
    • 2018-12-20
    • 1970-01-01
    • 2020-04-07
    • 1970-01-01
    • 2012-02-20
    • 1970-01-01
    • 2022-11-12
    • 1970-01-01
    相关资源
    最近更新 更多