php - 使用 PHPUnit 测试具有多个参数的构造函数

标签 php unit-testing testing phpunit

给定以下 Value 对象(没有可公开访问的 setter):

class Address
{
    public function __construct(string $addressLine1, string $addressLine2 = null, string $town, string $county, PostcodeInterface $postcode)
    {
        if (!$this->validateAddressLine($addressLine1)) {
            throw new \InvalidArgumentException(...);
        }
        $this->addressLine1 = $this->normaliseAddressLine($addressLine1);
        ...
    }

    private function validateAddressLine(string $addressLine)
    {
        ...
    }

    private function normaliseAddressLine(string $addressLine)
    {
        ...
    }
}

我有以下测试类:

class AddressTest extends PHPUnit\Framework\TestCase
{
    public function invalidConstructorArgs()
    {
        return [
            ['1/1 Greenbank Road', '%$', 'city', 'county', new Postcode('123FX')]
        ];
    }

    /**
     * @test
     * @dataProvider invalidConstructorArgs
     */
    public function constructor_with_invalid_args_throws_exception($addressLine1, $addressLine2, $town, $county, $postcode)
    {
        $this->expectedException(\InvalidArgumentException::class);
        $address = new Address($addressLine1, $addressLine2, $town, $county, $postcode);
    }
}

如您所见,我目前正在使用 DataProvider 为我的单元测试提供数据。这样做会导致大量要测试的值,这些值都是手动编写的。每个参数都通过适当的私有(private)方法进行验证。目前为了测试这些方法,我正在编写一个数据提供程序,其中包含用于测试这些方法的有效和无效参数值(如下所示):

// test validation on first argument
["invalid", "valid", "valid", "valid", "valid"],
...
// test validation on second argument
["valid", "invalid", "valid", "valid", "valid"],
...
// test validation on third argument
["valid", "valid", "invalid", "valid", "valid"],
...
// etc.

在这种情况下,PHPUnit 中是否有我应该利用的东西被我忽略了?

最佳答案

我同意您的评论,即在您的值对象中使用验证逻辑是一种品味问题,但一个主要缺点是它确实使单元测试变得更加困难,正如您所看到的那样。如果您的值对象现在负责两件事(数据存储和验证),那么测试会变得更加复杂,尤其是当您将验证逻辑设为私有(private)时。

您可能要考虑的一种方法是使用 Reflection直接测试你的私有(private)方法。你会发现很多关于这是否是不好的做法的争论在与这一方面相关的一些相关问题中,我不会在这里再次讨论。就我个人的观点而言,我认为这是少数几个有意义的案例之一。

您可以使用类似这样的方法直接从单元测试运行私有(private)方法:

/**
 * @test
 * @dataProvider invalidAddressLine1Provider
 */
public function invalid_parameter_throws_exception($invalidParameter)
{
    $reflector = new \ReflectionClass(Foo::class);
    // The call to newInstanceWithoutConstructor here is important, since the
    // constructor is what we're looking to avoid
    $instance = $reflector->newInstanceWithoutConstructor();

    $method = $reflector->getMethod('validateAddressLine1');
    $method->setAccessible(true);

    $this->expectException(\Exception::class);
    $method->invoke($instance, $invalidParameter);
}

您还可以将所有验证方法的无效参数合并到一个 dataProvider 中,并将方法名称作为参数传入,以节省重复的反射代码。

public function invalidProvider()
{
    return [
        ['validateAddressLine1', 'invalid value for line 1'],
        ['validateAddressLine2', 'invalid value for line 2'],
        // ...
    ];
}

关于php - 使用 PHPUnit 测试具有多个参数的构造函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46319666/

相关文章:

php - 获取 Symfony 命令的输出并将其保存到文件中

unit-testing - Grails应用程序Spock测试失败

unit-testing - cabal:如何停止构建测试失败?

.net - 我们将理论属性用于什么?

testing - 我们是否在创建测试计划之前/之后准备需求可追溯性矩阵?

ruby-on-rails - 如何在 Rails 3 中运行重点功能测试

javascript - Appium - 滚动到一个不起作用的按钮

php - mysql_connect 和 mysql_selectdb 没有将一些变量作为参数的奇怪问题

javascript - 如何在下拉列表中将事件类添加到列表和父列表

php - 计算帖子被查看的次数