我有一个名为MachineId
的类。当创建该类的实例时,我要确保构造函数参数得到验证。
class MachineId
{
private $uid;
public function __construct(string $uid)
{
$this->uid = $uid;
}
public function validate() : bool
{
// check uid against specific format (by regex) and return true if valid and false if invalid
}
}
我的问题是:我应该什么时候进行验证?
- 我应该做验证,而我创建的MachineId的实例?在这种情况下,将
validate
方法设为私有是有意义的。这将使使用Unittest测试变得困难。
// Validation WHILE instantiation
class MachineId
{
private $uid;
public function __construct(string $uid)
{
$this->uid = $uid;
if($this->validate() === false) {
// throw exception
}
}
private function validate() : bool
{
...
}
}
- 我应该做验证之前,我创建一个实例的MachineId?这将使通过Unittest测试
validate
方法成为可能,要求在创建对象之前调用验证。使validate
方法成为静态方法(我试图阻止)是有意义的。
// Validation BEFORE instantiation
class MachineId
{
private $uid;
public function __construct(string $uid)
{
$this->uid = $uid;
}
public static function validate(string $uid) : bool
{
...
}
}
if(MachineId::validate($uid) === false) {
// throw exception
};
$machineId = new MachineId($uid);
- 我应该做验证后,我创建的MachineId的实例?这样可以通过Unittest测试验证方法,要求在创建对象后调用验证。
// Validation AFTER instantiation
class MachineId
{
private $uid;
public function __construct(string $uid)
{
$this->uid = $uid;
}
public function validate() : bool
{
...
}
}
$machineId = new MachineId($uid);
if($machineId->validate() === false) {
// throw exception
}
- 我应该创建一个空MachineId,然后设置uid吗?在本例中,我将把验证调用移到
setUid
方法中。同样,将validate方法设为私有是有意义的,因此很难测试。
// Validation in set method
class MachineId
{
private $uid;
public function __construct()
{
$this->uid = null;
}
public function setUid(string $uid)
{
$this->uid = $uid;
if($this->validate() === false) {
// throw exception
}
}
private function validate() : bool
{
...
}
}
$machineId = new MachineId();
$machineId->setUid($uid);
- 我应该创建一个验证类,我也注入构造函数?
// Validation with injecting a validation class
interface ValidatorInterface
{
public function validate(mixed $value) : bool;
}
class MachineIdValidator implements ValidatorInterface
{
public function validate($uid) : bool
{
...
}
}
class MachineId
{
private $uid;
public function __construct(string $uid, ValidatorInterface $validator)
{
if($validator->validate($uid) === false) {
// throw exception
}
$this->uid = $uid;
}
}
$machineId = new MachineId($uid, new MachineIdValidator());
我已经遇到这个问题很多次了,想问一下,如果有人对这种情况有什么建议。如何验证字符串,没有一个静态方法,但它仍然是可测试的,不应该被遗忘?
我最喜欢的解决方案是创建一个MachineId的新实例,并提供uid字符串作为构造函数参数。uid应该被验证,验证方法也应该是可测试的。
单元测试是测试行为,而不是测试方法或类。
我建议在MachineId
类中保持验证。这样做将使您的客户更容易,因为他们不必再次担心验证。这里我更喜欢的方法是创建构造函数private
并创建一个静态工厂方法。然后,您可以在静态工厂中实现验证,并在验证成功时创建对象。测试验证行为就像测试静态工厂本身一样简单。
我最喜欢的解决方案是创建一个MachineId的新实例,并提供uid字符串作为构造函数参数。uid应该被验证,验证方法也应该是可测试的。
我想你是在找这样的东西:
// Validation WHILE instantiation
class MachineId
{
private $uid;
public function __construct(string $uid)
{
$this->uid = $uid;
if(MachineId::validate($uid) === false) {
// throw exception
};
}
public static function validate(string $uid) : bool
{
...
}
}
编写尽可能多的测试,以确保MachineId::validate
被正确实现。
推荐阅读:
- Martin Fowler: Public和Published接口
- Alexis King:解析,不要验证
确保你的对象总是处于一致的状态是好的面向对象设计(这意味着什么取决于你的对象/类型)。对于MachineId
,这可能意味着它永远不会处于无效状态。这意味着您应该在构造函数中进行验证,并在验证失败时抛出异常。
不需要将验证方法设为public。在一些简单的情况下,您甚至可能根本不需要在单独的方法中执行此操作。比起显式地测试验证方法,你应该测试该类型的对象是否永远不会无效(即测试构造,而不是验证方法)。