【问题标题】:PHPUnit: Doing assertions on non-public variablesPHPUnit:对非公共变量进行断言
【发布时间】:2019-01-15 20:45:49
【问题描述】:

假设我有一个具有私有属性和关联的公共 getter 和 setter 的类。我想用 PHPUnit 测试该属性在使用 setter 后是否获得正确的值,或者 getter 返回正确的属性。

当然,我可以通过使用 getter 来测试 setter 以查看对象是否存储了正确的值,反之亦然以测试 getter。但是,这并不能保证私有属性就是被设置的属性。

假设我有以下课程。我创建了一个属性,getter 和 setter。但是我在属性名称中打错了字,所以 getter 和 setter 并没有真正操作它们要操作的属性

class SomeClass
{
    private 
        $mane = NULL; // Was supposed to be $name but got fat-fingered!

    public function getName ()
    {
        return ($this -> name);
    }

    public function setName ($newName)
    {
        $this -> name = $newName;
        return ($this);
    }
}

如果我运行以下测试

public function testSetName ()
{
    $this -> object -> setName ('Gerald');
    $this -> assertTrue ($this -> object -> getName () == 'Gerald');
}

我会得到一个通行证。但是,实际上发生了我不希望的非常糟糕的事情。当 setName() 被调用时,它实际上在类中创建了一个新属性,其名称是我认为我的私有属性的名称,只有 setter 创建的那个是公共的!我可以用以下代码证明这一点:

$a  = new SomeClass;

$a -> setName('gerald');
var_dump ($a -> getName ());
var_dump ($a -> name);

它会输出:

字符串(6)“杰拉德”

字符串(6)“杰拉德”

有什么方法可以让我从 PHPUnit 访问私有属性,这样我就可以编写测试来确保我认为正在被获取和设置的属性实际上正在被获取和设置?

或者我是否应该在测试中做一些其他事情来捕捉此类问题,而无需尝试访问被测对象的私有状态?

【问题讨论】:

标签: php unit-testing phpunit white-box


【解决方案1】:

我只想指出一件事。让我们暂时忘记私有字段,专注于您班级的客户关心的内容。在这种情况下,您的类公开了一个合同 - 更改和检索名称的能力(通过 getter 和 setter)。预期的功能很简单:

  • 当我将setName 的名称设置为"Gerald" 时,我希望在调用getName 时得到"Gerald"

就是这样。客户不会(好吧,不应该!)关心内部实现。无论您是使用私有字段名称、哈希集还是通过动态生成的代码调用 Web 服务 - 对客户端而言都无关紧要。从用户的角度来看,您当前遇到的错误根本不是错误。

PHPUnit 是否允许您测试私有变量 - 我不知道。但从单元测试的角度来看,你不应该这样做。

编辑(回应评论):

我理解您对可能暴露内部状态的担忧,但我认为单元测试不是解决此问题的正确工具。你可以想出很多可能的场景,something 可能会做 some else 没有计划的事情。单元测试绝不是万能的,不应该这样使用。

【讨论】:

  • 你是对的,在履行合同的情况下,课程按广告宣传。该类是否实现了它所做出的承诺,这是一个黑盒 PHPUnit 测试通常可以轻松发现的东西。这里的问题是,在履行 API 合同的同时,发生了一个潜在的严重问题(无意暴露内部状态)。这是需要“玻璃盒”或“white-box”测试才能捕获的东西(这就是我标记问题的方式)
  • 谢谢!我正沿着那个兔子洞走。
【解决方案2】:

对于测试属性,我会提出与测试私有方法相同的论点。

You usually don't want to do this.

这是关于测试可观察的行为。

如果您重命名所有属性或决定将它们存储到数组中,则根本不需要调整测试。您希望您的测试告诉您一切正常!当您需要更改测试以确保一切正常时,您将失去所有好处,因为您还可能在更改测试时出错。

因此,总而言之,您失去了测试套件的价值!


只测试 get/set 组合就足够了,但通常不是每个 setter 都应该有一个 getter,仅仅创建它们用于测试并不是一件好事。

通常,您设置一些东西,然后将方法告诉DO(行为)一些东西。对此进行测试(该类执行应执行的操作)是测试的最佳选择,并且应该使测试属性变得多余。


如果你真的想这样做,PHP 反射 API 中有 setAccessible 功能,但我无法举出一个我认为这是可取的示例

寻找未使用的属性来捕获类似这样的错误/问题:

PHP Mess Detector 作为UnusedPrivateField Rule

class Something
{
    private static $FOO = 2; // Unused
    private $i = 5; // Unused
    private $j = 6;
    public function addOne()
    {
        return $this->j++;
    }
}

这将为您生成两个警告,因为变量永远不会被访问

【讨论】:

  • 如果有某种方法可以捕获问题中描述的错误,但我想知道它是什么。
  • @vascowhite:似乎没有(Netbeans 7.0)
  • @GordonM 我已经稍微扩展了这个问题,展示了一个可以帮助您找到这些问题的工具
【解决方案3】:

我同意其他人的观点,一般而言,您希望避免在测试中访问私人信息,但对于您需要的情况,您可以use reflection to read and write the property

【讨论】:

    【解决方案4】:

    您也可以使用Assert::assertAttributeEquals('value', 'propertyName', $object)

    https://github.com/sebastianbergmann/phpunit/blob/3.7/PHPUnit/Framework/Assert.php#L490

    【讨论】:

    • 或者使用Assert::readAttribute($actualClassOrObject, $actualAttributeName)Assert::assertAttributeEquals()使用
    • 此方法现已弃用,测试私有/公共方法是反实践的(根据 PHPunit 贡献者的说法)。
    • @viion 我认为您的意思是私有/受保护的方法,因为测试公共方法是测试的重点;)
    • "anti-practice" 取决于上下文...这在检查选项数组是否已从 json 字符串(可能来自数据库)中正确设置时非常有用或来自外部来源),例如。
    猜你喜欢
    • 2016-07-16
    • 1970-01-01
    • 1970-01-01
    • 2016-09-14
    • 2014-04-05
    • 2012-10-10
    • 1970-01-01
    • 2012-05-12
    相关资源
    最近更新 更多