使用私有/受保护常量测试类

问题描述 投票:1回答:2

在测试类方法时,偶尔我需要将返回值与某些类中定义的常量进行比较。

class FooBar
{
    const RANDOM = 18;
}
....
// Somewhere in test...
$this->assertEquals(FooBar::RANDOM, $mock->doSomething());

现在,从PHP 7.1开始,可以使用visibility modifier定义类常量,这可以更改为:

private const RANDOM = 18;

但是,这会阻止测试工作,因为现在我们正在尝试访问私有常量。

所以现在我们有两个选择:

  1. 将常量声明为public。
  2. 在测试中使用反射。意味着测试成为:

$this->assertEquals( (new ReflectionClass(FooBar::class))->getConstant('RANDOM'), $mock->doSomething() );

第一种方法感觉非常错误,因为我们只是为了测试而不断公开,而不是因为类/层次/商业模型需要它是公开的。

第二个也感觉不对,因为任何IDE都找不到这个用例,所以任何搜索/替换/重构都会在这里失败。

所以我的问题是,第二种情况是否应该使用而不关心重构是否会破坏测试?或者甚至可能在断言中不鼓励使用常量?

unit-testing const access-modifiers php-7.1
2个回答
1
投票

在测试中使用常量实际上是一种不好的做法恕我直言。

您应该测试常量的文字值。 ($this->assertSame(18, $mock->doSomething())

为什么?

因为测试带来的重要价值之一是您注意到代码更改的意外后果。由于常量是私有的,因此它的值永远不会在类之外使用。但是许多不同的事情可能取决于它内部的价值。

现在想象一个不熟悉代码库的初级开发人员的任务是更改使用常量的地方之一并将其从18更改为16.他将把常量的值从18更改为16并执行粗略操作检查常量的使用位置(不注意你的doSomething()方法)。现在,在你的方法中,你绝对需要随机为18,而不是16!但如果你使用常数,他永远不会知道,因为当他将它从18变为16时,断言也会从18变为16.并且测试将通过。

我的经验法则:

永远不要使用从应用程序代码中提取的断言的期望值。始终尽可能使用字面值。


0
投票

由于你已经使常量变为私有,因此它显然不属于类的接口。但是,您可以使用某些公共API间接测试常量的正确设置。这将是一个首选方案。

请注意,此类测试使用公共API,但严格来说仍然是基于类的白盒知识设计的。因此,您正在通过公共API测试实现细节。结果是,在重构测试之后可能会继续工作,但如果类的实现发生变化,它可能会失去其目的。我只提到这一点,因为有些人声称你不应该对实现细节进行单元测试(这是错误的IMO),有些人甚至认为,仅仅通过限制测试来使用公共API,这意味着你没有测试实现细节。

那么,如果上述不能以合理的方式完成呢?我不知道PHP为你提供了什么,但在其他语言中你有更多选择,而不仅仅是公共和私有:在C ++中你有一个朋友概念,可以用来使测试类成为要测试的类的朋友。那里的朋友课程可以访问所有私人信息。

如果这不是PHP的一个选项,你实际上可以增加可见性(使用public,但也许还有protected或package local?)。有时这是一个很好的选择,您可以在“公开可见”和“供公众使用”之间进行逻辑区分。为了使这一点更加明显(并且让用户真正避免使用这样的接口),你可以用丑陋的方式命名它们。对于你的常数,你可以声明一个getter方法for_testing_only__get_constant或更糟糕的东西。

许多人似乎很难改变可见度并区分“公开可见”和“供公众使用”。可能是因为在交货时间紧张或出于任何原因时,它似乎需要在开发和管理骨干期间需要更多的纪律。但是,它是许多语言中的必要概念(较老的语言,如C,但Python有意使用它)。而且,严格地说,通过内省,无论如何都没有真正的隐私。

也就是说,通过命名明确“公开可见”而不是“供公众使用”的选项是IMO比仅仅使用内省更清晰的方法:它允许引入和传达更多级别的私密性,例如'真正的私人和我的意思是','除了测试','公共'以外的所有人。如果你只是使用内省,那么就不会有这种区别。 (你当然可以将私有常量重命名为constant_access_allowed_for_testing - 但这也影响了使用常量的类中的所有位置的可读性。)

TL; DR:尝试使用公共接口,并注意这意味着通过公共接口测试实现细节。如果使用公共API不能合理地完成,那么将事物“公开可见但不打算供公众使用”并通过命名进行通信。不要采用内省方法。

© www.soinside.com 2019 - 2024. All rights reserved.