在测试类方法时,有时我需要将返回值与某个类中定义的某个常量进行比较。
class FooBar
{
const RANDOM = 18;
}
....
// Somewhere in test...
$this->assertEquals(FooBar::RANDOM, $mock->doSomething());
现在,从PHP 7.1开始,可以使用可视性修饰符定义类常量,可以将其更改为:
private const RANDOM = 18;
但是,由于我们现在正在尝试访问私有常量,因此该测试无法正常工作。
所以现在我们有两个选择:
$this->assertEquals(
(new ReflectionClass(FooBar::class))->getConstant('RANDOM'),
$mock->doSomething()
);
第一种方法感觉很错误,因为我们只是为了测试而不断公开,而不是因为类/层次结构/业务模型需要公开。
第二个也不满意,因为找不到该用例 通过任何IDE,因此任何搜索/替换/重构都将在这里失败。
所以我的问题是,是否应该在不考虑重构会破坏测试的情况下使用第二种情况?或者也许应该在断言中不建议一般使用常量?
答案 0 :(得分:1)
在测试中使用常量实际上是不好的做法,恕我直言。
您应该测试该常量的文字值。($this->assertSame(18, $mock->doSomething())
为什么?
因为测试为您带来的重要价值之一就是您注意到代码更改的意外后果。由于常量是私有的,因此它的值永远不会在类外部使用。但是许多不同的事情可能在内部取决于它的价值。
现在想象一个不熟悉代码库的初级开发人员,负责将常量的使用位置之一更改为18到16。他将常量的值从18更改为16,然后对使用常量的位置进行粗略检查(不注意doSomething()方法)。现在,在您的方法中,您绝对需要将随机数设为18,而不是16!但是,如果您使用该常数,他将永远不会知道,因为当他将常数从18更改为16时,断言也将从18更改为16。并且测试将通过。
我的经验法则:
从不使用从代码中提取的assert期望值 应用程式。尽可能使用文字值。
答案 1 :(得分:0)
由于您已将常量设为私有,因此它显然不属于此类的接口。但是,您很有可能可以使用某些公共API间接测试常数的正确设置。那将是首选。
请注意,这种测试使用公共API,但严格来说仍是基于该类的白盒知识来设计的。因此,您正在通过公共API测试实现细节。结果是,重构后您的测试可以继续工作,但是如果类的实现发生更改,它可能会失去目的。我仅提及这一点,因为有些人声称您不应该对实现细节进行单元测试(这是IMO的错误),甚至有些人似乎认为,仅通过将测试限制为使用公共API即可,这意味着您不在测试实现细节。
那么,如果不能以明智的方式完成上述操作,该怎么办?我不知道PHP为您提供了什么,但是在其他语言中,除了公共和私有语言之外,您还有更多选择:在C ++中,您有一个Friends的概念,该概念可用于使测试类成为要测试的类的朋友。在那里的朋友班可以访问所有私人详细信息。
如果这不是PHP中的选项,则实际上您可以增加可见性(使用public,但也许也有受保护的或本地的软件包?)。有时,这是一个不错的选择,您可以在此处在“公开可见”和“打算公开使用”之间进行逻辑区分。为了使其更加明显(并使用户真正避免使用此类界面),您可以使用丑陋的方式为它们命名。对于常量,您可以声明一个getter方法for_testing_only__get_constant
或更糟糕的方法。
许多人似乎很难改变可见度,并难以区分“公开可见”和“打算公开使用”。可能是因为在交付时间表紧张或出于任何原因时,在开发和管理骨干中似乎需要更多的纪律性。但是,在许多语言中(这是较早的语言,例如C,但是Python故意使用它),这是一个必要的概念。而且,严格来说,经过内省,反正没有真正的隐私。
也就是说,通过命名使某些内容明确“公开可见”但不“打算供公众使用”的选择是一种比使用内省更干净的IMO方法:它允许引入和传达更多级别的隐私,例如“真正的私密性,我的意思是,”除测试外,所有其他人均私有”,“公开”。如果仅使用自省,则不会进行这种区分。 (不过,您当然可以将私有常量重命名为constant__access_allowed_for_testing-但这也会影响使用该常量的类中所有位置的可读性。)
TL; DR:尝试使用公共接口,请注意,这意味着通过公共接口测试实现细节。如果无法明智地使用公共API,则将事物“公开可见,但不供公共使用”,并通过命名进行交流。不要自省。