在测试驱动开发(TDD)中何时验证类的输入



我有一个名为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。在一些简单的情况下,您甚至可能根本不需要在单独的方法中执行此操作。比起显式地测试验证方法,你应该测试该类型的对象是否永远不会无效(即测试构造,而不是验证方法)。

相关内容

  • 没有找到相关文章