使用单个方法或每个setter验证输入


Validate input using single method or per setter

我刚开始进行测试驱动的开发,我想知道在验证使用用户输入设置的属性时,哪种做法是最好的。我不确定哪种方法更可取,甚至是最好的。

下面是两个这样做的示例方法。

如果有一位大师能在这个问题上有所启发,我将不胜感激!

方法:使用一种验证方法来对其进行全部规则化

考虑以下内容:我有一个类,其中包含属性的getter和setter。第一种方法是使用单个方法验证所有集合属性。

/**
 * @returns bool TRUE if validates, FALSE if not
 */
public function validate()
{
// validate all set properties here
}

在测试方面,我需要考虑每个需要验证的属性的每一个可能的选项,这在我的单元测试中产生了这样的结果:

$this->oObject->setActionCodeGroupID(12);
$this->oObject->setFilename('filename.txt');
$this->oObject->setAmount(10000);
$this->oObject->setMaxUsage(10);
$this->oObject->setMinLength(9);
$this->oObject->setMaxLength(12);
$this->oObject->setPrefix('TM_');
$this->assertTrue($this->oObject->validate());
$this->oObject->setActionCodeGroupID(0);
$this->assertFalse($this->oObject->validate());
$this->oObject->setActionCodeGroupID(12);
$this->oObject->setFilename('');
$this->assertFalse($this->oObject->validate());
$this->oObject->setFilename('filename.txt');
$this->oObject->setAmount(0);
$this->assertFalse($this->oObject->validate());
$this->oObject->setAmount(10000);
$this->oObject->setMaxUsage(0);
$this->assertFalse($this->oObject->validate());
$this->oObject->setMaxUsage(10);
...

方法2:验证setter中的输入

这个方法基本上按照标题所说的去做,它(如果必要的话)会立即验证输入。如果输入未验证,则将引发异常。

public function setCodeGroup(CodeGroup $oCodeGroup)
{
    if (!($oCodeGroup instanceof CodeGroup)) {
        throw new 'InvalidArgumentException('Expected instance of CodeGroup, got '. get_class($oCodeGroup));
    }
    $this->oCodeGroup = $oCodeGroup;
}

IMO实现这一点的最佳方法是方法1。你能做的最好的事情就是测试"好"的情况,然后检查当设置了无效值时每个属性是否都会产生验证错误。

这是一种常见的xUnit模式。

归档的一种方法是使用一个生成有效对象的方法,并将其用于测试所有情况。对于无效的情况,您可以使用dataProvider来传递setter方法名称和值。对于每种情况,都要设置该值并检查对象是否不再有效。

基本上,这和你做的一样,但以一种更有组织的方式:

private function getValidFixture()
{
    $fixture = $this->oObject();
    $this->oObject->setActionCodeGroupID(12);
    $this->oObject->setFilename('filename.txt');
    $this->oObject->setAmount(10000);
    $this->oObject->setMaxUsage(10);
    $this->oObject->setMinLength(9);
    $this->oObject->setMaxLength(12);
    $this->oObject->setPrefix('TM_');
    return $this->oObject;
}
public function testValidObject()
{
    $this->assertTrue($this->getValidFixture()->validate());
}
public function invalidProperties()
{
    return array(
         array(
             $setter = "setActionCodeGroupID",
             $value = 0
         ),
         array(
             $setter = "setFilename",
             $value = ""
         ),
         /** more setters ... */
    );
}
/**
 * @dataProvider invalidProperties
 */
public function testInvalidObject($setter, $value)
{
     $fixture = $this->getValidFixture();
     $fixture->$setter($value);
     $this->assertFalse($fixture->validate());
}

方法2不能因抛出的异常而失败,因为它将因违反类型提示而导致PHP端失败。您不能在不抱怨PHP的情况下将CodeGroup的非实例传递到此函数中。请注意,您可以将此参数设置为可选参数,然后允许显式传递默认值,该值必须不符合类型提示。这样,您就可以允许NULL作为一个值,然后总是要检查您是否有一个正确类型的对象,"否则就做其他事情"。

然而,使用类型提示是一个非常好的主意。它们将防止您传递错误的对象,并使大多数IDE能够为您提供自动完成支持。

现在,我看到你的验证方法的问题是,你有大量的参数被塞在一起,现在你需要大量的测试用例进行正确的检查。在最坏的情况下,每个参数都有一个有效的值范围,以及两个无效的值范围。因此,对于单个参数,您至少有三个测试用例,可能有四个(最小和最大的有效值,以及旁边的无效值)。

添加具有相同数量的单个测试的第二个参数将乘以测试用例的数量。现在,如果不能独立检查参数,则需要9或16个测试用例。

如果您可以独立检查每个参数验证,则需要三个或四个测试用例(两个参数的时间为2),以及一个具有一个有效参数和一个无效参数的额外组合测试用例,即4个额外用例,以测试有效参数和无效参数的组合是否正确组合。这就剩下3+3+4=10或4+4+4=12个测试用例,而不是9或16个。

输入第三个参数。如果没有单独的测试,你现在将有3^3=27个测试用例——有了单独的测试用例,你只需要3*3+2^3=17个测试案例。在这一点上,你可能会利用这样一个事实,即你的逻辑将X个单独的验证结果组合为一个,并遵循每一个验证都必须通过才能使整个验证通过的一般规则。

现在,验证任意参数组合的任务被分解为为为每个参数编写一个特定的验证器,然后将所有这些验证器组合为一个结果。

有了这种方法,你的测试会变得容易得多,因为你可以将所有这些方面划分为单个测试。